thursday-agent 0.5.0 → 0.7.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 +5 -4
- 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 +4 -4
- 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 +15 -15
- 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 +9 -10
- 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 -5
- 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 +11 -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]__0-06t3f._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__02846fb._.js +8 -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]__06l05n_._.js +3 -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]__0btqrct._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0c9fgca._.js +5 -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]__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/_1gnjfli._.js +3 -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_0v_7drq._.js → node_modules_consola_dist_chunks_prompt_mjs_0a7-pbw._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_17k8irj._.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_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]__0-e011f._.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]__0lyi8i2._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0pd8yr7._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__16jc196._.js +1 -1
- 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]__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]__1vdmfbe._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1waa3rc._.js +85 -0
- package/.next/server/chunks/ssr/_0-2qsxo._.js +3 -0
- package/.next/server/chunks/ssr/_00rehl_._.js +3 -0
- package/.next/server/chunks/ssr/{_1lz_cq2._.js → _02oc_h5._.js} +76 -97
- package/.next/server/chunks/ssr/_05yghx6._.js +3 -0
- package/.next/server/chunks/ssr/_07nbwap._.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/{_0m2lsd1._.js → _0b6_jv-._.js} +2 -2
- package/.next/server/chunks/ssr/_0evkpto._.js +3 -0
- package/.next/server/chunks/ssr/_0geg212._.js +3 -0
- package/.next/server/chunks/ssr/{_1jdgjv9._.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/_0no7i1o._.js +3 -0
- package/.next/server/chunks/ssr/_0tzg--b._.js +3 -0
- 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/_114ylv7._.js +7 -0
- package/.next/server/chunks/ssr/_123y-ah._.js +3 -0
- package/.next/server/chunks/ssr/_12c-iwh._.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/_167hchk._.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/_1_krfph._.js +3 -0
- package/.next/server/chunks/ssr/{_0rzucul._.js → _1_x5spz._.js} +3 -3
- package/.next/server/chunks/ssr/_1a61fc9._.js +3 -0
- 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/_1rgtxhr._.js +5 -0
- package/.next/server/chunks/ssr/_1slp25y._.js +3 -0
- package/.next/server/chunks/ssr/_1w_t7h7._.js +101 -0
- package/.next/server/chunks/ssr/{_0s99bgb._.js → _1wyfaq_._.js} +9 -3
- package/.next/server/chunks/ssr/_200gt-m._.js +102 -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/features_workspace_00jz2hu._.js +8 -0
- package/.next/server/chunks/ssr/{_1-teee5._.js → lib_logger_ts_0-olsze._.js} +2 -7
- package/.next/server/chunks/ssr/{node_modules_05-n6dz._.js → node_modules_02ai7br._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1chvq5f._.js → node_modules_02deexj._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0m383r2._.js → node_modules_07za262._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_07dsgkx._.js → node_modules_0_s9get._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1a0kmjl._.js → node_modules_0n3c7h0._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0zeg3x_._.js → node_modules_0od6jwr._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1y79i83._.js → node_modules_0rdt7pk._.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_04sksn9._.js → node_modules_0w1z7kz._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1uyk91w._.js → node_modules_0wdupfz._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ifp389._.js → node_modules_1720w0a._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0u9da1h._.js → node_modules_18lsg7d._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1k6n600._.js → node_modules_1b94fxf._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_20c28js._.js → node_modules_1ecdo1s._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0fkeku3._.js → node_modules_1fa9ag7._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1j8j4pt._.js → node_modules_1fv-_si._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0-dr_o7._.js → node_modules_1hf461g._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1lz0oxc._.js → node_modules_1m71ta-._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1t77e4n._.js → node_modules_1n93n-g._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_16mkxuo._.js → node_modules_1n9lqu9._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1k7z2j6._.js → node_modules_1npnsvd._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_056qgf-._.js → node_modules_1rn2r0n._.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_0bvopy2._.js → node_modules_1vq-mfk._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0iur59s._.js → node_modules_1vzyw1t._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1k8j8n0._.js → node_modules_1w359w4._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1x0ztf4._.js → node_modules_1yajbl6._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1-y7nni._.js → node_modules_20eqhy8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0bkwd0u._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_019sbis._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0p9a9su._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0s6piq3._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@vercel_oidc_dist_1o553h-._.js → node_modules_@vercel_oidc_dist_05itbb1._.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/07vqbk_9t4em4.js +2 -0
- package/.next/static/chunks/08krja1zx7nqg.js +1 -0
- package/.next/static/chunks/0g4b348l0pve7.js +1 -0
- package/.next/static/chunks/0qcuwdk132jjt.js +1 -0
- package/.next/static/chunks/0vlgzqo9ka90v.js +1 -0
- package/.next/static/chunks/{11r2yp_yh9_qn.js → 0z69gvysmave7.js} +1 -1
- package/.next/static/chunks/0zrupxgci5gqc.js +1 -0
- package/.next/static/chunks/12fvz1w5ows75.js +1 -0
- package/.next/static/chunks/14t03ned286iu.js +1 -0
- package/.next/static/chunks/156xxbkrz5lzu.js +18 -0
- package/.next/static/chunks/1fz14reod4lft.js +1 -0
- package/.next/static/chunks/1g23dbabl-vkr.js +1 -0
- package/.next/static/chunks/1ohslk4568tm3.js +1 -0
- package/.next/static/chunks/1qy_rc3oxfxok.js +1 -0
- package/.next/static/chunks/1sbmwswc9k2-1.js +1 -0
- package/.next/static/chunks/1vqezbhjm3goq.js +1 -0
- package/.next/static/chunks/1zea7qdl-7365.js +1 -0
- package/.next/static/chunks/1zvqumkg7kmo0.css +1 -0
- package/.next/static/chunks/26hpqt3y6bogl.js +12 -0
- package/.next/static/chunks/2_j5mqm6d-z18.js +99 -0
- package/.next/static/chunks/2dt6tu7a22svs.js +1 -0
- package/.next/static/chunks/2h5ls3d86kya6.js +1 -0
- package/.next/static/chunks/2wc6y1ht6dfuy.js +1 -0
- package/.next/static/chunks/{435atsnhs7ksu.js → 2xr0zpll7thyi.js} +2 -2
- package/.next/static/chunks/31jztc-yq30m7.js +1 -0
- package/.next/static/chunks/391af0i8eljzk.js +1 -0
- package/.next/static/chunks/3bfwpww3m3x4u.js +1 -0
- package/.next/static/chunks/{38gweqdo4jpfj.js → 3izg0a3k87q1-.js} +5 -5
- package/.next/static/chunks/3p2s2cjokx1aa.js +5 -0
- package/.next/static/chunks/{408ueu4hfvcg7.js → 41s3fml5oivsa.js} +1 -1
- package/README.md +45 -193
- 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 +40 -35
- package/docs/images/keeps-going.png +0 -0
- package/docs/images/two-minds.png +0 -0
- package/docs/live-calls.md +236 -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 +152 -79
- 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 +169 -0
- package/features/ai/prompts/prompt-helper.ts +92 -32
- package/features/ai/prompts/thursday.prompt.ts +164 -190
- package/features/ai/tools/bot.tool.ts +38 -145
- package/features/ai/tools/call.tool.ts +33 -5
- 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 +11 -6
- package/features/ai/tools/studio.tool.ts +26 -21
- package/features/ai/tools/tool-name.ts +3 -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 +296 -744
- package/features/bot/bot.runner.ts +463 -864
- 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/ascii.const.ts +74 -0
- package/features/thursday/components/ascii-field.tsx +24 -44
- package/features/thursday/components/ascii-orb.tsx +525 -381
- package/features/thursday/components/call-log.tsx +46 -44
- package/features/thursday/components/face.tsx +40 -10
- package/features/thursday/components/side-captions.tsx +273 -0
- package/features/thursday/components/tab-state.tsx +14 -6
- package/features/thursday/components/thursday-mark.tsx +1 -0
- package/features/thursday/components/thursday-setting.tsx +633 -333
- package/features/thursday/components/thursday.tsx +315 -274
- package/features/thursday/components/voice-picker.tsx +255 -0
- package/features/thursday/face.store.ts +5 -0
- package/features/thursday/thursday.action.ts +96 -72
- package/features/thursday/thursday.query.ts +49 -30
- package/features/thursday/thursday.schema.ts +37 -35
- package/features/thursday/thursday.store.ts +14 -7
- package/features/thursday/tool-call.ts +3 -3
- package/features/thursday/tool-line.ts +5 -3
- package/features/thursday/use-thursday.ts +801 -457
- package/features/workspace/file-kind.ts +9 -0
- package/features/workspace/workspace.query.ts +17 -0
- package/features/workspace/workspace.ts +82 -36
- package/hooks/use-hotkey.ts +3 -2
- package/lib/live/live.schema.ts +26 -0
- package/lib/live/live.server.ts +153 -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 +38 -0
- package/scripts/bot-context.test.mts +1408 -0
- package/scripts/live-history.test.mts +115 -0
- package/scripts/live-session.test.mts +938 -0
- package/server.js +1 -1
- package/skills/browser/SKILL.md +11 -1
- 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]__026u77e._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__050zri4._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0bg9710._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0ffbg85._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0in-d5s._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0ncs9uu._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0pa9k-f._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__0qi_m0e._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__0tq0m_k._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0xep6j_._.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]__144l7qt._.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]__1fizn31._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1jg8t86._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__1lldzy9._.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]__1peqvsi._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1w8gm5h._.js +0 -10
- package/.next/server/chunks/[root-of-the-server]__1xt39nn._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__1zb8mfo._.js +0 -8
- package/.next/server/chunks/_0-73of8._.js +0 -55
- package/.next/server/chunks/_0-fjql4._.js +0 -31
- package/.next/server/chunks/_00xh_q0._.js +0 -25
- package/.next/server/chunks/_051qh8u._.js +0 -3
- package/.next/server/chunks/_05sovvj._.js +0 -3
- package/.next/server/chunks/_091bi8d._.js +0 -3
- package/.next/server/chunks/_0fzqt2y._.js +0 -142
- package/.next/server/chunks/_0lr92vd._.js +0 -3
- package/.next/server/chunks/_0mecv0f._.js +0 -31
- package/.next/server/chunks/_0mh8j4m._.js +0 -3
- package/.next/server/chunks/_0sb7tlz._.js +0 -3
- package/.next/server/chunks/_0si7xzn._.js +0 -3
- package/.next/server/chunks/_0u10wmw._.js +0 -55
- 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/_18d7-5p._.js +0 -3
- package/.next/server/chunks/_1c78vw5._.js +0 -237
- 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/_1zkfbaa._.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 -8
- package/.next/server/chunks/features_bot_0zt92oh._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1jidylq._.js +0 -3
- package/.next/server/chunks/ssr/[externals]__0dizgek._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0-nob52._.js +0 -4
- 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]__0s4wb56._.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]__0sd3izg._.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]__1-13tcn._.js +0 -43
- package/.next/server/chunks/ssr/[root-of-the-server]__10-dz5o._.js +0 -61
- package/.next/server/chunks/ssr/[root-of-the-server]__1zidpfl._.js +0 -3
- package/.next/server/chunks/ssr/_001_ms1._.js +0 -33
- package/.next/server/chunks/ssr/_01n3o0m._.js +0 -3
- package/.next/server/chunks/ssr/_04z2c7b._.js +0 -3
- package/.next/server/chunks/ssr/_06tssji._.js +0 -3
- package/.next/server/chunks/ssr/_0fk4c7d._.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/_0mggveb._.js +0 -3
- package/.next/server/chunks/ssr/_0nbpc_8._.js +0 -3
- 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/_11j92tu._.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/_1ebxla1._.js +0 -3
- package/.next/server/chunks/ssr/_1nj4niv._.js +0 -42
- package/.next/server/chunks/ssr/_1ov37if._.js +0 -3
- package/.next/server/chunks/ssr/_1ovmc9s._.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/0n3jle9kbqfr2.js +0 -40
- package/.next/static/chunks/0r5qcctujhxli.js +0 -1
- package/.next/static/chunks/10teww7wt-f6h.js +0 -1
- package/.next/static/chunks/126fj4pga_72k.js +0 -1
- package/.next/static/chunks/159udiexf6dom.js +0 -2
- package/.next/static/chunks/18a96rkhi4_yd.js +0 -1
- package/.next/static/chunks/18gi7o2ibrgh8.js +0 -3
- package/.next/static/chunks/1dkjg87pxk0n9.js +0 -28
- package/.next/static/chunks/1q9z1tohil7xn.js +0 -1
- package/.next/static/chunks/1sjgfets224re.js +0 -1
- package/.next/static/chunks/1wszsr83t01gh.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/2szintyibu0s5.js +0 -1
- package/.next/static/chunks/2t4vhi3m8ahya.js +0 -1
- package/.next/static/chunks/2tigdtz3dpcy8.js +0 -1
- package/.next/static/chunks/3-b6b7entqazs.js +0 -1
- package/.next/static/chunks/3786h5vayt80-.js +0 -28
- package/.next/static/chunks/3cq5g494yuke8.css +0 -1
- package/.next/static/chunks/3dmbhsv2u_8kj.js +0 -1
- package/.next/static/chunks/3hibbjnt85-k6.js +0 -1
- package/.next/static/chunks/3i0a5px-oufgc.js +0 -1
- package/.next/static/chunks/3wexk9qo8zuj3.js +0 -18
- package/.next/static/chunks/3xk7yqgomx7yq.js +0 -1
- package/.next/static/chunks/3xre8bu_pk1p_.js +0 -1
- package/.next/static/chunks/3zv6t3i77xhr_.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 -1079
- package/features/bot/task.store.ts +0 -487
- 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/{G6-gNoyGDEcgAsq-L4PE1 → yyRVBc0xxYQsh3GpbQX9b}/_buildManifest.js +0 -0
- /package/.next/static/{G6-gNoyGDEcgAsq-L4PE1 → yyRVBc0xxYQsh3GpbQX9b}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{G6-gNoyGDEcgAsq-L4PE1 → yyRVBc0xxYQsh3GpbQX9b}/_ssgManifest.js +0 -0
|
@@ -1,28 +1,16 @@
|
|
|
1
|
+
import { setTimeout as wait } from "node:timers/promises";
|
|
1
2
|
import type { ModelMessage } from "ai";
|
|
3
|
+
import { appEvents, presence } from "@/app/api/events/app-event.server";
|
|
4
|
+
import { BOT_RUN, WORKSPACE_KEEP } from "@/config";
|
|
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";
|
|
8
|
+
import { opensOnFinish, pathsIn } from "@/features/workspace/file-kind";
|
|
2
9
|
import {
|
|
3
|
-
|
|
4
|
-
appEvents,
|
|
5
|
-
presence,
|
|
6
|
-
} from "@/app/api/events/app-event.server";
|
|
7
|
-
import { BOT_RUN, PATHS, WORKSPACE_KEEP } from "@/config";
|
|
8
|
-
import {
|
|
9
|
-
type ModelFailure,
|
|
10
|
-
modelErrorToString,
|
|
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";
|
|
22
|
-
import { pathsIn } from "@/features/workspace/file-kind";
|
|
23
|
-
import {
|
|
10
|
+
botBrowserSession,
|
|
24
11
|
closeHiddenBrowser,
|
|
25
12
|
closeJobShell,
|
|
13
|
+
filesOnDisk,
|
|
26
14
|
jobScratch,
|
|
27
15
|
listScratchFolders,
|
|
28
16
|
pruneJobFiles,
|
|
@@ -31,302 +19,469 @@ 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";
|
|
24
|
+
import { PromiseChain } from "@/lib/utils";
|
|
25
|
+
import { findJobBot } from "./bot.query";
|
|
26
|
+
import { resumeTranscript, runBot, type ThreadEvent } from "./bot.run";
|
|
36
27
|
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
type
|
|
41
|
-
} from "./bot.run";
|
|
42
|
-
import {
|
|
43
|
-
TASK_CONTINUE,
|
|
44
|
-
type TaskPending,
|
|
45
|
-
type TaskSpeaker,
|
|
46
|
-
type TaskStatus,
|
|
47
|
-
tagSpeaker,
|
|
28
|
+
isAppStop,
|
|
29
|
+
THREAD_CONTINUE,
|
|
30
|
+
type ThreadSpeaker,
|
|
31
|
+
type ThreadStatus,
|
|
48
32
|
} from "./bot.schema";
|
|
49
33
|
import {
|
|
50
|
-
|
|
51
|
-
|
|
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,
|
|
52
55
|
deleteMessages,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
listTaskFolders,
|
|
61
|
-
listThread,
|
|
62
|
-
stopNote,
|
|
63
|
-
type TaskMessageInput,
|
|
64
|
-
updateTask,
|
|
56
|
+
deleteThread,
|
|
57
|
+
findThread,
|
|
58
|
+
insertThread,
|
|
59
|
+
listAutoStoppedThreads,
|
|
60
|
+
listRunningThreadIds,
|
|
61
|
+
listThreadFolders,
|
|
62
|
+
updateThread,
|
|
65
63
|
upsertMessage,
|
|
66
|
-
} from "./
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Runs a job so it outlives the request, writing every event to the task's
|
|
70
|
-
* thread (task_message). A run always starts from the thread. `answerTask` is
|
|
71
|
-
* the one way a person adds to it, serialized per job by `taskLock`, and
|
|
72
|
-
* `resumeTask` the one way the app picks a job back up by itself.
|
|
73
|
-
*/
|
|
74
|
-
|
|
75
|
-
type Run = {
|
|
76
|
-
stop: AbortController;
|
|
77
|
-
/** Resolves when the run ends, however it ends. Interrupters wait on it. */
|
|
78
|
-
done: Promise<void>;
|
|
79
|
-
/**
|
|
80
|
-
* Queues words for a run still going (bot.run `notes`). False once the loop
|
|
81
|
-
* has ended; then they go on the thread the ordinary way (answerTask).
|
|
82
|
-
*/
|
|
83
|
-
note: (text: string) => boolean;
|
|
84
|
-
};
|
|
64
|
+
} from "./thread.query";
|
|
85
65
|
|
|
66
|
+
type Run = { threadId: string; stop: AbortController; done: Promise<void> };
|
|
86
67
|
type Pinned = {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
__botPausing?: { current: Promise<void> };
|
|
68
|
+
__roomRuns?: Map<string, Run>;
|
|
69
|
+
__roomThreadLock?: ReturnType<typeof createKeyedLock>;
|
|
70
|
+
__roomPausing?: { current: Promise<void> };
|
|
91
71
|
};
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* One lane per job. Stop, append, relaunch must not interleave: two answers a
|
|
99
|
-
* moment apart would abort the same run and claim the same `lastSeq + 1`.
|
|
100
|
-
*/
|
|
101
|
-
const taskLock = ((globalThis as Pinned).__botTaskLock ??= createKeyedLock());
|
|
102
|
-
|
|
103
|
-
/** When each parked job picks itself back up (scheduleResume); one timer per job. */
|
|
104
|
-
const resumes: Map<string, ReturnType<typeof setTimeout>> = ((
|
|
105
|
-
globalThis as Pinned
|
|
106
|
-
).__botResumes ??= new Map());
|
|
107
|
-
|
|
108
|
-
/** The pause under way, if any; a returning browser waits for it (resumeStoppedTasks). */
|
|
109
|
-
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 ??= {
|
|
110
76
|
current: Promise.resolve(),
|
|
111
77
|
});
|
|
112
78
|
|
|
113
|
-
|
|
114
|
-
export async function startTask(input: {
|
|
79
|
+
export async function startThread(input: {
|
|
115
80
|
bot: string;
|
|
116
81
|
request: string;
|
|
117
82
|
label: string;
|
|
118
83
|
callId?: string | null;
|
|
119
|
-
|
|
120
|
-
from: TaskSpeaker;
|
|
84
|
+
from: ThreadSpeaker;
|
|
121
85
|
}) {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
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({
|
|
127
90
|
bot: row.bot,
|
|
128
91
|
request: row.request,
|
|
129
|
-
conversation,
|
|
130
92
|
from,
|
|
131
93
|
});
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
messages: [{ role: "user", content: opening }],
|
|
136
|
-
});
|
|
137
|
-
return task.id;
|
|
94
|
+
const thread = await insertThread({ ...row, opening });
|
|
95
|
+
await pump(thread.id);
|
|
96
|
+
return thread.id;
|
|
138
97
|
}
|
|
139
98
|
|
|
140
|
-
/**
|
|
141
|
-
|
|
142
|
-
* a job stopped on `ask_thursday`, a note to a running one (queued and read
|
|
143
|
-
* before its next step; the step in flight finishes), or a follow-up to a
|
|
144
|
-
* finished one. Only when the run has already ended does the running case
|
|
145
|
-
* fall through to stop, append, relaunch.
|
|
146
|
-
*
|
|
147
|
-
* @param from Who said it. The words go on the thread tagged with it (bot.schema
|
|
148
|
-
* tagSpeaker): Thursday passing something on from a call and the user typing on
|
|
149
|
-
* screen reach the bot down this one pipe, and a go-ahead means something
|
|
150
|
-
* different from each. Unset for words that already carry their tags — the notes
|
|
151
|
-
* a finished run left unread (drive).
|
|
152
|
-
*/
|
|
153
|
-
export async function answerTask(
|
|
99
|
+
/** A recipient selects a desk; replying to a question also names the exact exchange. */
|
|
100
|
+
export async function answerThread(
|
|
154
101
|
id: string,
|
|
155
102
|
answer: string,
|
|
156
|
-
from
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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;
|
|
119
|
+
}
|
|
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
|
+
);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
await pump(id);
|
|
144
|
+
return told;
|
|
145
|
+
}
|
|
162
146
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
+
);
|
|
166
161
|
return;
|
|
167
162
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
await
|
|
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);
|
|
171
167
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const thread = await listThread(id);
|
|
176
|
-
const { reply, as } = joinThread(task, said, thread);
|
|
177
|
-
logger.debug(`task ${id}: ${as}`);
|
|
178
|
-
|
|
179
|
-
await upsertMessage(id, (await lastSeq(id)) + 1, {
|
|
180
|
-
bot: null,
|
|
181
|
-
parent: null,
|
|
182
|
-
...reply,
|
|
183
|
-
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
184
170
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
endedAt: null,
|
|
191
|
-
});
|
|
192
|
-
// Resumed on the rows as they were stored (bot.run resumeThread)
|
|
193
|
-
launch(id, {
|
|
194
|
-
bot: task.bot,
|
|
195
|
-
messages: resumeThread([...thread, reply], await listBorrowedTails(id)),
|
|
196
|
-
});
|
|
171
|
+
function launch(work: RoomWork) {
|
|
172
|
+
const stop = new AbortController();
|
|
173
|
+
let finish!: () => void;
|
|
174
|
+
const done = new Promise<void>((resolve) => {
|
|
175
|
+
finish = resolve;
|
|
197
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
|
+
});
|
|
198
189
|
}
|
|
199
190
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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);
|
|
200
|
+
}
|
|
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),
|
|
223
|
+
);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
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 path = (await filesOnDisk(pathsIn(thread.outcome ?? ""), null)).find(
|
|
232
|
+
opensOnFinish,
|
|
233
|
+
);
|
|
234
|
+
if (path)
|
|
235
|
+
appEvents.emit({
|
|
236
|
+
type: "artifact",
|
|
237
|
+
threadId: thread.id,
|
|
238
|
+
label: thread.label,
|
|
239
|
+
path,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
234
242
|
}
|
|
235
243
|
|
|
236
|
-
/**
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
+
),
|
|
260
270
|
},
|
|
261
|
-
|
|
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
|
+
};
|
|
326
|
+
}
|
|
262
327
|
}
|
|
263
328
|
return {
|
|
264
|
-
as:
|
|
265
|
-
|
|
266
|
-
? "interruption"
|
|
267
|
-
: task.status === "waiting"
|
|
268
|
-
? "answer"
|
|
269
|
-
: "follow-up",
|
|
270
|
-
reply: { role: "user", content: answer },
|
|
329
|
+
ending: ending as { text: string; stopped: boolean } | null,
|
|
330
|
+
failure: failure as { message: string; retry: boolean } | null,
|
|
271
331
|
};
|
|
272
332
|
}
|
|
273
333
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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>();
|
|
348
|
+
constructor(
|
|
349
|
+
private work: RoomWork,
|
|
350
|
+
private signal: AbortSignal,
|
|
351
|
+
) {}
|
|
352
|
+
private async write(
|
|
353
|
+
seq: number | null,
|
|
354
|
+
value: ModelMessage,
|
|
355
|
+
compact = false,
|
|
356
|
+
) {
|
|
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,
|
|
364
|
+
};
|
|
365
|
+
if (seq === null) return appendRoomMessage(this.work.threadId, row);
|
|
366
|
+
await upsertMessage(this.work.threadId, seq, row);
|
|
367
|
+
return seq;
|
|
368
|
+
}
|
|
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, {
|
|
375
|
+
role: "assistant",
|
|
376
|
+
content: this.parts.assistant,
|
|
377
|
+
});
|
|
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({
|
|
394
|
+
type: "tool-call",
|
|
395
|
+
toolCallId: event.id,
|
|
396
|
+
toolName: event.name,
|
|
397
|
+
input: event.input,
|
|
398
|
+
...(event.providerExecuted ? { providerExecuted: true } : {}),
|
|
399
|
+
...(event.providerOptions
|
|
400
|
+
? { providerOptions: event.providerOptions }
|
|
401
|
+
: {}),
|
|
402
|
+
});
|
|
403
|
+
this.assistant = await this.write(this.assistant, {
|
|
404
|
+
role: "assistant",
|
|
405
|
+
content: this.parts.assistant,
|
|
406
|
+
});
|
|
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({
|
|
411
|
+
type: "tool-result",
|
|
412
|
+
toolCallId: event.id,
|
|
413
|
+
toolName: event.name,
|
|
414
|
+
output: event.error
|
|
415
|
+
? { type: "error-text", value: String(event.output) }
|
|
416
|
+
: typeof event.output === "string"
|
|
417
|
+
? { type: "text", value: event.output }
|
|
418
|
+
: { type: "json", value: event.output as never },
|
|
419
|
+
});
|
|
420
|
+
this.tool = await this.write(this.tool, {
|
|
421
|
+
role: "tool",
|
|
422
|
+
content: this.parts.tool,
|
|
423
|
+
});
|
|
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);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
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
|
+
}
|
|
446
|
+
|
|
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,
|
|
287
456
|
pending: null,
|
|
288
|
-
// Nobody needs to be told what they just did themselves
|
|
289
457
|
seen: true,
|
|
290
458
|
endedAt: new Date(),
|
|
291
459
|
});
|
|
292
|
-
|
|
293
|
-
// answerTask is followed by a relaunch and must not close it.
|
|
294
|
-
await live?.done;
|
|
460
|
+
await stopRuns(id);
|
|
295
461
|
await closeJobShell(id);
|
|
296
462
|
});
|
|
297
463
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
export async function removeTask(id: string) {
|
|
301
|
-
return taskLock(id, () => removeLockedTask(id));
|
|
464
|
+
export async function removeThread(id: string) {
|
|
465
|
+
return threadLock(id, () => removeLockedThread(id));
|
|
302
466
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
forgetResume(id);
|
|
308
|
-
const live = running.get(id);
|
|
309
|
-
live?.stop.abort();
|
|
310
|
-
await live?.done;
|
|
467
|
+
async function removeLockedThread(id: string) {
|
|
468
|
+
const thread = await findThread(id);
|
|
469
|
+
await cancelRoom(id);
|
|
470
|
+
await stopRuns(id);
|
|
311
471
|
await closeJobShell(id);
|
|
312
|
-
const
|
|
313
|
-
if (
|
|
314
|
-
return
|
|
472
|
+
const removed = await deleteThread(id);
|
|
473
|
+
if (removed && thread) await removeJobScratch(id, thread.label);
|
|
474
|
+
return removed;
|
|
315
475
|
}
|
|
316
476
|
|
|
317
|
-
|
|
318
|
-
* Clearing finished jobs from the screen, and their working folders with them.
|
|
319
|
-
* Lives here rather than in the query because a row and a folder go together
|
|
320
|
-
* and only one of the two is the database's.
|
|
321
|
-
*/
|
|
322
|
-
export async function removeFinishedTasks(): Promise<number> {
|
|
477
|
+
export async function removeFinishedThreads(): Promise<number> {
|
|
323
478
|
let removed = 0;
|
|
324
|
-
for (const
|
|
325
|
-
if (
|
|
326
|
-
await
|
|
327
|
-
const current = await
|
|
328
|
-
if (current?.status !== "done" && current?.status !== "
|
|
329
|
-
if (await
|
|
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;
|
|
330
485
|
});
|
|
331
486
|
}
|
|
332
487
|
return removed;
|
|
@@ -343,25 +498,25 @@ export async function removeFinishedTasks(): Promise<number> {
|
|
|
343
498
|
*/
|
|
344
499
|
export async function sweepJobFiles(): Promise<string[]> {
|
|
345
500
|
const cutoff = Date.now() - WORKSPACE_KEEP.forMs;
|
|
346
|
-
const stale = (
|
|
347
|
-
status:
|
|
501
|
+
const stale = (thread: {
|
|
502
|
+
status: ThreadStatus;
|
|
348
503
|
endedAt: Date | null;
|
|
349
504
|
updatedAt: Date;
|
|
350
505
|
}) =>
|
|
351
|
-
(
|
|
352
|
-
(
|
|
506
|
+
(thread.status === "done" || thread.status === "cancelled") &&
|
|
507
|
+
(thread.endedAt ?? thread.updatedAt).getTime() < cutoff;
|
|
353
508
|
|
|
354
509
|
// Folders on disk; each one a job owns is taken out as its job is read
|
|
355
510
|
const unowned = new Set(await listScratchFolders());
|
|
356
511
|
const removed: string[] = [];
|
|
357
|
-
for (const
|
|
358
|
-
const folder = jobScratch(
|
|
359
|
-
if (!unowned.delete(folder) || !stale(
|
|
360
|
-
await
|
|
361
|
-
const now = await
|
|
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);
|
|
362
517
|
if (!now || !stale(now)) return;
|
|
363
|
-
await closeHiddenBrowser(
|
|
364
|
-
await removeJobScratch(
|
|
518
|
+
await closeHiddenBrowser(thread.id);
|
|
519
|
+
await removeJobScratch(thread.id, thread.label);
|
|
365
520
|
removed.push(folder);
|
|
366
521
|
});
|
|
367
522
|
}
|
|
@@ -375,600 +530,44 @@ export async function sweepJobFiles(): Promise<string[]> {
|
|
|
375
530
|
return removed;
|
|
376
531
|
}
|
|
377
532
|
|
|
378
|
-
/**
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
why: "The server restarted while this was running.",
|
|
390
|
-
});
|
|
391
|
-
}
|
|
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
|
+
});
|
|
392
544
|
}
|
|
393
545
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (!task || task.status !== "running") return;
|
|
410
|
-
await parkTask(id, { why: reason });
|
|
411
|
-
}).catch((cause) => logger.error(`task ${id}: pause`, cause)),
|
|
412
|
-
),
|
|
413
|
-
).then(() => {});
|
|
414
|
-
pausing.current = work;
|
|
415
|
-
await work;
|
|
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;
|
|
416
561
|
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* A browser is on the app again (instrumentation `presence.onBack`): every job
|
|
420
|
-
* the app parked picks itself back up — now, or when the wait a failed model
|
|
421
|
-
* call set runs out. A pause still under way is waited for first, so a quick
|
|
422
|
-
* reload does not miss what it parks.
|
|
423
|
-
*/
|
|
424
|
-
export async function resumeStoppedTasks() {
|
|
562
|
+
export async function resumeStoppedThreads() {
|
|
425
563
|
await pausing.current;
|
|
426
|
-
for (const task of await listAutoStoppedTasks()) {
|
|
427
|
-
scheduleResume(task.id, task.retryAt - Date.now());
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/** One timer per job: a newer stop replaces what an older one set. */
|
|
432
|
-
function scheduleResume(id: string, inMs: number) {
|
|
433
|
-
forgetResume(id);
|
|
434
|
-
resumes.set(
|
|
435
|
-
id,
|
|
436
|
-
setTimeout(
|
|
437
|
-
() => {
|
|
438
|
-
resumes.delete(id);
|
|
439
|
-
void resumeTask(id).catch((cause) =>
|
|
440
|
-
logger.error(`task ${id}: resume`, cause),
|
|
441
|
-
);
|
|
442
|
-
},
|
|
443
|
-
Math.max(0, inMs),
|
|
444
|
-
),
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function forgetResume(id: string) {
|
|
449
|
-
clearTimeout(resumes.get(id));
|
|
450
|
-
resumes.delete(id);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* Picks a parked job back up. Only with a browser on the app — nothing runs
|
|
455
|
-
* without one, and its return tries again — and only if it is still parked: a
|
|
456
|
-
* person may have answered, cancelled or deleted it since. No turn is added,
|
|
457
|
-
* because nobody said anything; the note parkTask left is what the run reads last.
|
|
458
|
-
*/
|
|
459
|
-
async function resumeTask(id: string) {
|
|
460
564
|
if (!presence.watching) return;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const thread = await listThread(id);
|
|
467
|
-
await updateTask(id, {
|
|
468
|
-
status: "running",
|
|
469
|
-
outcome: null,
|
|
470
|
-
pending: null,
|
|
471
|
-
seen: false,
|
|
472
|
-
endedAt: null,
|
|
473
|
-
});
|
|
474
|
-
launch(id, {
|
|
475
|
-
bot: task.bot,
|
|
476
|
-
messages: resumeThread(thread, await listBorrowedTails(id)),
|
|
477
|
-
});
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Stops a job the app stopped, rather than the bot or a person: a restart, a
|
|
483
|
-
* closed browser, a model call a retry or a compaction can fix. It waits with
|
|
484
|
-
* the one continue option like any stop, with a line in its thread saying why —
|
|
485
|
-
* the resumed run reads it — and picks itself back up (scheduleResume). Unless
|
|
486
|
-
* the app has already stopped it `BOT_RUN.autoResumes` times since a person last
|
|
487
|
-
* spoke to it: that is what a job that keeps taking the server down looks like,
|
|
488
|
-
* and it waits for a person instead.
|
|
489
|
-
*/
|
|
490
|
-
async function parkTask(
|
|
491
|
-
id: string,
|
|
492
|
-
stop: {
|
|
493
|
-
why: string;
|
|
494
|
-
/** A failed model call: wait before trying again (config BOT_RUN.retryAfterMs). */
|
|
495
|
-
retry?: boolean;
|
|
496
|
-
/** Where the job compacts from now on (bot.run `budget`). */
|
|
497
|
-
budget?: number;
|
|
498
|
-
},
|
|
499
|
-
) {
|
|
500
|
-
const stops = await countStopsSinceSpoken(id);
|
|
501
|
-
const auto = stops < BOT_RUN.autoResumes;
|
|
502
|
-
const waits = BOT_RUN.retryAfterMs;
|
|
503
|
-
const inMs =
|
|
504
|
-
auto && stop.retry ? waits[Math.min(stops, waits.length - 1)] : 0;
|
|
505
|
-
|
|
506
|
-
await writeNote(id, stopNote(stop.why)).catch((cause) =>
|
|
507
|
-
logger.warn(`task ${id}: stop not written`, cause),
|
|
508
|
-
);
|
|
509
|
-
await writeEnding(id, {
|
|
510
|
-
status: "waiting",
|
|
511
|
-
outcome: auto
|
|
512
|
-
? `${stop.why} ${inMs ? `It tries again in ${spell(inMs)}.` : "It picks back up by itself."}`
|
|
513
|
-
: `${stop.why} It has stopped ${stops + 1} times in a row, so it waits for you. Everything it had done is still here.`,
|
|
514
|
-
pending: {
|
|
515
|
-
toolCallId: null,
|
|
516
|
-
options: [TASK_CONTINUE],
|
|
517
|
-
...(auto ? { auto: true, retryAt: Date.now() + inMs } : {}),
|
|
518
|
-
},
|
|
519
|
-
...(stop.budget ? { contextBudget: stop.budget } : {}),
|
|
520
|
-
seen: false,
|
|
521
|
-
endedAt: null,
|
|
522
|
-
});
|
|
523
|
-
if (auto) scheduleResume(id, inMs);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
const spell = (ms: number) =>
|
|
527
|
-
ms < 60_000
|
|
528
|
-
? `${Math.round(ms / 1000)} seconds`
|
|
529
|
-
: `${Math.round(ms / 60_000)} minutes`;
|
|
530
|
-
|
|
531
|
-
/** A line from the app in a thread no run is writing (ThreadWriter.note is the one for a run that is). */
|
|
532
|
-
async function writeNote(id: string, text: string) {
|
|
533
|
-
await upsertMessage(id, (await lastSeq(id)) + 1, {
|
|
534
|
-
bot: null,
|
|
535
|
-
parent: null,
|
|
536
|
-
role: "user",
|
|
537
|
-
content: text,
|
|
538
|
-
note: true,
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* The write that tells anyone a run ended at all. Tried again: everything else
|
|
544
|
-
* in the run has happened, and a miss leaves the row `running` until the next
|
|
545
|
-
* boot. Writes are serialised, so what is left is a database that briefly
|
|
546
|
-
* cannot be written to (database/db.ts).
|
|
547
|
-
*/
|
|
548
|
-
async function writeEnding(
|
|
549
|
-
id: string,
|
|
550
|
-
patch: Parameters<typeof updateTask>[1],
|
|
551
|
-
) {
|
|
552
|
-
for (const wait of [1_000, 3_000, null]) {
|
|
553
|
-
try {
|
|
554
|
-
await updateTask(id, patch);
|
|
555
|
-
return;
|
|
556
|
-
} catch (cause) {
|
|
557
|
-
if (wait === null) throw cause;
|
|
558
|
-
logger.warn(`task ${id}: ending not written, trying again`, cause);
|
|
559
|
-
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
function launch(id: string, input: { bot: string; messages: ModelMessage[] }) {
|
|
565
|
-
const stop = new AbortController();
|
|
566
|
-
let finish: () => void = () => {};
|
|
567
|
-
const done = new Promise<void>((resolve) => {
|
|
568
|
-
finish = resolve;
|
|
569
|
-
});
|
|
570
|
-
// Notes said to the job while it runs. The run drains them before each step;
|
|
571
|
-
// what is left when the loop ends is handed on by `drive`.
|
|
572
|
-
const notes: string[] = [];
|
|
573
|
-
const run: Run = {
|
|
574
|
-
stop,
|
|
575
|
-
done,
|
|
576
|
-
note: (text) => {
|
|
577
|
-
if (!running.has(id)) return false;
|
|
578
|
-
notes.push(text);
|
|
579
|
-
return true;
|
|
580
|
-
},
|
|
581
|
-
};
|
|
582
|
-
running.set(id, run);
|
|
583
|
-
// Held by the server, not by a request: a run also starts from a returning
|
|
584
|
-
// browser and a retry timer, where `after()` throws (no request) or waits for
|
|
585
|
-
// the event stream to close
|
|
586
|
-
void drive(id, input, stop.signal, notes)
|
|
587
|
-
.catch((cause) => {
|
|
588
|
-
logger.error(`task ${id}`, cause);
|
|
589
|
-
})
|
|
590
|
-
.finally(finish);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
/**
|
|
594
|
-
* Writes the thread one step at a time: rows go in while the step streams and
|
|
595
|
-
* are rewritten from the step's own messages when it ends. Slots (`seq`) are
|
|
596
|
-
* claimed synchronously because a borrowed bot writes into the same thread
|
|
597
|
-
* from inside a tool call.
|
|
598
|
-
*/
|
|
599
|
-
class ThreadWriter {
|
|
600
|
-
private seq: number;
|
|
601
|
-
/** One step in flight per writer: the job's bot and each bot it borrowed. */
|
|
602
|
-
private steps = new Map<
|
|
603
|
-
string,
|
|
604
|
-
{ assistant: number | null; tool: number | null; parts: StepParts }
|
|
605
|
-
>();
|
|
606
|
-
|
|
607
|
-
/** Set once the run is over: a borrowed run still unwinding must not write into a thread a resume has taken. */
|
|
608
|
-
private closed = false;
|
|
609
|
-
|
|
610
|
-
constructor(
|
|
611
|
-
private taskId: string,
|
|
612
|
-
seq: number,
|
|
613
|
-
) {
|
|
614
|
-
this.seq = seq;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
close() {
|
|
618
|
-
this.closed = true;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/**
|
|
622
|
-
* A line from the app rather than from anyone in the room. A user row so a
|
|
623
|
-
* resumed run reads it as something it was told, and `note` so the screen
|
|
624
|
-
* draws it as a note and `rosterOf` does not take it for a person speaking.
|
|
625
|
-
*/
|
|
626
|
-
async note(text: string) {
|
|
627
|
-
await upsertMessage(this.taskId, ++this.seq, {
|
|
628
|
-
bot: null,
|
|
629
|
-
parent: null,
|
|
630
|
-
role: "user",
|
|
631
|
-
content: text,
|
|
632
|
-
note: true,
|
|
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);
|
|
633
570
|
});
|
|
571
|
+
await pump(id);
|
|
634
572
|
}
|
|
635
|
-
|
|
636
|
-
private step(event: TaskEvent) {
|
|
637
|
-
const key = `${event.bot} ${event.parent ?? ""}`;
|
|
638
|
-
let step = this.steps.get(key);
|
|
639
|
-
if (!step) {
|
|
640
|
-
step = {
|
|
641
|
-
assistant: null,
|
|
642
|
-
tool: null,
|
|
643
|
-
parts: { assistant: [], tool: [] },
|
|
644
|
-
};
|
|
645
|
-
this.steps.set(key, step);
|
|
646
|
-
}
|
|
647
|
-
return step;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
private write(
|
|
651
|
-
seq: number,
|
|
652
|
-
event: TaskEvent,
|
|
653
|
-
message: ModelMessage,
|
|
654
|
-
extra: { compact?: boolean; note?: boolean } = {},
|
|
655
|
-
) {
|
|
656
|
-
const input: TaskMessageInput = {
|
|
657
|
-
bot: event.bot,
|
|
658
|
-
parent: event.parent,
|
|
659
|
-
role: message.role,
|
|
660
|
-
content: message.content,
|
|
661
|
-
compact: extra.compact ?? false,
|
|
662
|
-
note: extra.note ?? false,
|
|
663
|
-
};
|
|
664
|
-
return upsertMessage(this.taskId, seq, input);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
async on(event: TaskEvent) {
|
|
668
|
-
if (this.closed) return;
|
|
669
|
-
const step = this.step(event);
|
|
670
|
-
switch (event.type) {
|
|
671
|
-
case "request":
|
|
672
|
-
return this.write(++this.seq, event, {
|
|
673
|
-
role: "user",
|
|
674
|
-
content: event.content,
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
case "text":
|
|
678
|
-
step.assistant ??= ++this.seq;
|
|
679
|
-
step.parts.assistant.push({ type: "text", text: event.text });
|
|
680
|
-
return this.write(step.assistant, event, {
|
|
681
|
-
role: "assistant",
|
|
682
|
-
content: step.parts.assistant,
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
case "tool":
|
|
686
|
-
step.assistant ??= ++this.seq;
|
|
687
|
-
step.parts.assistant.push({
|
|
688
|
-
type: "tool-call",
|
|
689
|
-
toolCallId: event.id,
|
|
690
|
-
toolName: event.name,
|
|
691
|
-
input: event.input,
|
|
692
|
-
});
|
|
693
|
-
return this.write(step.assistant, event, {
|
|
694
|
-
role: "assistant",
|
|
695
|
-
content: step.parts.assistant,
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
case "tool-result":
|
|
699
|
-
step.assistant ??= ++this.seq;
|
|
700
|
-
step.tool ??= ++this.seq;
|
|
701
|
-
step.parts.tool.push({
|
|
702
|
-
type: "tool-result",
|
|
703
|
-
toolCallId: event.id,
|
|
704
|
-
toolName: event.name,
|
|
705
|
-
output: event.error
|
|
706
|
-
? { type: "error-text", value: String(event.output) }
|
|
707
|
-
: typeof event.output === "string"
|
|
708
|
-
? { type: "text", value: event.output }
|
|
709
|
-
: { type: "json", value: event.output as never },
|
|
710
|
-
});
|
|
711
|
-
return this.write(step.tool, event, {
|
|
712
|
-
role: "tool",
|
|
713
|
-
content: step.parts.tool,
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
case "step": {
|
|
717
|
-
// The slots the step streamed into, then fresh ones for anything more
|
|
718
|
-
const slots = [step.assistant, step.tool];
|
|
719
|
-
this.steps.delete(`${event.bot} ${event.parent ?? ""}`);
|
|
720
|
-
const written = new Set<number>();
|
|
721
|
-
for (const [index, message] of event.messages.entries()) {
|
|
722
|
-
const seq = slots[index] ?? ++this.seq;
|
|
723
|
-
written.add(seq);
|
|
724
|
-
await this.write(seq, event, message);
|
|
725
|
-
}
|
|
726
|
-
// A slot claimed while streaming that the step did not fill: a
|
|
727
|
-
// provider-executed tool (web search) answers inside the assistant
|
|
728
|
-
// message, leaving a tool row with no call in front of it.
|
|
729
|
-
const stale = slots.filter(
|
|
730
|
-
(seq): seq is number => seq !== null && !written.has(seq),
|
|
731
|
-
);
|
|
732
|
-
if (stale.length) await deleteMessages(this.taskId, stale);
|
|
733
|
-
return;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
case "compact":
|
|
737
|
-
// The row a resume restarts from (task.query listThread); earlier rows
|
|
738
|
-
// stay for the screen.
|
|
739
|
-
return this.write(
|
|
740
|
-
++this.seq,
|
|
741
|
-
event,
|
|
742
|
-
{ role: "user", content: event.text },
|
|
743
|
-
{ compact: true, note: true },
|
|
744
|
-
);
|
|
745
|
-
|
|
746
|
-
case "interjection":
|
|
747
|
-
// Between the two steps, where the model read it.
|
|
748
|
-
return this.write(++this.seq, event, {
|
|
749
|
-
role: "user",
|
|
750
|
-
content: event.text,
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
default:
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
type StepParts = {
|
|
760
|
-
assistant: Extract<ModelMessage, { role: "assistant" }>["content"] &
|
|
761
|
-
unknown[];
|
|
762
|
-
tool: Extract<ModelMessage, { role: "tool" }>["content"];
|
|
763
|
-
};
|
|
764
|
-
|
|
765
|
-
async function drive(
|
|
766
|
-
id: string,
|
|
767
|
-
input: { bot: string; messages: ModelMessage[] },
|
|
768
|
-
signal: AbortSignal,
|
|
769
|
-
notes: string[],
|
|
770
|
-
) {
|
|
771
|
-
const thread = new ThreadWriter(id, await lastSeq(id));
|
|
772
|
-
const desks = createBotDesks();
|
|
773
|
-
|
|
774
|
-
let ending: {
|
|
775
|
-
status: TaskStatus;
|
|
776
|
-
outcome: string;
|
|
777
|
-
pending: TaskPending | null;
|
|
778
|
-
} | null = null;
|
|
779
|
-
/**
|
|
780
|
-
* What broke, as the provider said it — the thread carries it too (below) —
|
|
781
|
-
* and whose it is to fix (model.ts modelFailureOf).
|
|
782
|
-
*/
|
|
783
|
-
let broken: {
|
|
784
|
-
message: string;
|
|
785
|
-
failure: ModelFailure;
|
|
786
|
-
budget?: number;
|
|
787
|
-
} | null = null;
|
|
788
|
-
|
|
789
|
-
try {
|
|
790
|
-
await runBot(input, {
|
|
791
|
-
signal,
|
|
792
|
-
taskId: id,
|
|
793
|
-
desks,
|
|
794
|
-
notes: () => notes.splice(0),
|
|
795
|
-
emit: async (event) => {
|
|
796
|
-
await thread.on(event);
|
|
797
|
-
// A borrowed bot's steps count toward the job's usage; the window fill
|
|
798
|
-
// is only the job's own bot's.
|
|
799
|
-
if (event.type === "step") {
|
|
800
|
-
await addTaskUsage(
|
|
801
|
-
id,
|
|
802
|
-
event.usage,
|
|
803
|
-
event.parent
|
|
804
|
-
? null
|
|
805
|
-
: { tokens: event.usage.input, budget: event.budget },
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
// Compaction and answering a borrowed bot (bot.run answerBack) cost the job too.
|
|
809
|
-
if (event.type === "compact" || event.type === "answered") {
|
|
810
|
-
await addTaskUsage(id, event.usage);
|
|
811
|
-
}
|
|
812
|
-
// Only the job's own bot ends it; a borrowed bot's ending is a tool result.
|
|
813
|
-
if (event.parent) return;
|
|
814
|
-
if (event.type === "waiting") {
|
|
815
|
-
ending = {
|
|
816
|
-
status: "waiting",
|
|
817
|
-
outcome: event.question,
|
|
818
|
-
pending: { toolCallId: event.id, options: event.options },
|
|
819
|
-
};
|
|
820
|
-
} else if (event.type === "answer") {
|
|
821
|
-
// An answer ends the job; one the app stopped (bot.run `stopped`) waits
|
|
822
|
-
// with the continue option. The outcome is the answer text verbatim;
|
|
823
|
-
// the same text is the thread line (bot.query linesOf).
|
|
824
|
-
ending = event.stopped
|
|
825
|
-
? {
|
|
826
|
-
status: "waiting",
|
|
827
|
-
outcome: event.text,
|
|
828
|
-
pending: { toolCallId: null, options: [TASK_CONTINUE] },
|
|
829
|
-
}
|
|
830
|
-
: { status: "done", outcome: event.text, pending: null };
|
|
831
|
-
} else if (event.type === "error") {
|
|
832
|
-
broken = {
|
|
833
|
-
message: event.message,
|
|
834
|
-
failure: event.failure,
|
|
835
|
-
budget: event.budget,
|
|
836
|
-
};
|
|
837
|
-
ending = {
|
|
838
|
-
status: "failed",
|
|
839
|
-
outcome: broke(event.message),
|
|
840
|
-
pending: null,
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
},
|
|
844
|
-
});
|
|
845
|
-
} catch (cause) {
|
|
846
|
-
broken = {
|
|
847
|
-
message: modelErrorToString(cause),
|
|
848
|
-
failure: modelFailureOf(cause),
|
|
849
|
-
};
|
|
850
|
-
ending = {
|
|
851
|
-
status: "failed",
|
|
852
|
-
outcome: broke(broken.message),
|
|
853
|
-
pending: null,
|
|
854
|
-
};
|
|
855
|
-
} finally {
|
|
856
|
-
// A resumed segment cannot overtake a participant still unwinding its tools.
|
|
857
|
-
await desks.settle();
|
|
858
|
-
thread.close();
|
|
859
|
-
running.delete(id);
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
// Whoever aborted writes the row: cancel marks failed, answerTask relaunches,
|
|
863
|
-
// a pause parks.
|
|
864
|
-
if (signal.aborted) return;
|
|
865
|
-
|
|
866
|
-
const final = ending ?? {
|
|
867
|
-
status: "failed" as const,
|
|
868
|
-
outcome: "Stopped without handing anything back.",
|
|
869
|
-
pending: null,
|
|
870
|
-
};
|
|
871
|
-
// A break a moment or a compaction can fix is the app's to pick back up, not
|
|
872
|
-
// a failure for a person to find (parkTask)
|
|
873
|
-
const park = broken && broken.failure !== "fatal" ? broken : null;
|
|
874
|
-
|
|
875
|
-
try {
|
|
876
|
-
if (park) {
|
|
877
|
-
await parkTask(id, {
|
|
878
|
-
why:
|
|
879
|
-
park.failure === "overflow"
|
|
880
|
-
? `The model refused the context as too long (${park.message}); it is compacted before going on.`
|
|
881
|
-
: `The model call failed (${park.message}).`,
|
|
882
|
-
retry: park.failure === "transient",
|
|
883
|
-
budget: park.budget,
|
|
884
|
-
});
|
|
885
|
-
} else {
|
|
886
|
-
// A thread that simply stops cannot be read: the room shows a tool call
|
|
887
|
-
// with no answer, and a resumed run has no idea why. The task row says it
|
|
888
|
-
// too, but the row is a status and this is the story. Never at the cost of
|
|
889
|
-
// the status write, which is what tells anyone the job ended at all.
|
|
890
|
-
if (final.status === "failed") {
|
|
891
|
-
await thread
|
|
892
|
-
.note(`The run stopped here: ${broken?.message ?? final.outcome}`)
|
|
893
|
-
.catch((cause) =>
|
|
894
|
-
logger.warn(`task ${id}: break not written`, cause),
|
|
895
|
-
);
|
|
896
|
-
}
|
|
897
|
-
await writeEnding(id, {
|
|
898
|
-
...final,
|
|
899
|
-
seen: false,
|
|
900
|
-
endedAt: final.status === "waiting" ? null : new Date(),
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
} catch (cause) {
|
|
904
|
-
// Said plainly rather than thrown into a promise nobody awaits; the row
|
|
905
|
-
// stays `running`, which the next boot reconciles (sweepTasks)
|
|
906
|
-
logger.error(`task ${id}: could not record how it ended`, cause);
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
// Follow-ups can reopen a completed job. Its participants keep their browsers
|
|
911
|
-
// until cancellation, deletion, or the workspace retention sweep.
|
|
912
|
-
void pruneJobFiles();
|
|
913
|
-
|
|
914
|
-
const task = await findTask(id);
|
|
915
|
-
if (!task) return;
|
|
916
|
-
|
|
917
|
-
// Notes that arrived after the last step began were never read; they go on
|
|
918
|
-
// as a turn and the job resumes from what it just handed back.
|
|
919
|
-
if (notes.length) {
|
|
920
|
-
const said = notes.splice(0).join("\n\n");
|
|
921
|
-
logger.debug(`task ${id}: interjection after the run ended — resuming`);
|
|
922
|
-
void answerTask(id, said).catch((cause) => {
|
|
923
|
-
logger.error(`task ${id}: late interjection`, cause);
|
|
924
|
-
});
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
// A parked job picks itself back up; nobody needs telling yet
|
|
929
|
-
if (park) return;
|
|
930
|
-
|
|
931
|
-
await announce(task.label, final);
|
|
932
|
-
|
|
933
|
-
// A finished job naming a document opens it on the screen (workspace artifact-view).
|
|
934
|
-
if (final.status === "done") {
|
|
935
|
-
const path = artifactIn(final.outcome);
|
|
936
|
-
if (path) {
|
|
937
|
-
appEvents.emit({ type: "artifact", taskId: id, label: task.label, path });
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
/** The document an answer names: the artifacts folder first, otherwise the first drawable file. */
|
|
943
|
-
function artifactIn(outcome: string): string | null {
|
|
944
|
-
const paths = pathsIn(outcome);
|
|
945
|
-
return (
|
|
946
|
-
paths.find((path) => path.startsWith(`${PATHS.artifacts}/`)) ??
|
|
947
|
-
paths[0] ??
|
|
948
|
-
null
|
|
949
|
-
);
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
/**
|
|
953
|
-
* A failure the user can act on: the provider message stays as the clue, plus
|
|
954
|
-
* the way out (answering resumes from a condensed thread).
|
|
955
|
-
*/
|
|
956
|
-
function broke(message: string): string {
|
|
957
|
-
return `The run broke: ${message}\n\nEverything it had done is still here — answer to pick it back up.`;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
/**
|
|
961
|
-
* Desktop notification unless a call is live (the browser relays the job to
|
|
962
|
-
* the call itself). `watchers === 0` is checked first: a crashed browser can
|
|
963
|
-
* leave a live call row behind until boot clears it (thursday.query sweepCalls).
|
|
964
|
-
*/
|
|
965
|
-
async function announce(
|
|
966
|
-
label: string,
|
|
967
|
-
ending: { status: TaskStatus; outcome: string },
|
|
968
|
-
) {
|
|
969
|
-
const nobodyWatching = appEventStream.watchers === 0;
|
|
970
|
-
if (!nobodyWatching && (await isAnyCallLive())) return;
|
|
971
|
-
|
|
972
|
-
const title = ending.status === "waiting" ? `${label} — needs you` : label;
|
|
973
|
-
desktopNotify(title, ending.outcome);
|
|
974
573
|
}
|