diff --git a/agentmain.py b/agentmain.py
index 68aacdd3..e4a7807f 100644
--- a/agentmain.py
+++ b/agentmain.py
@@ -162,7 +162,7 @@ class GenericAgent:
self.llmclient.backend.stream = False
self.llmclient.backend.read_timeout = max(self.llmclient.backend.read_timeout, 1200)
gen = agent_runner_loop(self.llmclient, sys_prompt, raw_query, handler, TOOLS_SCHEMA,
- max_turns=80, verbose=self.verbose, yield_info=True)
+ max_turns=180, verbose=self.verbose, yield_info=True)
try:
full_resp = ""; last_pos = 0; curr_turn = 0; turn_resps = []
for chunk in gen:
diff --git a/assets/tool_usable_history.json b/assets/tool_usable_history.json
deleted file mode 100644
index 1539e942..00000000
--- a/assets/tool_usable_history.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"role":"user","content":[{"type":"text","text":"code_run个helloworld,根据结果简单评价你的工具配置"}]},{"role":"assistant","content":[{"type":"text","text":"\n\n用户要求code_run helloworld并评价工具配置\n\n```python\nprint(\"Hello, World!\")\n```"},{"type":"tool_use","id":"tooluse_GlmTNw190WGgK4sBBOiDHK","name":"code_run","input":{"script":"print(\"Hello, World!\")"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"tooluse_GlmTNw190WGgK4sBBOiDHK","content":"{\"status\": \"success\", \"stdout\": \"Hello, World!\\r\\n\", \"exit_code\": 0}"},{"type":"text","text":"\n### [WORKING MEMORY]\n\n\n[USER]: code_run个helloworld,根据结果简单评价你的工具配置\n\nCurrent turn: 1\n"}]},{"role":"assistant","content":[{"type":"text","text":"\n\ncode_run helloworld成功,评价工具配置\n\n工具配置没问题。`code_run` 返回了结构化的 JSON(`status`、`stdout`、`exit_code`),执行链路通畅,cwd 默认落在 temp 目录。\n\n刚才还验证了 `inline_eval` 能正确访问 agent 内部状态并导出 history,`file_patch` 也能精准改文件。整套工具链从代码执行、文件读写、到 agent 内省都是通的。"}]}]
\ No newline at end of file
diff --git a/frontends/stapp.py b/frontends/stapp.py
index b15f1ed6..d5480537 100644
--- a/frontends/stapp.py
+++ b/frontends/stapp.py
@@ -13,6 +13,7 @@ sys.path.append(os.path.abspath(script_dir))
import streamlit as st
import time, json, re, threading, queue
+from datetime import timedelta
from agentmain import GeneraticAgent
import chatapp_common # activate /continue command (monkey patches GeneraticAgent)
from continue_cmd import handle_frontend_command, reset_conversation, list_sessions, extract_ui_messages
@@ -34,13 +35,27 @@ if LANG not in ('zh', 'en'): LANG = 'zh'
I18N = {
'zh': {
'force_stop': '强行停止任务',
- 'reinject_tools': '重新注入工具',
'desktop_pet': '🐱 桌面宠物',
+ 'suggest_btn': '🎯 给我找点事做',
+ 'suggest_prompt': '按照自主行动的规划部分,充分分析我的情况,给我生成一批TODO,务必让我感兴趣',
+ 'auto_start': '开始空闲自主行动',
+ 'auto_pause': '⏸️ 禁止自主行动',
+ 'auto_enable': '▶️ 允许自主行动',
+ 'auto_on_cap': '🟢 自主行动运行中,会在你离开它30分钟后自动进行',
+ 'auto_off_cap': '🔴 自主行动已停止',
+ 'auto_prompt': '[AUTO]🤖 用户已经离开超过30分钟,作为自主智能体,请阅读自动化sop,执行自动任务。',
},
'en': {
'force_stop': 'Force Stop',
- 'reinject_tools': 'Reinject Tools',
'desktop_pet': '🐱 Desktop Pet',
+ 'suggest_btn': '🎯 Suggest tasks',
+ 'suggest_prompt': 'Following the planning section of autonomous sop, analyze my situation thoroughly and generate a batch of TODOs that will interest me.',
+ 'auto_start': 'Start idle auto-action',
+ 'auto_pause': '⏸️ Pause auto-action',
+ 'auto_enable': '▶️ Enable auto-action',
+ 'auto_on_cap': '🟢 Auto-action enabled, triggers after 30min idle',
+ 'auto_off_cap': '🔴 Auto-action disabled',
+ 'auto_prompt': '[AUTO]🤖 User has been idle for over 30 minutes. As an autonomous agent, read the automation SOP and execute automatic tasks.',
},
}
def T(key): return I18N.get(LANG, I18N['zh']).get(key, key)
@@ -72,14 +87,6 @@ def render_sidebar():
agent.next_llm(selected_idx); st.rerun(scope="fragment")
if st.button(T('force_stop')):
agent.abort(); st.toast("Stop signal sended"); st.rerun()
- if st.button(T('reinject_tools')):
- agent.llmclient.last_tools = ''
- try:
- hist_path = os.path.join(script_dir, '..', 'assets', 'tool_usable_history.json')
- with open(hist_path, 'r', encoding='utf-8') as f: tool_hist = json.load(f)
- agent.llmclient.backend.history.extend(tool_hist)
- st.toast(f"Tools injected")
- except Exception as e: st.toast(f"Injected tools failed: {e}")
if st.button(T('desktop_pet')):
kwargs = {'creationflags': 0x08} if sys.platform == 'win32' else {}
pet_script = os.path.join(script_dir, 'desktop_pet_v2.pyw')
@@ -103,25 +110,37 @@ def render_sidebar():
agent._turn_end_hooks['pet'] = _pet_hook
st.toast("Desktop pet started")
- if LANG == 'zh':
- if st.button('🎯 给我找点事做'):
- st.session_state['_inject_prompt'] = '按照自主行动的规划部分,充分分析我的情况,给我生成一批TODO,务必让我感兴趣'
- st.rerun(scope="app")
- st.divider()
- if st.button("开始空闲自主行动"):
- st.session_state.last_reply_time = int(time.time()) - 1800
+ if st.button(T('suggest_btn')):
+ st.session_state['_inject_prompt'] = T('suggest_prompt')
+ st.rerun(scope="app")
+ st.divider()
+ if st.session_state.get('loop_enabled'):
+ if st.button("⏹️ Stop Loop"):
+ st.session_state.loop_enabled = False
+ st.toast("⏹️ Loop stopped"); st.rerun(scope="app")
+ st.caption("🔁 Looping")
+ else:
+ loop_prompt = st.text_input("Loop prompt", value="继续" if LANG=='zh' else 'next', key="loop_prompt_input")
+ if st.button("🔁 Loop!"):
+ st.session_state.loop_enabled = True
+ st.session_state.loop_prompt = loop_prompt
+ st.session_state['_inject_prompt'] = loop_prompt
+ st.toast("🔁 Looping"); st.rerun(scope="app")
+ st.divider()
+ if st.button(T('auto_start')):
+ st.session_state.last_reply_time = int(time.time()) - 1800
+ st.session_state.autonomous_enabled = True
+ st.rerun(scope="app")
+ if st.session_state.autonomous_enabled:
+ if st.button(T('auto_pause')):
+ st.session_state.autonomous_enabled = False
+ st.toast(T('auto_pause')); st.rerun(scope="app")
+ st.caption(T('auto_on_cap'))
+ else:
+ if st.button(T('auto_enable'), type="primary"):
st.session_state.autonomous_enabled = True
- st.toast("已将上次回复时间设为1800秒前,自主行动已激活"); st.rerun(scope="app")
- if st.session_state.autonomous_enabled:
- if st.button("⏸️ 禁止自主行动"):
- st.session_state.autonomous_enabled = False
- st.toast("⏸️ 已禁止自主行动"); st.rerun(scope="app")
- st.caption("🟢 自主行动运行中,会在你离开它30分钟后自动进行")
- else:
- if st.button("▶️ 允许自主行动", type="primary"):
- st.session_state.autonomous_enabled = True
- st.toast("✅ 已允许自主行动"); st.rerun(scope="app")
- st.caption("🔴 自主行动已停止")
+ st.toast("✅"); st.rerun(scope="app")
+ st.caption(T('auto_off_cap'))
with st.sidebar: render_sidebar()
def fold_turns(text):
@@ -226,6 +245,10 @@ def render_main_stream(prompt=None):
if response:
st.session_state.messages.append({"role": "assistant", "content": response})
st.session_state.last_reply_time = int(time.time())
+ # ── 循环回调:回答完成后自动注入下一条 ──
+ if st.session_state.get('loop_enabled'):
+ st.session_state['_inject_prompt'] = st.session_state.get('loop_prompt', '继续')
+ st.rerun()
if "messages" not in st.session_state: st.session_state.messages = []
for msg in st.session_state.messages:
@@ -334,5 +357,15 @@ elif st.session_state.get('display_queue') is not None:
# No new prompt but a task is mid-flight (typically a /btw rerun) — resume drain.
render_main_stream()
-if st.session_state.autonomous_enabled:
- st.markdown(f"""
{st.session_state.get('last_reply_time', int(time.time()))}
""", unsafe_allow_html=True)
+# ── 空闲自主行动:fragment 定时检测,替代 launch.pyw 的 idle_monitor ──
+@st.fragment(run_every=timedelta(minutes=5))
+def _idle_checker():
+ if not st.session_state.get('autonomous_enabled'): return
+ if st.session_state.get('display_queue') is not None: return # 正在运行中
+ if st.session_state.get('loop_enabled'): return # 循环模式自己管
+ last = st.session_state.get('last_reply_time', int(time.time()))
+ if time.time() - last > 1800:
+ st.session_state['_inject_prompt'] = T('auto_prompt')
+ st.session_state['last_reply_time'] = int(time.time()) # 防重入
+ st.rerun(scope="app")
+_idle_checker()
diff --git a/ga.py b/ga.py
index 5fec4fe2..a9076204 100644
--- a/ga.py
+++ b/ga.py
@@ -561,7 +561,7 @@ class GenericAgentHandler(BaseHandler):
self.history_info.append(f'[Agent] {summary}')
_plan = self._in_plan_mode()
- if turn % 75 == 0 and (not _plan):
+ if turn % 175 == 0 and (not _plan):
next_prompt += f"\n\n[DANGER] 已连续执行第 {turn} 轮。必须总结情况进行ask_user,不允许继续重试。"
elif turn % 7 == 0:
next_prompt += f"\n\n[SYSTEM] 已连续执行第 {turn} 轮。建议调用update_working_checkpoint保存关键上下文。禁止无效重试;若无有效进展,必须切换策略:1. 探测物理边界 2. **重读相关SOP**"
@@ -569,7 +569,7 @@ class GenericAgentHandler(BaseHandler):
if _plan and turn >= 10 and turn % 5 == 0:
next_prompt = f"[Plan Hint] 正在计划模式。必须 file_read({_plan}) 确认当前步骤,回复开头引用:📌 当前步骤:...\n\n" + next_prompt
- if _plan and turn >= 120: next_prompt += f"\n\n[DANGER] Plan模式已运行 {turn} 轮,已达上限。必须 ask_user 汇报进度并确认是否继续。"
+ if _plan and turn >= 190: next_prompt += f"\n\n[DANGER] Plan模式已运行 {turn} 轮,已达上限。必须 ask_user 汇报进度并确认是否继续。"
injkeyinfo = consume_file(self.parent.task_dir, '_keyinfo')
injprompt = consume_file(self.parent.task_dir, '_intervene')
diff --git a/launch.pyw b/launch.pyw
index 7e5c466b..ee79c978 100644
--- a/launch.pyw
+++ b/launch.pyw
@@ -22,31 +22,6 @@ def start_streamlit(port):
proc = subprocess.Popen(cmd)
atexit.register(proc.kill)
-def inject(text):
- window.evaluate_js(f"""
- const textarea = document.querySelector('textarea[data-testid="stChatInputTextArea"]');
- if (textarea) {{
- // 1. 用原生 setter 设置值(绕过 React)
- const nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
- nativeTextAreaValueSetter.call(textarea, {repr(text)});
- // 2. 触发 React 的 input 事件
- textarea.dispatchEvent(new Event('input', {{ bubbles: true }}));
- // 3. 触发 change 事件(有些组件需要)
- textarea.dispatchEvent(new Event('change', {{ bubbles: true }}));
- // 4. 延迟提交
- setTimeout(() => {{
- const btn = document.querySelector('[data-testid="stChatInputSubmitButton"]');
- if (btn) {{btn.click();console.log('Submitted:', {repr(text)});}}
- }}, 200);
- }}""")
-
-def get_last_reply_time():
- last = window.evaluate_js("""
- const el = document.getElementById('last-reply-time');
- el ? parseInt(el.textContent) : 0;
- """) or 0
- return last or int(time.time())
-
PASTE_HOOK_JS = """if (!window._pasteHooked) { window._pasteHooked = true;
document.addEventListener('paste', e => {
const items = e.clipboardData?.items; if (!items) return;
@@ -65,21 +40,13 @@ PASTE_HOOK_JS = """if (!window._pasteHooked) { window._pasteHooked = true;
}, true);
}"""
-def idle_monitor():
- last_trigger_time = 0
+def paste_hook_injector():
+ """注入剪贴板粘贴图片钩子,等窗口加载后执行一次,之后每60秒补注入防页面刷新丢失"""
+ time.sleep(5) # 等 webview 加载
while True:
- time.sleep(5)
- try:
- window.evaluate_js(PASTE_HOOK_JS)
- now = time.time()
- if now - last_trigger_time < 120: continue
- last_reply = get_last_reply_time()
- if now - last_reply > 1800:
- print('[Idle Monitor] Detected idle state, injecting task...')
- inject("[AUTO]🤖 用户已经离开超过30分钟,作为自主智能体,请阅读自动化sop,执行自动任务。")
- last_trigger_time = now
- except Exception as e:
- print(f'[Idle Monitor] Error: {e}')
+ try: window.evaluate_js(PASTE_HOOK_JS)
+ except: pass
+ time.sleep(60)
if __name__ == '__main__':
import argparse
@@ -140,8 +107,7 @@ if __name__ == '__main__':
print('[Launch] Task Scheduler started (duplicate prevented by scheduler port lock)')
else: print('[Launch] Task Scheduler not enabled (--sched)')
- monitor_thread = threading.Thread(target=idle_monitor, daemon=True)
- monitor_thread.start()
+ threading.Thread(target=paste_hook_injector, daemon=True).start()
if os.name == 'nt':
screen_width = get_screen_width()
x_pos = screen_width - WINDOW_WIDTH - RIGHT_PADDING