diff --git a/agentmain.py b/agentmain.py index 8645ca21..0f8cedba 100644 --- a/agentmain.py +++ b/agentmain.py @@ -246,6 +246,11 @@ def run(self): log_dir = os.path.join(script_dir, 'temp/reflect_logs'); os.makedirs(log_dir, exist_ok=True) script_name = os.path.splitext(os.path.basename(args.reflect))[0] open(os.path.join(log_dir, f'{script_name}_{datetime.now():%Y-%m-%d}.log'), 'a', encoding='utf-8').write(f'[{datetime.now():%m-%d %H:%M}]\n{result}\n\n') + try: + from scheduled_context import record_scheduled_output + record_scheduled_output(task, result, script_dir) + except Exception as e: + print(f'[Reflect] scheduled context error: {e}') if (on_done := getattr(mod, 'on_done', None)): try: on_done(result) except Exception as e: print(f'[Reflect] on_done error: {e}') diff --git a/frontends/chatapp_common.py b/frontends/chatapp_common.py index 2aa50350..f8dfea94 100644 --- a/frontends/chatapp_common.py +++ b/frontends/chatapp_common.py @@ -42,6 +42,16 @@ def build_help_text(commands=HELP_COMMANDS): SUMMARY_RE = re.compile(r"\s*(.*?)\s*", re.DOTALL) +def with_scheduled_context(text): + """Prepend recent scheduled-task outputs to a user message.""" + try: + from scheduled_context import recent_scheduled_context + ctx = recent_scheduled_context(PROJECT_ROOT) + except Exception: + ctx = "" + return f"{ctx}\n\n{text}" if ctx else text + + def clean_reply(text): for pat in TAG_PATS: text = re.sub(pat, "", text or "", flags=re.DOTALL) @@ -307,7 +317,7 @@ async def run_agent(self, chat_id, text, **ctx): self.user_tasks[chat_id] = state try: await self.send_text(chat_id, "思考中...", **ctx) - dq = self.agent.put_task(f"{FILE_HINT}\n\n{text}", source=self.source) + dq = self.agent.put_task(with_scheduled_context(f"{FILE_HINT}\n\n{text}"), source=self.source) last_ping = time.time() while state["running"]: try: diff --git a/frontends/fsapp.py b/frontends/fsapp.py index 9dcabfe4..41b14061 100644 --- a/frontends/fsapp.py +++ b/frontends/fsapp.py @@ -4,7 +4,7 @@ sys.path.insert(0, PROJECT_ROOT) os.chdir(PROJECT_ROOT) from agentmain import GeneraticAgent -from frontends.chatapp_common import format_restore +from frontends.chatapp_common import format_restore, with_scheduled_context from frontends.continue_cmd import handle_frontend_command as handle_continue_frontend, reset_conversation from llmcore import mykeys @@ -604,7 +604,7 @@ def run_agent(): if not hasattr(agent, '_turn_end_hooks'): agent._turn_end_hooks = {} agent._turn_end_hooks[hook_key] = _make_task_hook(card, done_event, on_final) try: - agent.put_task(user_input, source="feishu", images=image_paths) + agent.put_task(with_scheduled_context(user_input), source="feishu", images=image_paths) start = time.time() while not done_event.wait(timeout=3): if not user_tasks.get(open_id, {}).get("running", True): diff --git a/scheduled_context.py b/scheduled_context.py new file mode 100644 index 00000000..e4e665f8 --- /dev/null +++ b/scheduled_context.py @@ -0,0 +1,79 @@ +"""Scheduled task output journal: records what the reflect runner just sent. +Frontends read recent entries and inject them into the next user-prompt so the +agent can answer follow-up questions about its own scheduled output. +""" +import json, os, re, time + +_ROOT = os.path.dirname(os.path.abspath(__file__)) + + +def _compact(text, limit=1800): + text = re.sub(r"\s+", " ", str(text or "")).strip() + return text[:limit] + ("..." if len(text) > limit else "") + + +def _field(text, name): + m = re.search(rf"^\[{re.escape(name)}\]\s*(.+)$", text or "", re.M) + return m.group(1).strip() if m else "" + + +def _read_report(path): + if not path or not os.path.exists(path): + return "" + try: + with open(path, encoding="utf-8", errors="replace") as f: + return f.read() + except Exception: + return "" + + +def record_scheduled_output(task, result, root=_ROOT, keep=20): + """Append one entry to the journal after a reflect-mode task finishes.""" + task_name = _field(task, "定时任务") + if not task_name: + return + report_path = _field(result, "报告路径") or _field(task, "报告路径") + body = _read_report(report_path) if report_path else result + entry = { + "ts": time.time(), + "task": task_name, + "report": report_path, + "text": _compact(body), + } + path = os.path.join(root, "temp", "scheduled_context.jsonl") + os.makedirs(os.path.dirname(path), exist_ok=True) + rows = [] + if os.path.exists(path): + with open(path, encoding="utf-8", errors="replace") as f: + rows = [line for line in f if line.strip()][-keep + 1:] + rows.append(json.dumps(entry, ensure_ascii=False) + "\n") + tmp = path + ".tmp" + with open(tmp, "w", encoding="utf-8") as f: + f.writelines(rows) + os.replace(tmp, path) + + +def recent_scheduled_context(root=_ROOT, max_age=6 * 3600, limit=3): + """Return formatted context string from recent scheduled outputs, or ''.""" + path = os.path.join(root, "temp", "scheduled_context.jsonl") + if not os.path.exists(path): + return "" + now = time.time() + entries = [] + with open(path, encoding="utf-8", errors="replace") as f: + for line in f: + try: + item = json.loads(line) + item["ts"] = float(item.get("ts", 0)) + except Exception: + continue + if now - item["ts"] <= max_age: + entries.append(item) + if not entries: + return "" + parts = ["### Recent scheduled task outputs"] + for item in entries[-limit:]: + when = time.strftime("%m-%d %H:%M", time.localtime(item["ts"])) + report = f"\nReport: {item['report']}" if item.get("report") else "" + parts.append(f"[{when}] {item.get('task', 'scheduled task')}{report}\n{item.get('text', '')}") + return "\n\n".join(parts)