thursday-agent 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +3 -2
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.json +8 -1
- package/.next/routes-manifest.json +18 -6
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +13 -13
- package/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/artifact/route.js +10 -6
- package/.next/server/app/api/artifact/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/files/route.js +6 -5
- package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/route.js +8 -8
- package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/route.js +8 -8
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/thread/[id]/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/bot/thread/[id]/route.js +15 -0
- package/.next/server/app/api/bot/thread/[id]/route.js.nft.json +1 -0
- package/.next/server/app/api/bot/thread/[id]/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/bot/thread/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/bot/thread/route.js +15 -0
- package/.next/server/app/api/bot/thread/route.js.nft.json +1 -0
- package/.next/server/app/api/bot/thread/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/config/route.js +7 -7
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/events/route.js +3 -3
- package/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/server/app/api/file/[...path]/route.js +5 -5
- package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/catalog/route.js +8 -8
- package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/credits/route.js +8 -8
- package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/route.js +8 -8
- package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/subscription/route.js +8 -8
- package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/[name]/route.js +6 -6
- package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/oauth/callback/route.js +4 -4
- package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/route.js +6 -6
- package/.next/server/app/api/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/tools/route.js +6 -6
- package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/edit/route.js +13 -13
- package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/route.js +7 -6
- package/.next/server/app/api/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/[source]/[name]/route.js +6 -6
- package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +6 -6
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/call/route.js +10 -11
- package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/skills/route.js +10 -11
- package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/tool-call/route.js +15 -15
- package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/missing/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/workspace/missing/route/build-manifest.json +12 -0
- package/.next/server/app/api/workspace/missing/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/workspace/missing/route.js +12 -0
- package/.next/server/app/api/workspace/missing/route.js.map +5 -0
- package/.next/server/app/api/workspace/missing/route.js.nft.json +1 -0
- package/.next/server/app/api/workspace/missing/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/workspace/route.js +5 -4
- package/.next/server/app/api/workspace/route.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
- package/.next/server/app/artifact/[...path]/page.js +4 -3
- package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next/server/app/page/server-reference-manifest.json +67 -45
- package/.next/server/app/page.js +10 -9
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +3 -2
- package/.next/server/chunks/[root-of-the-server]__02846fb._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__049f521._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__05luceg._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__06--3q3._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__07bnah_._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__08eae20._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__0_ue8oe._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0c9fgca._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__0g-ueei._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__0h_x6q6._.js +5 -0
- package/.next/server/chunks/{[root-of-the-server]__0pa2n68._.js → [root-of-the-server]__0kxrvxf._.js} +2 -2
- package/.next/server/chunks/{[root-of-the-server]__0q0vvt2._.js → [root-of-the-server]__0ov3iir._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__0qwtlmx._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__0sm5xvm._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0ye3da2._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0zp35ul._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__144u9d_._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__18-2tsq._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__1e-ci13._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1e2365e._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__1fix932._.js +8 -0
- package/.next/server/chunks/{[root-of-the-server]__0ce8nf8._.js → [root-of-the-server]__1g3t7vn._.js} +3 -3
- package/.next/server/chunks/[root-of-the-server]__1kp-83u._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__1kv8p3e._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__1orn-5b._.js +2 -3
- package/.next/server/chunks/_02szwk8._.js +5 -0
- package/.next/server/chunks/_05h2cii._.js +1 -1
- package/.next/server/chunks/_06c9kie._.js +216 -0
- package/.next/server/chunks/{_1x6kd1r._.js → _07y2gj-._.js} +1 -1
- package/.next/server/chunks/_0a43a-0._.js +3 -0
- package/.next/server/chunks/_0bujjey._.js +5 -0
- package/.next/server/chunks/_0c9j6rb._.js +1 -1
- package/.next/server/chunks/_0dkd549._.js +3 -0
- package/.next/server/chunks/_0ej1kvv._.js +3 -0
- package/.next/server/chunks/_0f6ts7z._.js +3 -0
- package/.next/server/chunks/_0gm9hyv._.js +55 -0
- package/.next/server/chunks/_0i55en5._.js +25 -0
- package/.next/server/chunks/_0k50n27._.js +18 -0
- package/.next/server/chunks/_0khihjk._.js +3 -3
- package/.next/server/chunks/_0n74zsf._.js +5 -5
- package/.next/server/chunks/_0nvm9ef._.js +3 -0
- package/.next/server/chunks/_0p56fn0._.js +3 -0
- package/.next/server/chunks/_0recufk._.js +1 -1
- package/.next/server/chunks/_0xbkz10._.js +3 -0
- package/.next/server/chunks/_10tkr26._.js +3 -0
- package/.next/server/chunks/{_0ioz3nd._.js → _11u2kp7._.js} +1 -1
- package/.next/server/chunks/_11ujpya._.js +3 -0
- package/.next/server/chunks/_127nx_p._.js +5 -0
- package/.next/server/chunks/_17l5ygt._.js +1 -1
- package/.next/server/chunks/_19uauj6._.js +3 -0
- package/.next/server/chunks/_1_5mnq4._.js +5 -5
- package/.next/server/chunks/_1abl6t9._.js +55 -0
- package/.next/server/chunks/_1d0j961._.js +3 -3
- package/.next/server/chunks/_1dbixxz._.js +3 -0
- package/.next/server/chunks/_1dc_d1q._.js +98 -0
- package/.next/server/chunks/_1drgfx4._.js +5 -5
- package/.next/server/chunks/_1e48czp._.js +3 -3
- package/.next/server/chunks/_1f31u5x._.js +5 -0
- package/.next/server/chunks/_1kg70wc._.js +3 -3
- package/.next/server/chunks/_1klbcft._.js +5 -0
- package/.next/server/chunks/_1krd9rf._.js +4 -1
- package/.next/server/chunks/_1kwmzab._.js +3 -0
- package/.next/server/chunks/{_1pf4qe-._.js → _1oxj9fa._.js} +1 -1
- package/.next/server/chunks/{_1f46eie._.js → _1pcmdbb._.js} +1 -1
- package/.next/server/chunks/_1qgix-j._.js +3 -0
- package/.next/server/chunks/{lib_logger_ts_1exwjut._.js → _1rpchta._.js} +5 -5
- package/.next/server/chunks/_1vsej-s._.js +5 -0
- package/.next/server/chunks/_1yx0p_s._.js +5 -0
- package/.next/server/chunks/{_0rnf-6n._.js → _20fsr_z._.js} +6 -6
- package/.next/server/chunks/_2128hkj._.js +5 -0
- package/.next/server/chunks/_next-internal_server_app_api_bot_thread_[id]_route_actions_1fstjn2.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_bot_thread_route_actions_003ccso.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_workspace_missing_route_actions_1fidi6p.js +3 -0
- package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
- package/.next/server/chunks/features_0-cnt0l._.js +4 -0
- package/.next/server/chunks/features_0hdaci0._.js +4 -0
- package/.next/server/chunks/features_1e39j7-._.js +8 -0
- package/.next/server/chunks/features_bot_bot_query_ts_12mgkl2._.js +1 -1
- package/.next/server/chunks/features_memory_1uqvksn._.js +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0r28-sm._.js → node_modules_consola_dist_chunks_prompt_mjs_0a7-pbw._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0v_7drq._.js → node_modules_consola_dist_chunks_prompt_mjs_0gzvgfv._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0i7cvml._.js → node_modules_consola_dist_chunks_prompt_mjs_107pxfn._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0icqaqw._.js → node_modules_consola_dist_chunks_prompt_mjs_15hbel8._.js} +1 -1
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_18i-23e._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1bewgf3._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1kipdeo._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1nlww1m._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1q77nfx._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1xqdzx_._.js +3 -0
- package/.next/server/chunks/{node_modules_zod_v4_1801ax7._.js → node_modules_zod_v4_0lkux5-._.js} +2 -2
- package/.next/server/chunks/{node_modules_zod_v4_1cha_bw._.js → node_modules_zod_v4_1jmr9cq._.js} +2 -2
- package/.next/server/chunks/{node_modules_zod_v4_0ubdflj._.js → node_modules_zod_v4_1yrxmek._.js} +2 -2
- package/.next/server/chunks/node_modules_zod_v4_classic_errors_0tp0y-q.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__09q9med._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0bxsfyn._.js +101 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0lyi8i2._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__16jc196._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__172cq4x._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__18dthbp._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__18e68w8._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1fll8dc._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1nygfik._.js +5 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1sdphb0._.js +8 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1waa3rc._.js +100 -0
- package/.next/server/chunks/ssr/_00rehl_._.js +3 -0
- package/.next/server/chunks/ssr/{_1lz_cq2._.js → _02oc_h5._.js} +76 -89
- package/.next/server/chunks/ssr/_03_928f._.js +3 -0
- package/.next/server/chunks/ssr/_05yghx6._.js +3 -0
- package/.next/server/chunks/ssr/_08c4z-9._.js +3 -0
- package/.next/server/chunks/ssr/_0_qfdlw._.js +4 -0
- package/.next/server/chunks/ssr/_0c9e5w6._.js +9 -0
- package/.next/server/chunks/ssr/{_0s99bgb._.js → _0dh2e1j._.js} +9 -3
- package/.next/server/chunks/ssr/_0dr-ddg._.js +3 -0
- package/.next/server/chunks/ssr/_0eaog6_._.js +3 -0
- package/.next/server/chunks/ssr/_0evkpto._.js +3 -0
- package/.next/server/chunks/ssr/_0g_rlog._.js +3 -0
- package/.next/server/chunks/ssr/_0geg212._.js +3 -0
- package/.next/server/chunks/ssr/{_1gd8q8l._.js → _0hxt2ul._.js} +2 -2
- package/.next/server/chunks/ssr/_0i_6q1w._.js +2 -2
- package/.next/server/chunks/ssr/_0lu1n_x._.js +1 -1
- package/.next/server/chunks/ssr/{_15j2_8f._.js → _0wzaht-._.js} +2 -2
- package/.next/server/chunks/ssr/_0z-g0uy._.js +3 -0
- package/.next/server/chunks/ssr/_123ttsf._.js +3 -0
- package/.next/server/chunks/ssr/_123y-ah._.js +3 -0
- package/.next/server/chunks/ssr/_12w42gi._.js +3 -0
- package/.next/server/chunks/ssr/_161kqav._.js +3 -0
- package/.next/server/chunks/ssr/{_0ap2fzn._.js → _16c6_xm._.js} +2 -2
- package/.next/server/chunks/ssr/_18opah7._.js +3 -0
- package/.next/server/chunks/ssr/_18xqmny._.js +3 -0
- package/.next/server/chunks/ssr/_1_krfph._.js +3 -0
- package/.next/server/chunks/ssr/{_1-_etts._.js → _1anqots._.js} +2 -2
- package/.next/server/chunks/ssr/_1ey7qn-._.js +70 -0
- package/.next/server/chunks/ssr/_1g9vs7q._.js +3 -0
- package/.next/server/chunks/ssr/_1jmz33w._.js +3 -0
- package/.next/server/chunks/ssr/{_0n3toaw._.js → _1k0spz7._.js} +2 -2
- package/.next/server/chunks/ssr/_1rgtxhr._.js +5 -0
- package/.next/server/chunks/ssr/_1slp25y._.js +3 -0
- package/.next/server/chunks/ssr/{_1fojd41._.js → _1x9pskj._.js} +3 -3
- package/.next/server/chunks/ssr/_1y6b5mr._.js +101 -0
- package/.next/server/chunks/ssr/_1ztk7p6._.js +7 -0
- package/.next/server/chunks/ssr/components_ui_tabs_tsx_0ej40b4._.js +3 -0
- package/.next/server/chunks/ssr/features_bot_thread_query_ts_023eklt._.js +4 -0
- package/.next/server/chunks/ssr/features_config_components_config-setting_tsx_0xfwqdg._.js +1 -1
- package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
- package/.next/server/chunks/ssr/{_1-teee5._.js → lib_logger_ts_0-olsze._.js} +2 -7
- package/.next/server/chunks/ssr/{node_modules_0cxaxk9._.js → node_modules_00_51_v._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_04jf_m8._.js → node_modules_022sipm._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0g7fzk3._.js → node_modules_0332gw4._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1cx_ql6._.js → node_modules_04vxchl._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1vh5ntv._.js → node_modules_0746rzi._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0yugy6m._.js → node_modules_0d5uzof._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_09q35v_._.js → node_modules_0exkwcm._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_08dhkiq._.js → node_modules_0fr-ml8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_14q6g4n._.js → node_modules_0mxvg8h._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0arus10._.js → node_modules_0qapgjr._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1eg7m6c._.js → node_modules_0qbfyjh._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1cz2hle._.js → node_modules_0t-9nup._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_17bwo43._.js → node_modules_0tusx4d._.js} +3 -3
- package/.next/server/chunks/ssr/{node_modules_1efrqxi._.js → node_modules_0vsxbzp._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0_-_a--._.js → node_modules_0vv0-fg._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1gdh31p._.js → node_modules_0wsvp00._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1hpopmo._.js → node_modules_1-urho9._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1_pc9kq._.js → node_modules_16pi_dd._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_00etblx._.js → node_modules_1d53yiq._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1aaxuef._.js → node_modules_1g1eihe._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_198nmll._.js → node_modules_1gpednh._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0pvd7q-._.js → node_modules_1gu1pph._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0b__sgl._.js → node_modules_1he1w8_._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1qpjvy5._.js → node_modules_1lnjapm._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0940ns5._.js → node_modules_1lynj9m._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0x3-ply._.js → node_modules_1seuurl._.js} +2 -2
- package/.next/server/chunks/ssr/{node_modules_14om4ar._.js → node_modules_1u6w1pb._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0had7xd._.js → node_modules_1xn6saw._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1b748ic._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_067_exa._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1z4hm5e._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0nrrdaq._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@vercel_oidc_dist_06mk8id._.js → node_modules_@vercel_oidc_dist_21a5haa._.js} +1 -1
- package/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_trash_mjs_0px2gzr._.js +3 -0
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +68 -46
- package/.next/static/chunks/0dr16v67vj4n7.js +2 -0
- package/.next/static/chunks/0g4b348l0pve7.js +1 -0
- package/.next/static/chunks/0joan-69kbspd.js +1 -0
- package/.next/static/chunks/0vlgzqo9ka90v.js +1 -0
- package/.next/static/chunks/0zrupxgci5gqc.js +1 -0
- package/.next/static/chunks/10k2xvap03z20.css +1 -0
- package/.next/static/chunks/18mkhstgiqcbt.js +1 -0
- package/.next/static/chunks/{15m575979un5f.js → 1i-u2wzub_to1.js} +2 -2
- package/.next/static/chunks/1kp5nfp06nj9h.js +5 -0
- package/.next/static/chunks/1ohslk4568tm3.js +1 -0
- package/.next/static/chunks/1sbmwswc9k2-1.js +1 -0
- package/.next/static/chunks/1t9qms5pwwa3n.js +1 -0
- package/.next/static/chunks/1vj_5bnbxx84e.js +1 -0
- package/.next/static/chunks/1vqezbhjm3goq.js +1 -0
- package/.next/static/chunks/{3akmqm77muj4-.js → 1wlo979mx-y3w.js} +1 -1
- package/.next/static/chunks/26ql4y-43xwhp.js +1 -0
- package/.next/static/chunks/{0fl3gwe4-l6tz.js → 2_pnccjp6j1ca.js} +1 -1
- package/.next/static/chunks/2a51-ywz9b2a3.js +1 -0
- package/.next/static/chunks/2fvdgxih7x7hr.js +1 -0
- package/.next/static/chunks/2jlz8zp4jax_m.js +18 -0
- package/.next/static/chunks/2nvepds1fu2ux.js +1 -0
- package/.next/static/chunks/3addj3i4-izma.js +1 -0
- package/.next/static/chunks/3bfwpww3m3x4u.js +1 -0
- package/.next/static/chunks/3gfh7nxwusbt0.js +1 -0
- package/.next/static/chunks/3gwa44cvz4qoi.js +1 -0
- package/.next/static/chunks/{38gweqdo4jpfj.js → 3izg0a3k87q1-.js} +5 -5
- package/.next/static/chunks/3zk6q3-3f9swq.js +99 -0
- package/.next/static/chunks/3zkg5iup3v4a2.js +12 -0
- package/.next/static/chunks/405m0bqtoihev.js +1 -0
- package/.next/static/chunks/42wcg3wfg_m_p.js +1 -0
- package/README.md +27 -216
- package/app/api/events/app-event.ts +6 -4
- package/app/api/query-key.ts +25 -16
- package/bin/database.mjs +37 -0
- package/bin/thursday.mjs +44 -28
- package/components/ui/custom-icon.tsx +0 -60
- package/components/ui/radio-group.tsx +37 -0
- package/components/ui/shiny-text.tsx +33 -6
- package/database/migrations/{20260910173049_numerous_spirit → 20260916145701_clever_multiple_man}/migration.sql +80 -24
- package/database/migrations/{20260910173049_numerous_spirit → 20260916145701_clever_multiple_man}/snapshot.json +689 -110
- package/database/migrations/20260916160713_thread_cancelled/migration.sql +2 -0
- package/database/migrations/{20260911030320_opposite_jack_power → 20260916160713_thread_cancelled}/snapshot.json +706 -84
- package/docs/how-it-works.md +39 -36
- package/docs/images/keeps-going.png +0 -0
- package/docs/images/two-minds.png +0 -0
- package/docs/live-calls.md +231 -0
- package/docs/thread-rooms.md +71 -0
- package/features/ai/chatgpt.ts +1 -2
- package/features/ai/live.schema.ts +128 -0
- package/features/ai/load-tools.ts +148 -77
- package/features/ai/model.schema.ts +2 -120
- package/features/ai/model.ts +52 -88
- package/features/ai/prompts/bot.prompt.ts +91 -214
- package/features/ai/prompts/live.prompt.ts +265 -0
- package/features/ai/prompts/prompt-helper.ts +91 -32
- package/features/ai/prompts/thursday.prompt.ts +125 -172
- package/features/ai/tools/bot.tool.ts +38 -145
- package/features/ai/tools/call.tool.ts +1 -1
- package/features/ai/tools/connected.ts +5 -3
- package/features/ai/tools/mcp.tool.ts +7 -4
- package/features/ai/tools/memory.tool.ts +35 -31
- package/features/ai/tools/skills.tool.ts +36 -14
- package/features/ai/tools/studio.tool.ts +26 -21
- package/features/ai/tools/tool-name.ts +2 -5
- package/features/ai/tools/workspace.tool.ts +33 -10
- package/features/artifact/artifact.action.ts +2 -2
- package/features/artifact/artifact.query.ts +112 -61
- package/features/artifact/artifact.schema.ts +8 -6
- package/features/artifact/components/artifact-setting.tsx +83 -33
- package/features/bot/bot.action.ts +43 -36
- package/features/bot/bot.memory.ts +137 -20
- package/features/bot/bot.query.ts +28 -11
- package/features/bot/bot.run.ts +299 -636
- package/features/bot/bot.runner.ts +468 -844
- package/features/bot/bot.schema.ts +93 -82
- package/features/bot/bot.seed.ts +141 -64
- package/features/bot/components/bot-mark.tsx +324 -232
- package/features/bot/components/bot-room.tsx +1613 -697
- package/features/bot/components/bot-roster.tsx +8 -13
- package/features/bot/components/bot-setting.tsx +58 -42
- package/features/bot/components/bot-tool.tsx +55 -87
- package/features/bot/components/mark-palette.tsx +35 -32
- package/features/bot/components/path-chips.tsx +33 -3
- package/features/bot/components/thread-badge.tsx +34 -0
- package/features/bot/components/thread-reply.tsx +639 -0
- package/features/bot/components/thread-setting.tsx +674 -0
- package/features/bot/mark.const.ts +79 -31
- package/features/bot/room.query.ts +889 -0
- package/features/bot/room.schema.ts +64 -0
- package/features/bot/seed-bots.ts +2 -1
- package/features/bot/thread.query.ts +971 -0
- package/features/bot/thread.store.ts +587 -0
- package/features/config/components/voice-key.tsx +113 -88
- package/features/config/config.const.ts +5 -9
- package/features/connectors/mcp.schema.ts +0 -2
- package/features/intro/components/intro.tsx +169 -139
- package/features/memory/components/memory-setting.tsx +2 -7
- package/features/memory/memory.schema.ts +14 -23
- package/features/settings/components/setting-ui.tsx +0 -44
- package/features/settings/components/settings.tsx +12 -12
- package/features/settings/settings.alert.ts +3 -3
- package/features/settings/settings.store.ts +1 -1
- package/features/skills/skills.schema.ts +0 -2
- package/features/thursday/components/ascii-field.tsx +24 -44
- package/features/thursday/components/ascii-orb.tsx +359 -358
- package/features/thursday/components/call-log.tsx +46 -44
- package/features/thursday/components/face.tsx +35 -10
- package/features/thursday/components/tab-state.tsx +3 -3
- package/features/thursday/components/thursday-mark.tsx +1 -0
- package/features/thursday/components/thursday-setting.tsx +631 -332
- package/features/thursday/components/thursday.tsx +106 -41
- package/features/thursday/components/voice-picker.tsx +255 -0
- package/features/thursday/thursday.action.ts +94 -68
- package/features/thursday/thursday.query.ts +49 -30
- package/features/thursday/thursday.schema.ts +27 -30
- package/features/thursday/thursday.store.ts +11 -7
- package/features/thursday/tool-call.ts +3 -3
- package/features/thursday/tool-line.ts +5 -3
- package/features/thursday/use-thursday.ts +725 -450
- package/features/workspace/workspace.query.ts +17 -0
- package/features/workspace/workspace.ts +103 -50
- package/lib/live/live.schema.ts +47 -0
- package/lib/live/live.server.ts +155 -0
- package/lib/live/live.session.ts +574 -0
- package/lib/{realtime/realtime.tap.ts → live/live.tap.ts} +41 -12
- package/lib/live/live.transport.ts +152 -0
- package/lib/protocol/use-server-pages.ts +9 -5
- package/lib/utils.ts +21 -20
- package/package.json +2 -2
- package/public/voices/alloy.ogg +0 -0
- package/public/voices/ash.ogg +0 -0
- package/public/voices/ballad.ogg +0 -0
- package/public/voices/beacon.ogg +0 -0
- package/public/voices/bossa.ogg +0 -0
- package/public/voices/cedar.ogg +0 -0
- package/public/voices/cinder.ogg +0 -0
- package/public/voices/coral.ogg +0 -0
- package/public/voices/delta.ogg +0 -0
- package/public/voices/echo.ogg +0 -0
- package/public/voices/gleam.ogg +0 -0
- package/public/voices/marin.ogg +0 -0
- package/public/voices/meridian.ogg +0 -0
- package/public/voices/quartz.ogg +0 -0
- package/public/voices/ripple.ogg +0 -0
- package/public/voices/sage.ogg +0 -0
- package/public/voices/shimmer.ogg +0 -0
- package/public/voices/stone.ogg +0 -0
- package/public/voices/tempo.ogg +0 -0
- package/public/voices/verse.ogg +0 -0
- package/public/voices/vesper.ogg +0 -0
- package/public/voices/willow.ogg +0 -0
- package/scripts/artifact-paths.test.mts +25 -0
- package/scripts/bot-context.test.mts +1408 -0
- package/scripts/live-history.test.mts +115 -0
- package/scripts/live-session.test.mts +948 -0
- package/server.js +1 -1
- package/skills/browser/SKILL.md +17 -6
- package/skills/computer/SKILL.md +1 -1
- package/skills/find-skills/SKILL.md +3 -8
- package/skills/interactive-page/SKILL.md +12 -7
- package/skills/interactive-page/references/diagram.md +89 -0
- package/skills/interactive-page/scripts/archify/LICENSE +22 -0
- package/skills/interactive-page/scripts/archify/README.md +25 -0
- package/skills/interactive-page/scripts/archify/assets/JetBrainsMono-OFL.txt +93 -0
- package/skills/interactive-page/scripts/archify/assets/template.html +14934 -0
- package/skills/interactive-page/scripts/archify/bin/archify.mjs +2126 -0
- package/skills/interactive-page/scripts/archify/bin/open-artifact.mjs +86 -0
- package/skills/interactive-page/scripts/archify/bin/preview.mjs +653 -0
- package/skills/interactive-page/scripts/archify/bin/visual-check.mjs +829 -0
- package/skills/interactive-page/scripts/archify/delta/architecture-delta.mjs +1223 -0
- package/skills/interactive-page/scripts/archify/examples/agent-run.lifecycle.json +60 -0
- package/skills/interactive-page/scripts/archify/examples/agent-tool-call.workflow.json +94 -0
- package/skills/interactive-page/scripts/archify/examples/async-job-roundtrip.sequence.json +61 -0
- package/skills/interactive-page/scripts/archify/examples/cache-miss-request.sequence.json +82 -0
- package/skills/interactive-page/scripts/archify/examples/checkout-platform.base.architecture.json +31 -0
- package/skills/interactive-page/scripts/archify/examples/checkout-platform.head.architecture.json +31 -0
- package/skills/interactive-page/scripts/archify/examples/deployment-release.lifecycle.json +49 -0
- package/skills/interactive-page/scripts/archify/examples/event-stream.dataflow.json +57 -0
- package/skills/interactive-page/scripts/archify/examples/incident-response.workflow.json +64 -0
- package/skills/interactive-page/scripts/archify/examples/product-analytics.dataflow.json +76 -0
- package/skills/interactive-page/scripts/archify/examples/production-deployment.architecture.json +71 -0
- package/skills/interactive-page/scripts/archify/examples/release-delivery.workflow.json +62 -0
- package/skills/interactive-page/scripts/archify/examples/web-app.architecture.json +46 -0
- package/skills/interactive-page/scripts/archify/migrations/workflow-v2.mjs +279 -0
- package/skills/interactive-page/scripts/archify/recipes/scenarios.mjs +284 -0
- package/skills/interactive-page/scripts/archify/references/authoring-contract.md +243 -0
- package/skills/interactive-page/scripts/archify/references/delivery-contract.md +120 -0
- package/skills/interactive-page/scripts/archify/references/viewer-runtime.md +45 -0
- package/skills/interactive-page/scripts/archify/renderers/architecture/grid.mjs +62 -0
- package/skills/interactive-page/scripts/archify/renderers/architecture/render-architecture.mjs +1078 -0
- package/skills/interactive-page/scripts/archify/renderers/dataflow/README.md +104 -0
- package/skills/interactive-page/scripts/archify/renderers/dataflow/render-dataflow.mjs +483 -0
- package/skills/interactive-page/scripts/archify/renderers/lifecycle/README.md +115 -0
- package/skills/interactive-page/scripts/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
- package/skills/interactive-page/scripts/archify/renderers/sequence/README.md +114 -0
- package/skills/interactive-page/scripts/archify/renderers/sequence/render-sequence.mjs +464 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/brand-marks.mjs +563 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/cli.mjs +218 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/desktop-readability.mjs +26 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/diagnostics.mjs +127 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/engineering-profiles.mjs +157 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/generated-brand-marks.mjs +3 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/generated-validators.mjs +13 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/geometry.mjs +1423 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/i18n.mjs +595 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/layout-report.mjs +40 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/legend.mjs +217 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/output-path.mjs +340 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/repository-evidence.mjs +238 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/repository-location.mjs +58 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/text-fit.mjs +49 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/utils.mjs +232 -0
- package/skills/interactive-page/scripts/archify/renderers/shared/validator.mjs +86 -0
- package/skills/interactive-page/scripts/archify/renderers/workflow/README.md +223 -0
- package/skills/interactive-page/scripts/archify/renderers/workflow/render-workflow.mjs +35 -0
- package/skills/interactive-page/scripts/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
- package/skills/interactive-page/scripts/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
- package/skills/interactive-page/scripts/archify/schemas/README.md +211 -0
- package/skills/interactive-page/scripts/archify/schemas/architecture.schema.json +178 -0
- package/skills/interactive-page/scripts/archify/schemas/common.schema.json +115 -0
- package/skills/interactive-page/scripts/archify/schemas/dataflow.schema.json +243 -0
- package/skills/interactive-page/scripts/archify/schemas/lifecycle.schema.json +266 -0
- package/skills/interactive-page/scripts/archify/schemas/sequence.schema.json +223 -0
- package/skills/interactive-page/scripts/archify/schemas/workflow.schema.json +428 -0
- package/skills/interactive-page/scripts/archify/scripts/check-render-output.mjs +916 -0
- package/skills/interactive-page/scripts/archify/scripts/render-examples.mjs +26 -0
- package/skills/interactive-page/scripts/bundle-artifact.sh +9 -8
- package/skills/skill-creator/SKILL.md +4 -5
- package/.next/server/app/api/bot/task/[id]/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/bot/task/[id]/route.js +0 -16
- package/.next/server/app/api/bot/task/[id]/route.js.nft.json +0 -1
- package/.next/server/app/api/bot/task/[id]/route_client-reference-manifest.js +0 -3
- package/.next/server/app/api/bot/task/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/bot/task/route.js +0 -16
- package/.next/server/app/api/bot/task/route.js.nft.json +0 -1
- package/.next/server/app/api/bot/task/route_client-reference-manifest.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__01dkp4a._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__02xjoto._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__04q--0f._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__0bl-e63._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0ffbg85._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0jq4ohy._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__0ls6_8y._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__0ncs9uu._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1-ga0kh._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__11n256_._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__12cabg9._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1339jnv._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__18_teo8._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__1_04mcf._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__1anc1p9._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1ccon1w._.js +0 -10
- package/.next/server/chunks/[root-of-the-server]__1fizn31._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1llj3-b._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1p8m3f4._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1wa1k7r._.js +0 -8
- package/.next/server/chunks/_0-73of8._.js +0 -55
- package/.next/server/chunks/_0-fjql4._.js +0 -26
- package/.next/server/chunks/_00xh_q0._.js +0 -25
- package/.next/server/chunks/_03a0897._.js +0 -3
- package/.next/server/chunks/_051qh8u._.js +0 -3
- package/.next/server/chunks/_05sovvj._.js +0 -3
- package/.next/server/chunks/_0fzqt2y._.js +0 -134
- package/.next/server/chunks/_0mecv0f._.js +0 -26
- package/.next/server/chunks/_0mh8j4m._.js +0 -3
- package/.next/server/chunks/_0obojvv._.js +0 -3
- package/.next/server/chunks/_0sb7tlz._.js +0 -3
- package/.next/server/chunks/_0sbynis._.js +0 -3
- package/.next/server/chunks/_0u10wmw._.js +0 -55
- package/.next/server/chunks/_0u4aiy2._.js +0 -3
- package/.next/server/chunks/_0wp4le3._.js +0 -3
- package/.next/server/chunks/_0y5fg-x._.js +0 -3
- package/.next/server/chunks/_1230wbx._.js +0 -10
- package/.next/server/chunks/_12nheja._.js +0 -3
- package/.next/server/chunks/_13510xh._.js +0 -3
- package/.next/server/chunks/_15hq89o._.js +0 -3
- package/.next/server/chunks/_18-1wcc._.js +0 -55
- package/.next/server/chunks/_1_9mjx-._.js +0 -3
- package/.next/server/chunks/_1c78vw5._.js +0 -229
- package/.next/server/chunks/_1h5_8sk._.js +0 -3
- package/.next/server/chunks/_1hdnxb7._.js +0 -3
- package/.next/server/chunks/_1j8k_j5._.js +0 -3
- package/.next/server/chunks/_1nula3v._.js +0 -3
- package/.next/server/chunks/_1php4eq._.js +0 -3
- package/.next/server/chunks/_1yuv4vp._.js +0 -3
- package/.next/server/chunks/_1zjrdag._.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_bot_task_[id]_route_actions_1_k_8u3.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_bot_task_route_actions_0xln6ra.js +0 -3
- package/.next/server/chunks/features_0rpfxbk._.js +0 -3
- package/.next/server/chunks/features_1qf3cei._.js +0 -3
- package/.next/server/chunks/features_bot_0zt92oh._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1xoq2rl._.js +0 -3
- package/.next/server/chunks/ssr/[externals]__0dizgek._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__092knlj._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0mz33a7._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0s9dq5u._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0s9l2sv._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0vnmv-s._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0xsog8n._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__10-dz5o._.js +0 -61
- package/.next/server/chunks/ssr/[root-of-the-server]__1gtlw6v._.js +0 -43
- package/.next/server/chunks/ssr/[root-of-the-server]__1ox7hbk._.js +0 -4
- package/.next/server/chunks/ssr/[root-of-the-server]__202p40z._.js +0 -3
- package/.next/server/chunks/ssr/_001_ms1._.js +0 -28
- package/.next/server/chunks/ssr/_01n3o0m._.js +0 -3
- package/.next/server/chunks/ssr/_0fz4mf_._.js +0 -3
- package/.next/server/chunks/ssr/_0gb4gmf._.js +0 -3
- package/.next/server/chunks/ssr/_0h42fze._.js +0 -3
- package/.next/server/chunks/ssr/_0h7zdni._.js +0 -3
- package/.next/server/chunks/ssr/_0ilna52._.js +0 -42
- package/.next/server/chunks/ssr/_0q0zan6._.js +0 -70
- package/.next/server/chunks/ssr/_0zcjrbg._.js +0 -3
- package/.next/server/chunks/ssr/_0zhoc3r._.js +0 -3
- package/.next/server/chunks/ssr/_13fke0x._.js +0 -3
- package/.next/server/chunks/ssr/_18cymkq._.js +0 -3
- package/.next/server/chunks/ssr/_1b2mjwr._.js +0 -3
- package/.next/server/chunks/ssr/_1f-dg-6._.js +0 -3
- package/.next/server/chunks/ssr/_1h0rw11._.js +0 -3
- package/.next/server/chunks/ssr/_1jhd09t._.js +0 -3
- package/.next/server/chunks/ssr/_1nnsk7z._.js +0 -3
- package/.next/server/chunks/ssr/_1ov37if._.js +0 -3
- package/.next/server/chunks/ssr/_1sji22n._.js +0 -3
- package/.next/server/chunks/ssr/_1vi_uo7._.js +0 -3
- package/.next/server/chunks/ssr/_1vyd1ji._.js +0 -3
- package/.next/server/chunks/ssr/_1xx4zlb._.js +0 -3
- package/.next/server/chunks/ssr/components_ui_1y1l7yj._.js +0 -3
- package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +0 -3
- package/.next/server/chunks/ssr/lib_realtime_12wg_dd._.js +0 -27
- package/.next/server/chunks/ssr/lib_realtime_15h_v1v._.js +0 -27
- package/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0npjf8-._.js +0 -3
- package/.next/static/chunks/00rd4-v8r7axp.js +0 -1
- package/.next/static/chunks/08qe4as2q428z.js +0 -3
- package/.next/static/chunks/0gnqdjn2lvdad.js +0 -2
- package/.next/static/chunks/0hwap1-p0m5sa.js +0 -1
- package/.next/static/chunks/0r5qcctujhxli.js +0 -1
- package/.next/static/chunks/0w52rrrmuj9r9.js +0 -1
- package/.next/static/chunks/0wcprt5ot5she.js +0 -1
- package/.next/static/chunks/10teww7wt-f6h.js +0 -1
- package/.next/static/chunks/17p6tk6k88dq6.js +0 -1
- package/.next/static/chunks/18a96rkhi4_yd.js +0 -1
- package/.next/static/chunks/1dkjg87pxk0n9.js +0 -28
- package/.next/static/chunks/1jd09sxcjddu1.js +0 -1
- package/.next/static/chunks/1w73ece_6bheu.js +0 -18
- package/.next/static/chunks/214ya3n5gxeih.js +0 -1
- package/.next/static/chunks/22_zkas21rpna.js +0 -1
- package/.next/static/chunks/2fh4-3z_q8b_w.js +0 -1
- package/.next/static/chunks/2h0sd2vumhb15.js +0 -1
- package/.next/static/chunks/2t4vhi3m8ahya.js +0 -1
- package/.next/static/chunks/2tigdtz3dpcy8.js +0 -1
- package/.next/static/chunks/2u7xt6mxg44lf.js +0 -1
- package/.next/static/chunks/3786h5vayt80-.js +0 -28
- package/.next/static/chunks/39j0fvpybzabl.js +0 -40
- package/.next/static/chunks/3cq5g494yuke8.css +0 -1
- package/.next/static/chunks/3hibbjnt85-k6.js +0 -1
- package/.next/static/chunks/3woj-xd7ki5_i.js +0 -1
- package/.next/static/chunks/3xk7yqgomx7yq.js +0 -1
- package/.next/static/chunks/3zmcsnuo25zvq.js +0 -1
- package/database/migrations/20260911030320_opposite_jack_power/migration.sql +0 -1
- package/docs/images/task-thread.png +0 -0
- package/features/bot/components/task-badge.tsx +0 -42
- package/features/bot/components/task-reply.tsx +0 -328
- package/features/bot/components/task-setting.tsx +0 -524
- package/features/bot/task.query.ts +0 -976
- package/features/bot/task.store.ts +0 -489
- package/lib/realtime/client-secret.ts +0 -86
- package/lib/realtime/open-session.ts +0 -31
- package/lib/realtime/openai.ts +0 -35
- package/lib/realtime/realtime.audio.ts +0 -196
- package/lib/realtime/realtime.driver.ts +0 -489
- package/lib/realtime/realtime.protocol.ts +0 -393
- package/lib/realtime/realtime.schema.ts +0 -37
- package/lib/realtime/realtime.session.ts +0 -106
- package/lib/realtime/realtime.transport.ts +0 -384
- package/lib/realtime/xai.ts +0 -39
- /package/.next/server/app/api/bot/{task → thread}/[id]/route/build-manifest.json +0 -0
- /package/.next/server/app/api/bot/{task → thread}/[id]/route/server-reference-manifest.json +0 -0
- /package/.next/server/app/api/bot/{task → thread}/[id]/route.js.map +0 -0
- /package/.next/server/app/api/bot/{task → thread}/route/build-manifest.json +0 -0
- /package/.next/server/app/api/bot/{task → thread}/route/server-reference-manifest.json +0 -0
- /package/.next/server/app/api/bot/{task → thread}/route.js.map +0 -0
- /package/.next/static/{kaTO0UHqF1bMnWgCDik2f → 0qzGoKn2wTERVZtblX1tp}/_buildManifest.js +0 -0
- /package/.next/static/{kaTO0UHqF1bMnWgCDik2f → 0qzGoKn2wTERVZtblX1tp}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{kaTO0UHqF1bMnWgCDik2f → 0qzGoKn2wTERVZtblX1tp}/_ssgManifest.js +0 -0
|
@@ -1,28 +1,16 @@
|
|
|
1
|
+
import { setTimeout as wait } from "node:timers/promises";
|
|
1
2
|
import type { ModelMessage } from "ai";
|
|
2
|
-
import {
|
|
3
|
-
appEventStream,
|
|
4
|
-
appEvents,
|
|
5
|
-
presence,
|
|
6
|
-
} from "@/app/api/events/app-event.server";
|
|
3
|
+
import { appEvents, presence } from "@/app/api/events/app-event.server";
|
|
7
4
|
import { BOT_RUN, PATHS, WORKSPACE_KEEP } from "@/config";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
modelFailureOf,
|
|
12
|
-
} from "@/features/ai/model";
|
|
13
|
-
import {
|
|
14
|
-
buildTaskOpening,
|
|
15
|
-
OPENING_TURNS,
|
|
16
|
-
} from "@/features/ai/prompts/bot.prompt";
|
|
17
|
-
import { TOOL_NAMES } from "@/features/ai/tools/tool-name";
|
|
18
|
-
import {
|
|
19
|
-
isAnyCallLive,
|
|
20
|
-
listCallTurns,
|
|
21
|
-
} from "@/features/thursday/thursday.query";
|
|
5
|
+
import { isProviderRefusal, modelErrorToString } from "@/features/ai/model";
|
|
6
|
+
import { buildThreadOpening } from "@/features/ai/prompts/bot.prompt";
|
|
7
|
+
import { isAnyCallLive } from "@/features/thursday/thursday.query";
|
|
22
8
|
import { pathsIn } from "@/features/workspace/file-kind";
|
|
23
9
|
import {
|
|
10
|
+
botBrowserSession,
|
|
24
11
|
closeHiddenBrowser,
|
|
25
12
|
closeJobShell,
|
|
13
|
+
filesOnDisk,
|
|
26
14
|
jobScratch,
|
|
27
15
|
listScratchFolders,
|
|
28
16
|
pruneJobFiles,
|
|
@@ -31,652 +19,395 @@ import {
|
|
|
31
19
|
} from "@/features/workspace/workspace";
|
|
32
20
|
import { desktopNotify } from "@/lib/desktop-notify";
|
|
33
21
|
import { logger } from "@/lib/logger";
|
|
34
|
-
import { publicError } from "@/lib/public-error";
|
|
22
|
+
import { isPublicError, publicError } from "@/lib/public-error";
|
|
35
23
|
import { createKeyedLock } from "@/lib/queue";
|
|
36
|
-
import {
|
|
24
|
+
import { PromiseChain } from "@/lib/utils";
|
|
25
|
+
import { findJobBot } from "./bot.query";
|
|
26
|
+
import { resumeTranscript, runBot, type ThreadEvent } from "./bot.run";
|
|
37
27
|
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
type
|
|
41
|
-
type
|
|
42
|
-
tagSpeaker,
|
|
28
|
+
isAppStop,
|
|
29
|
+
THREAD_CONTINUE,
|
|
30
|
+
type ThreadSpeaker,
|
|
31
|
+
type ThreadStatus,
|
|
43
32
|
} from "./bot.schema";
|
|
44
33
|
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
34
|
+
appendRoomMessage,
|
|
35
|
+
cancelRoom,
|
|
36
|
+
claimRoomWork,
|
|
37
|
+
consumeRoomInbox,
|
|
38
|
+
finishRoomWork,
|
|
39
|
+
listParticipantTranscript,
|
|
40
|
+
listRoomReceipts,
|
|
41
|
+
listRoomWork,
|
|
42
|
+
lowerRoomContextBudget,
|
|
43
|
+
noteRoomBreak,
|
|
44
|
+
pauseRoom,
|
|
45
|
+
type RoomWork,
|
|
46
|
+
resumeRoom,
|
|
47
|
+
roomContextBudget,
|
|
48
|
+
sendRoomMessage,
|
|
49
|
+
settleRoom,
|
|
50
|
+
tellRoom,
|
|
51
|
+
} from "./room.query";
|
|
52
|
+
import { ROOM_THURSDAY } from "./room.schema";
|
|
53
|
+
import {
|
|
54
|
+
addThreadUsage,
|
|
48
55
|
deleteMessages,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
listTaskFolders,
|
|
57
|
-
listThread,
|
|
58
|
-
stopNote,
|
|
59
|
-
type TaskMessageInput,
|
|
60
|
-
updateTask,
|
|
56
|
+
deleteThread,
|
|
57
|
+
findThread,
|
|
58
|
+
insertThread,
|
|
59
|
+
listAutoStoppedThreads,
|
|
60
|
+
listRunningThreadIds,
|
|
61
|
+
listThreadFolders,
|
|
62
|
+
updateThread,
|
|
61
63
|
upsertMessage,
|
|
62
|
-
} from "./
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Runs a job so it outlives the request, writing every event to the task's
|
|
66
|
-
* thread (task_message). A run always starts from the thread. `answerTask` is
|
|
67
|
-
* the one way a person adds to it, serialized per job by `taskLock`, and
|
|
68
|
-
* `resumeTask` the one way the app picks a job back up by itself.
|
|
69
|
-
*/
|
|
70
|
-
|
|
71
|
-
type Run = {
|
|
72
|
-
stop: AbortController;
|
|
73
|
-
/** Resolves when the run ends, however it ends. Interrupters wait on it. */
|
|
74
|
-
done: Promise<void>;
|
|
75
|
-
/**
|
|
76
|
-
* Queues words for a run still going (bot.run `notes`). False once the loop
|
|
77
|
-
* has ended; then they go on the thread the ordinary way (answerTask).
|
|
78
|
-
*/
|
|
79
|
-
note: (text: string) => boolean;
|
|
80
|
-
};
|
|
64
|
+
} from "./thread.query";
|
|
81
65
|
|
|
66
|
+
type Run = { threadId: string; stop: AbortController; done: Promise<void> };
|
|
82
67
|
type Pinned = {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
__botPausing?: { current: Promise<void> };
|
|
68
|
+
__roomRuns?: Map<string, Run>;
|
|
69
|
+
__roomThreadLock?: ReturnType<typeof createKeyedLock>;
|
|
70
|
+
__roomPausing?: { current: Promise<void> };
|
|
87
71
|
};
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* One lane per job. Stop, append, relaunch must not interleave: two answers a
|
|
95
|
-
* moment apart would abort the same run and claim the same `lastSeq + 1`.
|
|
96
|
-
*/
|
|
97
|
-
const taskLock = ((globalThis as Pinned).__botTaskLock ??= createKeyedLock());
|
|
98
|
-
|
|
99
|
-
/** When each parked job picks itself back up (scheduleResume); one timer per job. */
|
|
100
|
-
const resumes: Map<string, ReturnType<typeof setTimeout>> = ((
|
|
101
|
-
globalThis as Pinned
|
|
102
|
-
).__botResumes ??= new Map());
|
|
103
|
-
|
|
104
|
-
/** The pause under way, if any; a returning browser waits for it (resumeStoppedTasks). */
|
|
105
|
-
const pausing = ((globalThis as Pinned).__botPausing ??= {
|
|
72
|
+
const running = ((globalThis as Pinned).__roomRuns ??= new Map<string, Run>());
|
|
73
|
+
const threadLock = ((globalThis as Pinned).__roomThreadLock ??=
|
|
74
|
+
createKeyedLock());
|
|
75
|
+
const pausing = ((globalThis as Pinned).__roomPausing ??= {
|
|
106
76
|
current: Promise.resolve(),
|
|
107
77
|
});
|
|
108
78
|
|
|
109
|
-
|
|
110
|
-
export async function startTask(input: {
|
|
79
|
+
export async function startThread(input: {
|
|
111
80
|
bot: string;
|
|
112
81
|
request: string;
|
|
113
82
|
label: string;
|
|
114
83
|
callId?: string | null;
|
|
115
|
-
|
|
116
|
-
from: TaskSpeaker;
|
|
84
|
+
from: ThreadSpeaker;
|
|
117
85
|
}) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const opening = buildTaskOpening({
|
|
86
|
+
const found = await findJobBot(input.bot);
|
|
87
|
+
if (!found || found.disabled) publicError("Choose an enabled bot.");
|
|
88
|
+
const { from, ...row } = { ...input, bot: found.name };
|
|
89
|
+
const opening = buildThreadOpening({
|
|
123
90
|
bot: row.bot,
|
|
124
91
|
request: row.request,
|
|
125
|
-
conversation,
|
|
126
92
|
from,
|
|
127
93
|
});
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
messages: [{ role: "user", content: opening }],
|
|
132
|
-
});
|
|
133
|
-
return task.id;
|
|
94
|
+
const thread = await insertThread({ ...row, opening });
|
|
95
|
+
await pump(thread.id);
|
|
96
|
+
return thread.id;
|
|
134
97
|
}
|
|
135
98
|
|
|
136
|
-
/**
|
|
137
|
-
|
|
138
|
-
* a job stopped on `ask_thursday`, a note to a running one (queued and read
|
|
139
|
-
* before its next step; the step in flight finishes), or a follow-up to a
|
|
140
|
-
* finished one. Only when the run has already ended does the running case
|
|
141
|
-
* fall through to stop, append, relaunch.
|
|
142
|
-
*
|
|
143
|
-
* @param from Who said it. The words go on the thread tagged with it (bot.schema
|
|
144
|
-
* tagSpeaker): Thursday passing something on from a call and the user typing on
|
|
145
|
-
* screen reach the bot down this one pipe, and a go-ahead means something
|
|
146
|
-
* different from each. Unset for words that already carry their tags — the notes
|
|
147
|
-
* a finished run left unread (drive).
|
|
148
|
-
*/
|
|
149
|
-
export async function answerTask(
|
|
99
|
+
/** A recipient selects a desk; replying to a question also names the exact exchange. */
|
|
100
|
+
export async function answerThread(
|
|
150
101
|
id: string,
|
|
151
102
|
answer: string,
|
|
152
|
-
from
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
103
|
+
from: ThreadSpeaker = "user",
|
|
104
|
+
recipient?: string,
|
|
105
|
+
replyTo?: string,
|
|
106
|
+
): Promise<Awaited<ReturnType<typeof tellRoom>> | null> {
|
|
107
|
+
let told: Awaited<ReturnType<typeof tellRoom>> | null = null;
|
|
108
|
+
await threadLock(id, async () => {
|
|
109
|
+
const thread = await findThread(id);
|
|
110
|
+
if (!thread) publicError("No such thread.");
|
|
111
|
+
if (recipient) {
|
|
112
|
+
const participants = await listRoomWork(id);
|
|
113
|
+
const found = participants.find(
|
|
114
|
+
(row) => row.bot.toLowerCase() === recipient!.trim().toLowerCase(),
|
|
115
|
+
);
|
|
116
|
+
if (!found || found.bot === ROOM_THURSDAY)
|
|
117
|
+
publicError("Choose a participant in this thread.");
|
|
118
|
+
recipient = found.bot;
|
|
163
119
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
await
|
|
120
|
+
// Continue picks up a stop the app made; to a bot's question it is an answer like any other
|
|
121
|
+
if (answer.trim() === THREAD_CONTINUE && isAppStop(thread.pending)) {
|
|
122
|
+
await resumeRoom(id);
|
|
123
|
+
const all = await listRoomWork(id);
|
|
124
|
+
if (!all.some((row) => row.state === "queued"))
|
|
125
|
+
told = await tellRoom(
|
|
126
|
+
id,
|
|
127
|
+
"Continue from the saved conversation.",
|
|
128
|
+
from === "user" ? "The user" : ROOM_THURSDAY,
|
|
129
|
+
recipient,
|
|
130
|
+
undefined,
|
|
131
|
+
false,
|
|
132
|
+
);
|
|
133
|
+
} else {
|
|
134
|
+
told = await tellRoom(
|
|
135
|
+
id,
|
|
136
|
+
answer,
|
|
137
|
+
from === "user" ? "The user" : ROOM_THURSDAY,
|
|
138
|
+
recipient,
|
|
139
|
+
replyTo,
|
|
140
|
+
);
|
|
167
141
|
}
|
|
168
|
-
// A person picked it up; whatever the app meant to do by itself is moot
|
|
169
|
-
forgetResume(id);
|
|
170
|
-
|
|
171
|
-
const thread = await listThread(id);
|
|
172
|
-
const { reply, as } = joinThread(task, said, thread);
|
|
173
|
-
logger.debug(`task ${id}: ${as}`);
|
|
174
|
-
|
|
175
|
-
await upsertMessage(id, (await lastSeq(id)) + 1, {
|
|
176
|
-
bot: null,
|
|
177
|
-
parent: null,
|
|
178
|
-
...reply,
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
await updateTask(id, {
|
|
182
|
-
status: "running",
|
|
183
|
-
outcome: null,
|
|
184
|
-
pending: null,
|
|
185
|
-
seen: false,
|
|
186
|
-
endedAt: null,
|
|
187
|
-
});
|
|
188
|
-
// Resumed on the rows as they were stored (bot.run resumeThread)
|
|
189
|
-
launch(id, {
|
|
190
|
-
bot: task.bot,
|
|
191
|
-
messages: resumeThread([...thread, reply], await listBorrowedTails(id)),
|
|
192
|
-
});
|
|
193
142
|
});
|
|
143
|
+
await pump(id);
|
|
144
|
+
return told;
|
|
194
145
|
}
|
|
195
146
|
|
|
196
|
-
/**
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
message.content.some(
|
|
218
|
-
(part) => part.type === "tool-call" && part.toolCallId === stoppedOn,
|
|
219
|
-
),
|
|
220
|
-
);
|
|
221
|
-
const asks =
|
|
222
|
-
step?.role === "assistant" && typeof step.content !== "string"
|
|
223
|
-
? step.content.flatMap((part) =>
|
|
224
|
-
part.type === "tool-call" && part.toolName === TOOL_NAMES.ask_thursday
|
|
225
|
-
? [part.toolCallId]
|
|
226
|
-
: [],
|
|
227
|
-
)
|
|
228
|
-
: [stoppedOn];
|
|
229
|
-
return asks.filter((toolCallId) => !answered.has(toolCallId));
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* How a person's words enter the thread. A job stopped on `ask_thursday` gets
|
|
234
|
-
* them as that call's tool result (a call with no result would break the
|
|
235
|
-
* thread); otherwise they are a fresh user turn. A job asking whether to
|
|
236
|
-
* continue has no call to answer, so it is a turn as well.
|
|
237
|
-
*/
|
|
238
|
-
function joinThread(
|
|
239
|
-
task: { status: TaskStatus; pending: TaskPending | null },
|
|
240
|
-
answer: string,
|
|
241
|
-
thread: ModelMessage[],
|
|
242
|
-
): { reply: ModelMessage; as: "answer" | "interruption" | "follow-up" } {
|
|
243
|
-
const pending = task.status === "waiting" ? task.pending : null;
|
|
244
|
-
const asks = pending?.toolCallId ? openAsks(thread, pending.toolCallId) : [];
|
|
245
|
-
if (asks.length) {
|
|
246
|
-
return {
|
|
247
|
-
as: "answer",
|
|
248
|
-
reply: {
|
|
249
|
-
role: "tool",
|
|
250
|
-
content: asks.map((toolCallId) => ({
|
|
251
|
-
type: "tool-result" as const,
|
|
252
|
-
toolCallId,
|
|
253
|
-
toolName: TOOL_NAMES.ask_thursday,
|
|
254
|
-
output: { type: "text" as const, value: answer },
|
|
255
|
-
})),
|
|
256
|
-
},
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
return {
|
|
260
|
-
as:
|
|
261
|
-
task.status === "running"
|
|
262
|
-
? "interruption"
|
|
263
|
-
: task.status === "waiting"
|
|
264
|
-
? "answer"
|
|
265
|
-
: "follow-up",
|
|
266
|
-
reply: { role: "user", content: answer },
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export async function cancelTask(id: string) {
|
|
271
|
-
return taskLock(id, async () => {
|
|
272
|
-
const task = await findTask(id);
|
|
273
|
-
if (!task) publicError("No such job.");
|
|
274
|
-
// A job that already ended has an answer in `outcome`; cancelling would
|
|
275
|
-
// write "Cancelled." over the thing it was run for.
|
|
276
|
-
if (task.endedAt) publicError("That job has already ended.");
|
|
277
|
-
forgetResume(id);
|
|
278
|
-
running.get(id)?.stop.abort();
|
|
279
|
-
await updateTask(id, {
|
|
280
|
-
status: "failed",
|
|
281
|
-
outcome: "Cancelled.",
|
|
282
|
-
pending: null,
|
|
283
|
-
// Nobody needs to be told what they just did themselves
|
|
284
|
-
seen: true,
|
|
285
|
-
endedAt: new Date(),
|
|
286
|
-
});
|
|
287
|
-
// Cancel is a real end, so the browser session closes here; the abort in
|
|
288
|
-
// answerTask is followed by a relaunch and must not close it.
|
|
289
|
-
void closeJobShell(id);
|
|
147
|
+
/** Claiming is short and serialized; model execution never holds the room lock. */
|
|
148
|
+
async function pump(id: string) {
|
|
149
|
+
await threadLock(id, async () => {
|
|
150
|
+
if (!presence.watching) {
|
|
151
|
+
const thread = await findThread(id);
|
|
152
|
+
if (
|
|
153
|
+
thread?.status === "running" &&
|
|
154
|
+
![...running.values()].some((run) => run.threadId === id)
|
|
155
|
+
)
|
|
156
|
+
await pauseRoom(
|
|
157
|
+
id,
|
|
158
|
+
"The app is not open. Work resumes when it returns.",
|
|
159
|
+
true,
|
|
160
|
+
);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
164
|
+
let work: RoomWork | null;
|
|
165
|
+
while ((work = await claimRoomWork(id))) launch(work);
|
|
166
|
+
await settleRoom(id);
|
|
167
|
+
}
|
|
290
168
|
});
|
|
291
169
|
}
|
|
292
170
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
running.get(id)?.stop.abort();
|
|
299
|
-
running.delete(id);
|
|
300
|
-
void closeJobShell(id);
|
|
301
|
-
const gone = await deleteTask(id);
|
|
302
|
-
// The working folder's lifetime is the row's: with the job gone, what it was
|
|
303
|
-
// working with is nobody's (workspace.ts jobScratch).
|
|
304
|
-
if (gone && task) await removeJobScratch(id, task.label);
|
|
305
|
-
return gone;
|
|
171
|
+
function launch(work: RoomWork) {
|
|
172
|
+
const stop = new AbortController();
|
|
173
|
+
let finish!: () => void;
|
|
174
|
+
const done = new Promise<void>((resolve) => {
|
|
175
|
+
finish = resolve;
|
|
306
176
|
});
|
|
177
|
+
running.set(work.id, { threadId: work.threadId, stop, done });
|
|
178
|
+
void drive(work, stop.signal)
|
|
179
|
+
.catch((cause) =>
|
|
180
|
+
logger.error(`thread ${work.threadId}: participant`, cause),
|
|
181
|
+
)
|
|
182
|
+
.finally(() => {
|
|
183
|
+
running.delete(work.id);
|
|
184
|
+
finish();
|
|
185
|
+
void pump(work.threadId).catch((cause) =>
|
|
186
|
+
logger.error(`thread ${work.threadId}: scheduling`, cause),
|
|
187
|
+
);
|
|
188
|
+
});
|
|
307
189
|
}
|
|
308
190
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Clears what jobs left behind by age (config WORKSPACE_KEEP), at boot and on a
|
|
322
|
-
* timer (instrumentation): a job's folder once the job ended that long ago — a job
|
|
323
|
-
* running or waiting keeps its own however old — a scratch folder no job owns once
|
|
324
|
-
* it has not changed that long, and spilled output and browser snapshots that old
|
|
325
|
-
* (pruneJobFiles). A job's folder goes under the job's lock, so a word that picks
|
|
326
|
-
* the job back up in that moment never finds its material gone. Returns the
|
|
327
|
-
* folders that went.
|
|
328
|
-
*/
|
|
329
|
-
export async function sweepJobFiles(): Promise<string[]> {
|
|
330
|
-
const cutoff = Date.now() - WORKSPACE_KEEP.forMs;
|
|
331
|
-
const stale = (task: {
|
|
332
|
-
status: TaskStatus;
|
|
333
|
-
endedAt: Date | null;
|
|
334
|
-
updatedAt: Date;
|
|
335
|
-
}) =>
|
|
336
|
-
(task.status === "done" || task.status === "failed") &&
|
|
337
|
-
(task.endedAt ?? task.updatedAt).getTime() < cutoff;
|
|
338
|
-
|
|
339
|
-
// Folders on disk; each one a job owns is taken out as its job is read
|
|
340
|
-
const unowned = new Set(await listScratchFolders());
|
|
341
|
-
const removed: string[] = [];
|
|
342
|
-
for (const task of await listTaskFolders()) {
|
|
343
|
-
const folder = jobScratch(task.id, task.label);
|
|
344
|
-
if (!unowned.delete(folder) || !stale(task)) continue;
|
|
345
|
-
await taskLock(task.id, async () => {
|
|
346
|
-
const now = await findTask(task.id);
|
|
347
|
-
if (!now || !stale(now)) return;
|
|
348
|
-
await removeJobScratch(task.id, task.label);
|
|
349
|
-
removed.push(folder);
|
|
350
|
-
});
|
|
191
|
+
async function drive(work: RoomWork, signal: AbortSignal) {
|
|
192
|
+
let turn = await attempt(work, signal);
|
|
193
|
+
// A break gets one more try from the stored transcript; a refusal, the content
|
|
194
|
+
// filter and the step limit wait for a person, and so does a second break
|
|
195
|
+
if (turn?.failure?.retry && !signal.aborted) {
|
|
196
|
+
await noteRoomBreak(work, turn.failure.message);
|
|
197
|
+
// Only an abort rejects the wait, and the check below reads it
|
|
198
|
+
await wait(BOT_RUN.retryMs, undefined, { signal }).catch(() => {});
|
|
199
|
+
if (!signal.aborted) turn = await attempt(work, signal);
|
|
351
200
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
201
|
+
if (!turn || signal.aborted) return;
|
|
202
|
+
const failure = turn.failure?.message;
|
|
203
|
+
const final = turn.ending;
|
|
204
|
+
if (failure || !final || final.stopped) {
|
|
205
|
+
// Release this run before the room lock drains it; cancellation holds the same lock.
|
|
206
|
+
const why =
|
|
207
|
+
failure ??
|
|
208
|
+
"The turn was interrupted. Continue from the saved conversation.";
|
|
209
|
+
void threadLock(work.threadId, async () => {
|
|
210
|
+
const current = (await listRoomWork(work.threadId)).find(
|
|
211
|
+
(row) => row.id === work.id,
|
|
212
|
+
);
|
|
213
|
+
if (
|
|
214
|
+
current?.state !== "running" ||
|
|
215
|
+
current.generation !== work.generation
|
|
216
|
+
)
|
|
217
|
+
return;
|
|
218
|
+
const drained = stopRuns(work.threadId);
|
|
219
|
+
await pauseRoom(work.threadId, why);
|
|
220
|
+
await drained;
|
|
221
|
+
}).catch((cause) =>
|
|
222
|
+
logger.error(`thread ${work.threadId}: pausing`, cause),
|
|
357
223
|
);
|
|
224
|
+
return;
|
|
358
225
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
why: "The server restarted while this was running.",
|
|
226
|
+
await finishRoomWork(work, final.text);
|
|
227
|
+
const thread = await findThread(work.threadId);
|
|
228
|
+
if (thread?.status === "done") {
|
|
229
|
+
if (!(await isAnyCallLive()))
|
|
230
|
+
desktopNotify(thread.label, thread.outcome ?? "");
|
|
231
|
+
const paths = await filesOnDisk(pathsIn(thread.outcome ?? ""), null);
|
|
232
|
+
const path =
|
|
233
|
+
paths.find((path) => path.startsWith(`${PATHS.artifacts}/`)) ?? paths[0];
|
|
234
|
+
if (path)
|
|
235
|
+
appEvents.emit({
|
|
236
|
+
type: "artifact",
|
|
237
|
+
threadId: thread.id,
|
|
238
|
+
label: thread.label,
|
|
239
|
+
path,
|
|
374
240
|
});
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* The browser closed, or the server is going down (instrumentation): every run
|
|
381
|
-
* stops where it is and is parked (parkTask). All at once — a run slow to let go
|
|
382
|
-
* must not keep the others running. The thread stays, and so does the window: a
|
|
383
|
-
* resume carries on in the page it left off at, including a sign-in the user is
|
|
384
|
-
* mid-way through, which only that window can finish.
|
|
385
|
-
*/
|
|
386
|
-
export async function pauseTasks(reason: string) {
|
|
387
|
-
const work = Promise.all(
|
|
388
|
-
[...running].map(([id, run]) =>
|
|
389
|
-
taskLock(id, async () => {
|
|
390
|
-
run.stop.abort();
|
|
391
|
-
await run.done;
|
|
392
|
-
const task = await findTask(id);
|
|
393
|
-
if (!task || task.status !== "running") return;
|
|
394
|
-
await parkTask(id, { why: reason });
|
|
395
|
-
}).catch((cause) => logger.error(`task ${id}: pause`, cause)),
|
|
396
|
-
),
|
|
397
|
-
).then(() => {});
|
|
398
|
-
pausing.current = work;
|
|
399
|
-
await work;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* A browser is on the app again (instrumentation `presence.onBack`): every job
|
|
404
|
-
* the app parked picks itself back up — now, or when the wait a failed model
|
|
405
|
-
* call set runs out. A pause still under way is waited for first, so a quick
|
|
406
|
-
* reload does not miss what it parks.
|
|
407
|
-
*/
|
|
408
|
-
export async function resumeStoppedTasks() {
|
|
409
|
-
await pausing.current;
|
|
410
|
-
for (const task of await listAutoStoppedTasks()) {
|
|
411
|
-
scheduleResume(task.id, task.retryAt - Date.now());
|
|
412
241
|
}
|
|
413
242
|
}
|
|
414
243
|
|
|
415
|
-
/** One
|
|
416
|
-
function
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
244
|
+
/** One run of a participant's turn from its stored transcript: how it ended, or why it broke. Null when the thread is gone. */
|
|
245
|
+
async function attempt(work: RoomWork, signal: AbortSignal) {
|
|
246
|
+
const writer = new TranscriptWriter(work, signal);
|
|
247
|
+
let ending: { text: string; stopped: boolean } | null = null;
|
|
248
|
+
let failure: { message: string; retry: boolean } | null = null;
|
|
249
|
+
try {
|
|
250
|
+
const thread = await findThread(work.threadId);
|
|
251
|
+
if (!thread) return null;
|
|
252
|
+
const prior = await listParticipantTranscript(work.threadId, work.bot);
|
|
253
|
+
if (!prior.length)
|
|
254
|
+
await appendRoomMessage(work.threadId, {
|
|
255
|
+
bot: work.bot,
|
|
256
|
+
parent: work.id,
|
|
257
|
+
role: "user",
|
|
258
|
+
content: `Request: ${thread.request}\nCoordinator: ${thread.bot}. Continue as ${work.bot} in this thread.`,
|
|
259
|
+
hidden: true,
|
|
260
|
+
});
|
|
261
|
+
await consumeRoomInbox(work);
|
|
262
|
+
const history = await listParticipantTranscript(work.threadId, work.bot);
|
|
263
|
+
await runBot(
|
|
264
|
+
{
|
|
265
|
+
bot: work.bot,
|
|
266
|
+
messages: resumeTranscript(
|
|
267
|
+
history,
|
|
268
|
+
await listRoomReceipts(work.threadId, work.bot, history),
|
|
269
|
+
),
|
|
426
270
|
},
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
},
|
|
483
|
-
) {
|
|
484
|
-
const stops = await countStopsSinceSpoken(id);
|
|
485
|
-
const auto = stops < BOT_RUN.autoResumes;
|
|
486
|
-
const waits = BOT_RUN.retryAfterMs;
|
|
487
|
-
const inMs =
|
|
488
|
-
auto && stop.retry ? waits[Math.min(stops, waits.length - 1)] : 0;
|
|
489
|
-
|
|
490
|
-
await writeNote(id, stopNote(stop.why)).catch((cause) =>
|
|
491
|
-
logger.warn(`task ${id}: stop not written`, cause),
|
|
492
|
-
);
|
|
493
|
-
await writeEnding(id, {
|
|
494
|
-
status: "waiting",
|
|
495
|
-
outcome: auto
|
|
496
|
-
? `${stop.why} ${inMs ? `It tries again in ${spell(inMs)}.` : "It picks back up by itself."}`
|
|
497
|
-
: `${stop.why} It has stopped ${stops + 1} times in a row, so it waits for you. Everything it had done is still here.`,
|
|
498
|
-
pending: {
|
|
499
|
-
toolCallId: null,
|
|
500
|
-
options: [TASK_CONTINUE],
|
|
501
|
-
...(auto ? { auto: true, retryAt: Date.now() + inMs } : {}),
|
|
502
|
-
},
|
|
503
|
-
...(stop.budget ? { contextBudget: stop.budget } : {}),
|
|
504
|
-
seen: false,
|
|
505
|
-
endedAt: null,
|
|
506
|
-
});
|
|
507
|
-
if (auto) scheduleResume(id, inMs);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const spell = (ms: number) =>
|
|
511
|
-
ms < 60_000
|
|
512
|
-
? `${Math.round(ms / 1000)} seconds`
|
|
513
|
-
: `${Math.round(ms / 60_000)} minutes`;
|
|
514
|
-
|
|
515
|
-
/** A line from the app in a thread no run is writing (ThreadWriter.note is the one for a run that is). */
|
|
516
|
-
async function writeNote(id: string, text: string) {
|
|
517
|
-
await upsertMessage(id, (await lastSeq(id)) + 1, {
|
|
518
|
-
bot: null,
|
|
519
|
-
parent: null,
|
|
520
|
-
role: "user",
|
|
521
|
-
content: text,
|
|
522
|
-
note: true,
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
/**
|
|
527
|
-
* The write that tells anyone a run ended at all. Tried again: everything else
|
|
528
|
-
* in the run has happened, and a miss leaves the row `running` until the next
|
|
529
|
-
* boot. Writes are serialised, so what is left is a database that briefly
|
|
530
|
-
* cannot be written to (database/db.ts).
|
|
531
|
-
*/
|
|
532
|
-
async function writeEnding(
|
|
533
|
-
id: string,
|
|
534
|
-
patch: Parameters<typeof updateTask>[1],
|
|
535
|
-
) {
|
|
536
|
-
for (const wait of [1_000, 3_000, null]) {
|
|
537
|
-
try {
|
|
538
|
-
await updateTask(id, patch);
|
|
539
|
-
return;
|
|
540
|
-
} catch (cause) {
|
|
541
|
-
if (wait === null) throw cause;
|
|
542
|
-
logger.warn(`task ${id}: ending not written, trying again`, cause);
|
|
543
|
-
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
271
|
+
{
|
|
272
|
+
signal,
|
|
273
|
+
threadId: work.threadId,
|
|
274
|
+
parent: work.id,
|
|
275
|
+
caller: work.caller,
|
|
276
|
+
owner: thread.bot,
|
|
277
|
+
contextBudget: await roomContextBudget(work.threadId, work.bot),
|
|
278
|
+
session: botBrowserSession(work.threadId, work.bot),
|
|
279
|
+
notes: () => consumeRoomInbox(work),
|
|
280
|
+
send: async (input) => {
|
|
281
|
+
signal.throwIfAborted();
|
|
282
|
+
let to = input.to.trim();
|
|
283
|
+
if (to.toLowerCase() === ROOM_THURSDAY.toLowerCase())
|
|
284
|
+
to = ROOM_THURSDAY;
|
|
285
|
+
else {
|
|
286
|
+
const bot = await findJobBot(to);
|
|
287
|
+
if (!bot || bot.disabled)
|
|
288
|
+
publicError(`No enabled bot named "${to}".`);
|
|
289
|
+
to = bot.name;
|
|
290
|
+
}
|
|
291
|
+
const receipt = await sendRoomMessage(work, { ...input, to });
|
|
292
|
+
void pump(work.threadId).catch((cause) =>
|
|
293
|
+
logger.error("room message scheduling", cause),
|
|
294
|
+
);
|
|
295
|
+
return receipt;
|
|
296
|
+
},
|
|
297
|
+
emit: async (event) => {
|
|
298
|
+
if (signal.aborted && event.type !== "tool-result") return;
|
|
299
|
+
await writer.on(event);
|
|
300
|
+
if (event.type === "step")
|
|
301
|
+
await addThreadUsage(
|
|
302
|
+
work.threadId,
|
|
303
|
+
event.usage,
|
|
304
|
+
work.bot === thread.bot
|
|
305
|
+
? { tokens: event.usage.input, budget: event.budget }
|
|
306
|
+
: null,
|
|
307
|
+
);
|
|
308
|
+
if (event.type === "compact")
|
|
309
|
+
await addThreadUsage(work.threadId, event.usage);
|
|
310
|
+
if (event.type === "turn-end") ending = event;
|
|
311
|
+
if (event.type === "error") {
|
|
312
|
+
failure = { message: event.message, retry: event.retry };
|
|
313
|
+
if (event.budget) await lowerRoomContextBudget(work, event.budget);
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
);
|
|
318
|
+
} catch (cause) {
|
|
319
|
+
if (!signal.aborted) {
|
|
320
|
+
logger.error(`thread ${work.threadId}: ${work.bot} broke`, cause);
|
|
321
|
+
failure = {
|
|
322
|
+
message: modelErrorToString(cause),
|
|
323
|
+
// A public error is the app refusing the turn (no key, no model), not a break
|
|
324
|
+
retry: !isPublicError(cause) && !isProviderRefusal(cause),
|
|
325
|
+
};
|
|
544
326
|
}
|
|
545
327
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const stop = new AbortController();
|
|
550
|
-
let finish: () => void = () => {};
|
|
551
|
-
const done = new Promise<void>((resolve) => {
|
|
552
|
-
finish = resolve;
|
|
553
|
-
});
|
|
554
|
-
// Notes said to the job while it runs. The run drains them before each step;
|
|
555
|
-
// what is left when the loop ends is handed on by `drive`.
|
|
556
|
-
const notes: string[] = [];
|
|
557
|
-
const run: Run = {
|
|
558
|
-
stop,
|
|
559
|
-
done,
|
|
560
|
-
note: (text) => {
|
|
561
|
-
if (!running.has(id)) return false;
|
|
562
|
-
notes.push(text);
|
|
563
|
-
return true;
|
|
564
|
-
},
|
|
328
|
+
return {
|
|
329
|
+
ending: ending as { text: string; stopped: boolean } | null,
|
|
330
|
+
failure: failure as { message: string; retry: boolean } | null,
|
|
565
331
|
};
|
|
566
|
-
running.set(id, run);
|
|
567
|
-
// Held by the server, not by a request: a run also starts from a returning
|
|
568
|
-
// browser and a retry timer, where `after()` throws (no request) or waits for
|
|
569
|
-
// the event stream to close
|
|
570
|
-
void drive(id, input, stop.signal, notes)
|
|
571
|
-
.catch((cause) => {
|
|
572
|
-
logger.error(`task ${id}`, cause);
|
|
573
|
-
})
|
|
574
|
-
.finally(finish);
|
|
575
332
|
}
|
|
576
333
|
|
|
577
|
-
/**
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
>();
|
|
590
|
-
|
|
591
|
-
/** Set once the run is over: a borrowed run still unwinding must not write into a thread a resume has taken. */
|
|
592
|
-
private closed = false;
|
|
593
|
-
|
|
334
|
+
/** Streaming updates share stable row slots; tools may be observed before the stream reports them. */
|
|
335
|
+
class TranscriptWriter {
|
|
336
|
+
private lane = PromiseChain();
|
|
337
|
+
private assistant: number | null = null;
|
|
338
|
+
private tool: number | null = null;
|
|
339
|
+
private parts: {
|
|
340
|
+
assistant: Exclude<
|
|
341
|
+
Extract<ModelMessage, { role: "assistant" }>["content"],
|
|
342
|
+
string
|
|
343
|
+
>;
|
|
344
|
+
tool: Extract<ModelMessage, { role: "tool" }>["content"];
|
|
345
|
+
} = { assistant: [], tool: [] };
|
|
346
|
+
private calls = new Set<string>();
|
|
347
|
+
private results = new Set<string>();
|
|
594
348
|
constructor(
|
|
595
|
-
private
|
|
596
|
-
|
|
349
|
+
private work: RoomWork,
|
|
350
|
+
private signal: AbortSignal,
|
|
351
|
+
) {}
|
|
352
|
+
private async write(
|
|
353
|
+
seq: number | null,
|
|
354
|
+
value: ModelMessage,
|
|
355
|
+
compact = false,
|
|
597
356
|
) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
/**
|
|
606
|
-
* A line from the app rather than from anyone in the room. A user row so a
|
|
607
|
-
* resumed run reads it as something it was told, and `note` so the screen
|
|
608
|
-
* draws it as a note and `rosterOf` does not take it for a person speaking.
|
|
609
|
-
*/
|
|
610
|
-
async note(text: string) {
|
|
611
|
-
await upsertMessage(this.taskId, ++this.seq, {
|
|
612
|
-
bot: null,
|
|
613
|
-
parent: null,
|
|
614
|
-
role: "user",
|
|
615
|
-
content: text,
|
|
616
|
-
note: true,
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
private step(event: TaskEvent) {
|
|
621
|
-
const key = `${event.bot} ${event.parent ?? ""}`;
|
|
622
|
-
let step = this.steps.get(key);
|
|
623
|
-
if (!step) {
|
|
624
|
-
step = {
|
|
625
|
-
assistant: null,
|
|
626
|
-
tool: null,
|
|
627
|
-
parts: { assistant: [], tool: [] },
|
|
628
|
-
};
|
|
629
|
-
this.steps.set(key, step);
|
|
630
|
-
}
|
|
631
|
-
return step;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
private write(
|
|
635
|
-
seq: number,
|
|
636
|
-
event: TaskEvent,
|
|
637
|
-
message: ModelMessage,
|
|
638
|
-
extra: { compact?: boolean; note?: boolean } = {},
|
|
639
|
-
) {
|
|
640
|
-
const input: TaskMessageInput = {
|
|
641
|
-
bot: event.bot,
|
|
642
|
-
parent: event.parent,
|
|
643
|
-
role: message.role,
|
|
644
|
-
content: message.content,
|
|
645
|
-
compact: extra.compact ?? false,
|
|
646
|
-
note: extra.note ?? false,
|
|
357
|
+
const row = {
|
|
358
|
+
bot: this.work.bot,
|
|
359
|
+
parent: this.work.id,
|
|
360
|
+
role: value.role,
|
|
361
|
+
content: value.content,
|
|
362
|
+
compact,
|
|
363
|
+
note: compact,
|
|
647
364
|
};
|
|
648
|
-
return
|
|
365
|
+
if (seq === null) return appendRoomMessage(this.work.threadId, row);
|
|
366
|
+
await upsertMessage(this.work.threadId, seq, row);
|
|
367
|
+
return seq;
|
|
649
368
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
step.assistant ??= ++this.seq;
|
|
657
|
-
step.parts.assistant.push({ type: "text", text: event.text });
|
|
658
|
-
return this.write(step.assistant, event, {
|
|
369
|
+
on(event: ThreadEvent) {
|
|
370
|
+
return this.lane(async () => {
|
|
371
|
+
if (this.signal.aborted && event.type !== "tool-result") return;
|
|
372
|
+
if (event.type === "text") {
|
|
373
|
+
this.parts.assistant.push({ type: "text", text: event.text });
|
|
374
|
+
this.assistant = await this.write(this.assistant, {
|
|
659
375
|
role: "assistant",
|
|
660
|
-
content:
|
|
376
|
+
content: this.parts.assistant,
|
|
661
377
|
});
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
378
|
+
} else if (event.type === "tool") {
|
|
379
|
+
if (this.calls.has(event.id)) {
|
|
380
|
+
const part = this.parts.assistant.find(
|
|
381
|
+
(part) => part.type === "tool-call" && part.toolCallId === event.id,
|
|
382
|
+
);
|
|
383
|
+
if (part && part.type === "tool-call" && event.providerOptions) {
|
|
384
|
+
part.providerOptions = event.providerOptions;
|
|
385
|
+
this.assistant = await this.write(this.assistant, {
|
|
386
|
+
role: "assistant",
|
|
387
|
+
content: this.parts.assistant,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
this.calls.add(event.id);
|
|
393
|
+
this.parts.assistant.push({
|
|
666
394
|
type: "tool-call",
|
|
667
395
|
toolCallId: event.id,
|
|
668
396
|
toolName: event.name,
|
|
669
397
|
input: event.input,
|
|
398
|
+
...(event.providerExecuted ? { providerExecuted: true } : {}),
|
|
399
|
+
...(event.providerOptions
|
|
400
|
+
? { providerOptions: event.providerOptions }
|
|
401
|
+
: {}),
|
|
670
402
|
});
|
|
671
|
-
|
|
403
|
+
this.assistant = await this.write(this.assistant, {
|
|
672
404
|
role: "assistant",
|
|
673
|
-
content:
|
|
405
|
+
content: this.parts.assistant,
|
|
674
406
|
});
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
step.parts.tool.push({
|
|
407
|
+
} else if (event.type === "tool-result") {
|
|
408
|
+
if (this.results.has(event.id)) return;
|
|
409
|
+
this.results.add(event.id);
|
|
410
|
+
this.parts.tool.push({
|
|
680
411
|
type: "tool-result",
|
|
681
412
|
toolCallId: event.id,
|
|
682
413
|
toolName: event.name,
|
|
@@ -686,264 +417,157 @@ class ThreadWriter {
|
|
|
686
417
|
? { type: "text", value: event.output }
|
|
687
418
|
: { type: "json", value: event.output as never },
|
|
688
419
|
});
|
|
689
|
-
|
|
420
|
+
this.tool = await this.write(this.tool, {
|
|
690
421
|
role: "tool",
|
|
691
|
-
content:
|
|
422
|
+
content: this.parts.tool,
|
|
692
423
|
});
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
// provider-executed tool (web search) answers inside the assistant
|
|
706
|
-
// message, leaving a tool row with no call in front of it.
|
|
707
|
-
const stale = slots.filter(
|
|
708
|
-
(seq): seq is number => seq !== null && !written.has(seq),
|
|
709
|
-
);
|
|
710
|
-
if (stale.length) await deleteMessages(this.taskId, stale);
|
|
711
|
-
return;
|
|
424
|
+
} else if (event.type === "step") {
|
|
425
|
+
const slots = [this.assistant, this.tool];
|
|
426
|
+
for (const [index, value] of event.messages.entries())
|
|
427
|
+
await this.write(slots[index] ?? null, value);
|
|
428
|
+
const stale = slots
|
|
429
|
+
.slice(event.messages.length)
|
|
430
|
+
.filter((seq): seq is number => seq !== null);
|
|
431
|
+
if (stale.length) await deleteMessages(this.work.threadId, stale);
|
|
432
|
+
this.assistant = this.tool = null;
|
|
433
|
+
this.parts = { assistant: [], tool: [] };
|
|
434
|
+
} else if (event.type === "compact") {
|
|
435
|
+
await this.write(null, { role: "user", content: event.text }, true);
|
|
712
436
|
}
|
|
713
|
-
|
|
714
|
-
case "compact":
|
|
715
|
-
// The row a resume restarts from (task.query listThread); earlier rows
|
|
716
|
-
// stay for the screen.
|
|
717
|
-
return this.write(
|
|
718
|
-
++this.seq,
|
|
719
|
-
event,
|
|
720
|
-
{ role: "user", content: event.text },
|
|
721
|
-
{ compact: true, note: true },
|
|
722
|
-
);
|
|
723
|
-
|
|
724
|
-
case "interjection":
|
|
725
|
-
// Between the two steps, where the model read it.
|
|
726
|
-
return this.write(++this.seq, event, {
|
|
727
|
-
role: "user",
|
|
728
|
-
content: event.text,
|
|
729
|
-
});
|
|
730
|
-
|
|
731
|
-
default:
|
|
732
|
-
return;
|
|
733
|
-
}
|
|
437
|
+
});
|
|
734
438
|
}
|
|
735
439
|
}
|
|
736
440
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
async function drive(
|
|
744
|
-
id: string,
|
|
745
|
-
input: { bot: string; messages: ModelMessage[] },
|
|
746
|
-
signal: AbortSignal,
|
|
747
|
-
notes: string[],
|
|
748
|
-
) {
|
|
749
|
-
const thread = new ThreadWriter(id, await lastSeq(id));
|
|
750
|
-
|
|
751
|
-
let ending: {
|
|
752
|
-
status: TaskStatus;
|
|
753
|
-
outcome: string;
|
|
754
|
-
pending: TaskPending | null;
|
|
755
|
-
} | null = null;
|
|
756
|
-
/**
|
|
757
|
-
* What broke, as the provider said it — the thread carries it too (below) —
|
|
758
|
-
* and whose it is to fix (model.ts modelFailureOf).
|
|
759
|
-
*/
|
|
760
|
-
let broken: {
|
|
761
|
-
message: string;
|
|
762
|
-
failure: ModelFailure;
|
|
763
|
-
budget?: number;
|
|
764
|
-
} | null = null;
|
|
441
|
+
async function stopRuns(id: string) {
|
|
442
|
+
const live = [...running.values()].filter((run) => run.threadId === id);
|
|
443
|
+
for (const run of live) run.stop.abort();
|
|
444
|
+
await Promise.all(live.map((run) => run.done));
|
|
445
|
+
}
|
|
765
446
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
if (event.type === "step") {
|
|
776
|
-
await addTaskUsage(
|
|
777
|
-
id,
|
|
778
|
-
event.usage,
|
|
779
|
-
event.parent
|
|
780
|
-
? null
|
|
781
|
-
: { tokens: event.usage.input, budget: event.budget },
|
|
782
|
-
);
|
|
783
|
-
}
|
|
784
|
-
// Compaction and answering a borrowed bot (bot.run answerBack) cost the job too.
|
|
785
|
-
if (event.type === "compact" || event.type === "answered") {
|
|
786
|
-
await addTaskUsage(id, event.usage);
|
|
787
|
-
}
|
|
788
|
-
// Only the job's own bot ends it; a borrowed bot's ending is a tool result.
|
|
789
|
-
if (event.parent) return;
|
|
790
|
-
if (event.type === "waiting") {
|
|
791
|
-
ending = {
|
|
792
|
-
status: "waiting",
|
|
793
|
-
outcome: event.question,
|
|
794
|
-
pending: { toolCallId: event.id, options: event.options },
|
|
795
|
-
};
|
|
796
|
-
} else if (event.type === "answer") {
|
|
797
|
-
// An answer ends the job; one the app stopped (bot.run `stopped`) waits
|
|
798
|
-
// with the continue option. The outcome is the answer text verbatim;
|
|
799
|
-
// the same text is the thread line (bot.query linesOf).
|
|
800
|
-
ending = event.stopped
|
|
801
|
-
? {
|
|
802
|
-
status: "waiting",
|
|
803
|
-
outcome: event.text,
|
|
804
|
-
pending: { toolCallId: null, options: [TASK_CONTINUE] },
|
|
805
|
-
}
|
|
806
|
-
: { status: "done", outcome: event.text, pending: null };
|
|
807
|
-
} else if (event.type === "error") {
|
|
808
|
-
broken = {
|
|
809
|
-
message: event.message,
|
|
810
|
-
failure: event.failure,
|
|
811
|
-
budget: event.budget,
|
|
812
|
-
};
|
|
813
|
-
ending = {
|
|
814
|
-
status: "failed",
|
|
815
|
-
outcome: broke(event.message),
|
|
816
|
-
pending: null,
|
|
817
|
-
};
|
|
818
|
-
}
|
|
819
|
-
},
|
|
820
|
-
});
|
|
821
|
-
} catch (cause) {
|
|
822
|
-
broken = {
|
|
823
|
-
message: modelErrorToString(cause),
|
|
824
|
-
failure: modelFailureOf(cause),
|
|
825
|
-
};
|
|
826
|
-
ending = {
|
|
827
|
-
status: "failed",
|
|
828
|
-
outcome: broke(broken.message),
|
|
447
|
+
export async function cancelThread(id: string) {
|
|
448
|
+
await threadLock(id, async () => {
|
|
449
|
+
const thread = await findThread(id);
|
|
450
|
+
if (!thread) publicError("No such thread.");
|
|
451
|
+
if (thread.endedAt) publicError("That thread has already ended.");
|
|
452
|
+
await cancelRoom(id);
|
|
453
|
+
await updateThread(id, {
|
|
454
|
+
status: "cancelled",
|
|
455
|
+
outcome: null,
|
|
829
456
|
pending: null,
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
457
|
+
seen: true,
|
|
458
|
+
endedAt: new Date(),
|
|
459
|
+
});
|
|
460
|
+
await stopRuns(id);
|
|
461
|
+
await closeJobShell(id);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
export async function removeThread(id: string) {
|
|
465
|
+
return threadLock(id, () => removeLockedThread(id));
|
|
466
|
+
}
|
|
467
|
+
async function removeLockedThread(id: string) {
|
|
468
|
+
const thread = await findThread(id);
|
|
469
|
+
await cancelRoom(id);
|
|
470
|
+
await stopRuns(id);
|
|
471
|
+
await closeJobShell(id);
|
|
472
|
+
const removed = await deleteThread(id);
|
|
473
|
+
if (removed && thread) await removeJobScratch(id, thread.label);
|
|
474
|
+
return removed;
|
|
475
|
+
}
|
|
848
476
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
});
|
|
859
|
-
} else {
|
|
860
|
-
// A thread that simply stops cannot be read: the room shows a tool call
|
|
861
|
-
// with no answer, and a resumed run has no idea why. The task row says it
|
|
862
|
-
// too, but the row is a status and this is the story. Never at the cost of
|
|
863
|
-
// the status write, which is what tells anyone the job ended at all.
|
|
864
|
-
if (final.status === "failed") {
|
|
865
|
-
await thread
|
|
866
|
-
.note(`The run stopped here: ${broken?.message ?? final.outcome}`)
|
|
867
|
-
.catch((cause) =>
|
|
868
|
-
logger.warn(`task ${id}: break not written`, cause),
|
|
869
|
-
);
|
|
870
|
-
}
|
|
871
|
-
await writeEnding(id, {
|
|
872
|
-
...final,
|
|
873
|
-
seen: false,
|
|
874
|
-
endedAt: final.status === "waiting" ? null : new Date(),
|
|
875
|
-
});
|
|
876
|
-
}
|
|
877
|
-
} catch (cause) {
|
|
878
|
-
// Said plainly rather than thrown into a promise nobody awaits; the row
|
|
879
|
-
// stays `running`, which the next boot reconciles (sweepTasks)
|
|
880
|
-
logger.error(`task ${id}: could not record how it ended`, cause);
|
|
881
|
-
return;
|
|
477
|
+
export async function removeFinishedThreads(): Promise<number> {
|
|
478
|
+
let removed = 0;
|
|
479
|
+
for (const thread of await listThreadFolders()) {
|
|
480
|
+
if (thread.status !== "done" && thread.status !== "cancelled") continue;
|
|
481
|
+
await threadLock(thread.id, async () => {
|
|
482
|
+
const current = await findThread(thread.id);
|
|
483
|
+
if (current?.status !== "done" && current?.status !== "cancelled") return;
|
|
484
|
+
if (await removeLockedThread(thread.id)) removed += 1;
|
|
485
|
+
});
|
|
882
486
|
}
|
|
487
|
+
return removed;
|
|
488
|
+
}
|
|
883
489
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
490
|
+
/**
|
|
491
|
+
* Clears what jobs left behind by age (config WORKSPACE_KEEP), at boot and on a
|
|
492
|
+
* timer (instrumentation): a job's folder once the job ended that long ago — a job
|
|
493
|
+
* running or waiting keeps its own however old — a scratch folder no job owns once
|
|
494
|
+
* it has not changed that long, and spilled output and browser snapshots that old
|
|
495
|
+
* (pruneJobFiles). A job's folder goes under the job's lock, so a word that picks
|
|
496
|
+
* the job back up in that moment never finds its material gone. Returns the
|
|
497
|
+
* folders that went.
|
|
498
|
+
*/
|
|
499
|
+
export async function sweepJobFiles(): Promise<string[]> {
|
|
500
|
+
const cutoff = Date.now() - WORKSPACE_KEEP.forMs;
|
|
501
|
+
const stale = (thread: {
|
|
502
|
+
status: ThreadStatus;
|
|
503
|
+
endedAt: Date | null;
|
|
504
|
+
updatedAt: Date;
|
|
505
|
+
}) =>
|
|
506
|
+
(thread.status === "done" || thread.status === "cancelled") &&
|
|
507
|
+
(thread.endedAt ?? thread.updatedAt).getTime() < cutoff;
|
|
891
508
|
|
|
892
|
-
//
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
509
|
+
// Folders on disk; each one a job owns is taken out as its job is read
|
|
510
|
+
const unowned = new Set(await listScratchFolders());
|
|
511
|
+
const removed: string[] = [];
|
|
512
|
+
for (const thread of await listThreadFolders()) {
|
|
513
|
+
const folder = jobScratch(thread.id, thread.label);
|
|
514
|
+
if (!unowned.delete(folder) || !stale(thread)) continue;
|
|
515
|
+
await threadLock(thread.id, async () => {
|
|
516
|
+
const now = await findThread(thread.id);
|
|
517
|
+
if (!now || !stale(now)) return;
|
|
518
|
+
await closeHiddenBrowser(thread.id);
|
|
519
|
+
await removeJobScratch(thread.id, thread.label);
|
|
520
|
+
removed.push(folder);
|
|
899
521
|
});
|
|
900
|
-
return;
|
|
901
522
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
if (
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
// A finished job naming a document opens it on the screen (workspace artifact-view).
|
|
909
|
-
if (final.status === "done") {
|
|
910
|
-
const path = artifactIn(final.outcome);
|
|
911
|
-
if (path) {
|
|
912
|
-
appEvents.emit({ type: "artifact", taskId: id, label: task.label, path });
|
|
913
|
-
}
|
|
523
|
+
removed.push(...(await removeUnchangedFolders([...unowned])));
|
|
524
|
+
await pruneJobFiles();
|
|
525
|
+
if (removed.length) {
|
|
526
|
+
logger.info(
|
|
527
|
+
`cleared ${removed.length} old job folder(s): ${removed.join(", ")}`,
|
|
528
|
+
);
|
|
914
529
|
}
|
|
530
|
+
return removed;
|
|
915
531
|
}
|
|
916
532
|
|
|
917
|
-
/**
|
|
918
|
-
function
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
533
|
+
/** A server restart is a manual resume boundary: external effects may already have happened. */
|
|
534
|
+
export async function sweepThreads() {
|
|
535
|
+
for (const id of await listRunningThreadIds()) {
|
|
536
|
+
await threadLock(id, async () => {
|
|
537
|
+
if ([...running.values()].some((run) => run.threadId === id)) return;
|
|
538
|
+
if ((await findThread(id))?.status !== "running") return;
|
|
539
|
+
await pauseRoom(
|
|
540
|
+
id,
|
|
541
|
+
"The server restarted. Resume from the saved conversation.",
|
|
542
|
+
);
|
|
543
|
+
});
|
|
544
|
+
}
|
|
925
545
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
546
|
+
export async function pauseThreads(reason: string, auto = false) {
|
|
547
|
+
const pause = (async () => {
|
|
548
|
+
const ids = await listRunningThreadIds();
|
|
549
|
+
await Promise.all(
|
|
550
|
+
ids.map((id) =>
|
|
551
|
+
threadLock(id, async () => {
|
|
552
|
+
if ((await findThread(id))?.status !== "running") return;
|
|
553
|
+
await stopRuns(id);
|
|
554
|
+
await pauseRoom(id, reason, auto);
|
|
555
|
+
}),
|
|
556
|
+
),
|
|
557
|
+
);
|
|
558
|
+
})();
|
|
559
|
+
pausing.current = pause;
|
|
560
|
+
await pause;
|
|
933
561
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
)
|
|
944
|
-
|
|
945
|
-
if (!nobodyWatching && (await isAnyCallLive())) return;
|
|
946
|
-
|
|
947
|
-
const title = ending.status === "waiting" ? `${label} — needs you` : label;
|
|
948
|
-
desktopNotify(title, ending.outcome);
|
|
562
|
+
export async function resumeStoppedThreads() {
|
|
563
|
+
await pausing.current;
|
|
564
|
+
if (!presence.watching) return;
|
|
565
|
+
for (const id of await listAutoStoppedThreads()) {
|
|
566
|
+
await threadLock(id, async () => {
|
|
567
|
+
const current = await findThread(id);
|
|
568
|
+
if (current?.status !== "waiting" || !current.pending?.auto) return;
|
|
569
|
+
await resumeRoom(id, false);
|
|
570
|
+
});
|
|
571
|
+
await pump(id);
|
|
572
|
+
}
|
|
949
573
|
}
|