From 18b4a791f012d385f13d3e90a5ef83f3ee0715ee Mon Sep 17 00:00:00 2001 From: Liang Jiaqing Date: Sat, 27 Jun 2026 14:57:10 +0800 Subject: [PATCH] Update loop controller and service discovery --- frontends/stapp.py | 46 ++++++++++++++++++++++++++++++++++++---------- hub.pyw | 4 ++-- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/frontends/stapp.py b/frontends/stapp.py index ce1bcb39..f490a165 100644 --- a/frontends/stapp.py +++ b/frontends/stapp.py @@ -71,6 +71,27 @@ def init(): agent = init() +def build_prompt(objective): + return f"""读取 {agent.log_path} 尾部,获取 agent 的最新输出。 +用户的 loop 诉求:{objective} +判断该 agent 是否偷懒、是否真正完成诉求,用 输出要追加给它的指令: +一般复述 objective,或不超过 10 字的督促,如:别停,继续 / 这就叫最优?你优化到位了吗 / 看我要求,你达成了吗 / 你好好看清楚 / 你能不能看看记忆 +只输出 …。""" + +@st.cache_resource +def get_controller(): + b = {'ev': threading.Event(), 'obj': '', 'out': None, 'ready': False} + def loop(): + ag = GeneraticAgent(); ag.verbose = False; ag.log_path = False + threading.Thread(target=ag.run, daemon=True).start() + while True: + b['ev'].wait(); b['ev'].clear() + dq = ag.put_task(build_prompt(b['obj']), source="controller") + while 'done' not in (it := dq.get()): pass + ms = re.findall(r'(.*?)', it['done'], re.S) + b['out'] = ms[-1].strip() if ms else None; b['ready'] = True + threading.Thread(target=loop, daemon=True).start(); return b + st.title("🖥️ Cowork") st.session_state.setdefault('autonomous_enabled', False) @@ -119,9 +140,7 @@ def render_sidebar(): field-sizing: content; min-height: 1.6em !important; height: auto !important; } """, unsafe_allow_html=True) - def _sync_loop_prompt(): - st.session_state.loop_prompt = st.session_state.loop_prompt_input - loop_prompt = st.text_area("Loop prompt", value=st.session_state.get('loop_prompt', "继续" if LANG=='zh' else 'next'), key="loop_prompt_input", height=1, on_change=_sync_loop_prompt) + st.text_area("Loop prompt", value=st.session_state.get('loop_prompt_input', "继续" if LANG=='zh' else 'next'), key="loop_prompt_input", height=1) if st.session_state.get('loop_enabled'): if st.button("⏹️ Stop Loop"): st.session_state.loop_enabled = False @@ -130,8 +149,8 @@ def render_sidebar(): else: if st.button("🔁 Loop!"): st.session_state.loop_enabled = True - st.session_state.loop_prompt = loop_prompt - st.session_state['_inject_prompt'] = loop_prompt + get_controller() + st.session_state['_inject_prompt'] = st.session_state.get('loop_prompt_input', '') st.toast("🔁 Looping"); st.rerun(scope="app") st.divider() if st.button(T('auto_start')): @@ -252,10 +271,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()) - # ── 循环回调:回答完成后自动注入下一条 ── + # ── 循环回调:回答完成戳醒 controller 决策(去程,现取最新objective) ── if st.session_state.get('loop_enabled'): - st.session_state['_inject_prompt'] = st.session_state.get('loop_prompt', '继续') - st.rerun() + b = get_controller() + b['obj'] = st.session_state.get('loop_prompt_input', ''); b['ready'] = False; b['ev'].set() if "messages" not in st.session_state: st.session_state.messages = [] for msg in st.session_state.messages: @@ -365,11 +384,18 @@ elif st.session_state.get('display_queue') is not None: render_main_stream() # ── 空闲自主行动:fragment 定时检测,替代 launch.pyw 的 idle_monitor ── -@st.fragment(run_every=timedelta(minutes=5)) +@st.fragment(run_every=timedelta(minutes=1)) def _idle_checker(): + if st.session_state.get('loop_enabled'): # 循环回程:取controller回填 + b = get_controller() + if b['ready']: + b['ready'] = False + if b['out']: st.session_state['_inject_prompt'] = b['out'] + else: st.session_state.loop_enabled = False + st.rerun(scope="app") + return 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') diff --git a/hub.pyw b/hub.pyw index 303fbffb..1feb3291 100644 --- a/hub.pyw +++ b/hub.pyw @@ -17,7 +17,7 @@ def acquire_singleton(): def discover_services(): services = [] - EXCLUDES = {'goal_mode.py', 'chatapp_common.py', 'tuiapp.py'} + EXCLUDES = {'goal_mode.py', 'chatapp_common.py', 'tuiapp.py', 'tui', '_master'} reflect_dir = os.path.join(BASE_DIR, 'reflect') if os.path.isdir(reflect_dir): for f in sorted(os.listdir(reflect_dir)): @@ -29,7 +29,7 @@ def discover_services(): frontends_dir = os.path.join(BASE_DIR, 'frontends') if os.path.isdir(frontends_dir): for f in sorted(os.listdir(frontends_dir)): - if 'app' in f and f.endswith('.py') and f not in EXCLUDES: + if 'app' in f and f.endswith('.py') and len([x for x in EXCLUDES if x in f]) == 0: if 'stapp' in f: cmd = [sys.executable, '-m', 'streamlit', 'run', 'frontends/' + f, '--server.headless=true'] else: cmd = [sys.executable, 'frontends/' + f] services.append({'name': 'frontends/' + f, 'cmd': cmd})