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
|
@@ -0,0 +1,1408 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { after, afterEach, mock, test } from "node:test";
|
|
6
|
+
import { APICallError, simulateReadableStream } from "ai";
|
|
7
|
+
import { MockLanguageModelV4 } from "ai/test";
|
|
8
|
+
|
|
9
|
+
// The real runner, DB, tools and prompts run against an empty temporary home.
|
|
10
|
+
const home = await mkdtemp(join(tmpdir(), "thursday-context-"));
|
|
11
|
+
process.env.THURSDAY_HOME = home;
|
|
12
|
+
process.env.THURSDAY_SKIP_BROWSER = "1";
|
|
13
|
+
process.env.THURSDAY_TOOL_PATH = join(home, "tools");
|
|
14
|
+
process.env.PATH = `${join(home, "tools")}:${process.env.PATH}`;
|
|
15
|
+
await mkdir(join(home, "tools"));
|
|
16
|
+
const browserCli = join(home, "tools", "playwright-cli");
|
|
17
|
+
await writeFile(
|
|
18
|
+
browserCli,
|
|
19
|
+
`#!/usr/bin/env node
|
|
20
|
+
const fs = require("node:fs");
|
|
21
|
+
const path = require("node:path");
|
|
22
|
+
const root = process.env.THURSDAY_HOME;
|
|
23
|
+
if (process.argv[2] === "list") {
|
|
24
|
+
const fixture = path.join(root, "browsers.json");
|
|
25
|
+
console.log(fs.existsSync(fixture) ? fs.readFileSync(fixture, "utf8") : '{"browsers":[]}');
|
|
26
|
+
} else if (process.argv[2] === "close") {
|
|
27
|
+
fs.appendFileSync(path.join(root, "closed.txt"), process.env.PLAYWRIGHT_CLI_SESSION + "\\n");
|
|
28
|
+
}
|
|
29
|
+
`,
|
|
30
|
+
);
|
|
31
|
+
await chmod(browserCli, 0o755);
|
|
32
|
+
const realModel = await import("../features/ai/model.ts");
|
|
33
|
+
const plans = new Map<string, ((prompt: string) => unknown[])[]>();
|
|
34
|
+
const replies = new Map<string, ((prompt: string) => string)[]>();
|
|
35
|
+
const inputs = new Map<string, string[]>();
|
|
36
|
+
const failures: unknown[] = [];
|
|
37
|
+
let nextId = 0;
|
|
38
|
+
const usage = {
|
|
39
|
+
inputTokens: {
|
|
40
|
+
total: 100,
|
|
41
|
+
noCache: 100,
|
|
42
|
+
cacheRead: undefined,
|
|
43
|
+
cacheWrite: undefined,
|
|
44
|
+
},
|
|
45
|
+
outputTokens: { total: 20, text: 20, reasoning: undefined },
|
|
46
|
+
};
|
|
47
|
+
const models = new Map(
|
|
48
|
+
["Alpha", "Beta", "Gamma"].map((name) => [
|
|
49
|
+
name,
|
|
50
|
+
new MockLanguageModelV4({
|
|
51
|
+
modelId: name,
|
|
52
|
+
doStream: async ({ prompt }) => {
|
|
53
|
+
const text = JSON.stringify(prompt);
|
|
54
|
+
inputs.set(name, [...(inputs.get(name) ?? []), text]);
|
|
55
|
+
const plan = plans.get(name)?.shift();
|
|
56
|
+
assert.ok(plan, `Unexpected ${name} step`);
|
|
57
|
+
let chunks: unknown[];
|
|
58
|
+
try {
|
|
59
|
+
chunks = plan(text);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
failures.push(error);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
stream: simulateReadableStream({
|
|
66
|
+
initialDelayInMs: null,
|
|
67
|
+
chunkDelayInMs: null,
|
|
68
|
+
chunks: [
|
|
69
|
+
...chunks,
|
|
70
|
+
{
|
|
71
|
+
type: "finish",
|
|
72
|
+
finishReason: {
|
|
73
|
+
unified: chunks.some((part: any) => part.type === "tool-call")
|
|
74
|
+
? "tool-calls"
|
|
75
|
+
: "stop",
|
|
76
|
+
raw: undefined,
|
|
77
|
+
},
|
|
78
|
+
usage,
|
|
79
|
+
},
|
|
80
|
+
] as any[],
|
|
81
|
+
}),
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
doGenerate: async ({ prompt }) => {
|
|
85
|
+
const reply = replies.get(name)?.shift();
|
|
86
|
+
assert.ok(reply, `Unexpected ${name} question or compaction`);
|
|
87
|
+
let answer: string;
|
|
88
|
+
try {
|
|
89
|
+
answer = reply(JSON.stringify(prompt));
|
|
90
|
+
} catch (error) {
|
|
91
|
+
failures.push(error);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
content: [{ type: "text", text: answer }],
|
|
96
|
+
finishReason: { unified: "stop", raw: undefined },
|
|
97
|
+
usage,
|
|
98
|
+
warnings: [],
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
]),
|
|
103
|
+
);
|
|
104
|
+
mock.module("../features/ai/model.ts", {
|
|
105
|
+
namedExports: {
|
|
106
|
+
...realModel,
|
|
107
|
+
getTextModel: async (ref: { model: string }) => ({
|
|
108
|
+
ref,
|
|
109
|
+
model: models.get(ref.model),
|
|
110
|
+
searchTools: null,
|
|
111
|
+
}),
|
|
112
|
+
compactBudget: async () => 8000,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
mock.module("../lib/desktop-notify.ts", {
|
|
116
|
+
namedExports: { desktopNotify: async () => {} },
|
|
117
|
+
});
|
|
118
|
+
const { database } = await import("../database/db.ts");
|
|
119
|
+
const { migrateDatabase } = await import("../database/migrate.ts");
|
|
120
|
+
const { botTable, threadMessageTable } = await import("../database/tables.ts");
|
|
121
|
+
const { startThread, answerThread, cancelThread } = await import(
|
|
122
|
+
"../features/bot/bot.runner.ts"
|
|
123
|
+
);
|
|
124
|
+
const { findThread, findThreadView, upsertMessage, lastSeq } = await import(
|
|
125
|
+
"../features/bot/thread.query.ts"
|
|
126
|
+
);
|
|
127
|
+
const { resumeTranscript } = await import("../features/bot/bot.run.ts");
|
|
128
|
+
const { botBrowserSession } = await import(
|
|
129
|
+
"../features/workspace/workspace.ts"
|
|
130
|
+
);
|
|
131
|
+
const { TOOL_NAMES: T } = await import("../features/ai/tools/tool-name.ts");
|
|
132
|
+
const {
|
|
133
|
+
listRoomWork,
|
|
134
|
+
listParticipantTranscript,
|
|
135
|
+
listRoomRelays,
|
|
136
|
+
sendRoomMessage,
|
|
137
|
+
} = await import("../features/bot/room.query.ts");
|
|
138
|
+
const { presence } = await import("../app/api/events/app-event.server.ts");
|
|
139
|
+
const { BOT_RUN } = await import("../config.ts");
|
|
140
|
+
// The retry after a break waits in real time; the tests only need its order
|
|
141
|
+
BOT_RUN.retryMs = 1;
|
|
142
|
+
const { eq } = await import("drizzle-orm");
|
|
143
|
+
await migrateDatabase();
|
|
144
|
+
Object.defineProperty(presence, "watching", { get: () => true });
|
|
145
|
+
for (const name of models.keys())
|
|
146
|
+
await database.insert(botTable).values({
|
|
147
|
+
name,
|
|
148
|
+
description: `${name} test worker`,
|
|
149
|
+
provider: "openai",
|
|
150
|
+
model: name,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const call = (name: string, input: unknown) => [
|
|
154
|
+
{
|
|
155
|
+
type: "tool-call",
|
|
156
|
+
toolCallId: `test-${++nextId}`,
|
|
157
|
+
toolName: name,
|
|
158
|
+
input: JSON.stringify(input),
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
const text = (value: string) => [
|
|
162
|
+
{ type: "text-start", id: "text" },
|
|
163
|
+
{ type: "text-delta", id: "text", delta: value },
|
|
164
|
+
{ type: "text-end", id: "text" },
|
|
165
|
+
];
|
|
166
|
+
const ask = (bot: string, request: string) =>
|
|
167
|
+
call(T.send_message, {
|
|
168
|
+
to: bot,
|
|
169
|
+
text: request,
|
|
170
|
+
kind: bot === "Thursday" ? "question" : "message",
|
|
171
|
+
});
|
|
172
|
+
const waitFor = async (id: string, status: string) => {
|
|
173
|
+
const until = Date.now() + 20_000;
|
|
174
|
+
while (Date.now() < until) {
|
|
175
|
+
const thread = await findThread(id);
|
|
176
|
+
if (thread?.status === status) return thread;
|
|
177
|
+
if (thread?.status === "cancelled")
|
|
178
|
+
assert.fail(`Thread was cancelled before it became ${status}`);
|
|
179
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
180
|
+
}
|
|
181
|
+
assert.fail(`Thread did not become ${status}`);
|
|
182
|
+
};
|
|
183
|
+
const rowsOf = (id: string) =>
|
|
184
|
+
database
|
|
185
|
+
.select()
|
|
186
|
+
.from(threadMessageTable)
|
|
187
|
+
.where(eq(threadMessageTable.threadId, id))
|
|
188
|
+
.orderBy(threadMessageTable.seq);
|
|
189
|
+
afterEach(() => {
|
|
190
|
+
assert.deepEqual(failures.splice(0), []);
|
|
191
|
+
});
|
|
192
|
+
after(async () => {
|
|
193
|
+
await rm(home, { recursive: true, force: true });
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("thread overview keeps old open work and the inbox retains unread endings", async () => {
|
|
197
|
+
const {
|
|
198
|
+
insertThread,
|
|
199
|
+
deleteThread,
|
|
200
|
+
listThreadOverview,
|
|
201
|
+
listInboxThreads,
|
|
202
|
+
markSeen,
|
|
203
|
+
} = await import("../features/bot/thread.query.ts");
|
|
204
|
+
const { threadRelayTable, threadTable } = await import(
|
|
205
|
+
"../database/tables.ts"
|
|
206
|
+
);
|
|
207
|
+
const { loadTools } = await import("../features/ai/load-tools.ts");
|
|
208
|
+
const { needsThreadReply } = await import("../features/bot/bot.schema.ts");
|
|
209
|
+
const ids: string[] = [];
|
|
210
|
+
try {
|
|
211
|
+
for (let index = 0; index < 14; index++) {
|
|
212
|
+
const thread = await insertThread({
|
|
213
|
+
bot: "Alpha",
|
|
214
|
+
label: `Overview ${index}`,
|
|
215
|
+
request: "Overview fixture",
|
|
216
|
+
opening: "Overview fixture",
|
|
217
|
+
});
|
|
218
|
+
ids.push(thread.id);
|
|
219
|
+
await database
|
|
220
|
+
.update(threadTable)
|
|
221
|
+
.set({
|
|
222
|
+
status:
|
|
223
|
+
index === 0
|
|
224
|
+
? "running"
|
|
225
|
+
: index === 1
|
|
226
|
+
? "waiting"
|
|
227
|
+
: index === 2
|
|
228
|
+
? "cancelled"
|
|
229
|
+
: "done",
|
|
230
|
+
seen: false,
|
|
231
|
+
outcome: index > 1 ? `Result ${index}` : null,
|
|
232
|
+
updatedAt: new Date(Date.UTC(2026, 0, 1, 0, index)),
|
|
233
|
+
})
|
|
234
|
+
.where(eq(threadTable.id, thread.id));
|
|
235
|
+
}
|
|
236
|
+
const overview = await listThreadOverview();
|
|
237
|
+
assert.equal(overview.length, 10);
|
|
238
|
+
assert.deepEqual(
|
|
239
|
+
overview.map((thread) => thread.id),
|
|
240
|
+
[ids[1], ids[0], ...ids.slice(6).reverse()],
|
|
241
|
+
);
|
|
242
|
+
const tools = await loadTools({ target: "thursday" });
|
|
243
|
+
const result = (await tools[T.thread].execute!(
|
|
244
|
+
{ action: "status", thread: null },
|
|
245
|
+
{ toolCallId: "overview", messages: [], context: {} },
|
|
246
|
+
)) as { threads: { id: string; status: string }[] };
|
|
247
|
+
assert.deepEqual(
|
|
248
|
+
result.threads.map((thread) => thread.id),
|
|
249
|
+
overview.map((thread) => thread.id),
|
|
250
|
+
);
|
|
251
|
+
const inbox = await listInboxThreads();
|
|
252
|
+
assert.equal(inbox.length, 14);
|
|
253
|
+
assert.ok(
|
|
254
|
+
inbox.some(
|
|
255
|
+
(thread) => thread.id === ids[2] && thread.status === "cancelled",
|
|
256
|
+
),
|
|
257
|
+
);
|
|
258
|
+
await markSeen([ids[2]]);
|
|
259
|
+
assert.ok(
|
|
260
|
+
!(await listInboxThreads()).some((thread) => thread.id === ids[2]),
|
|
261
|
+
);
|
|
262
|
+
// A report no call relayed holds its ending in the inbox; reading it settles that too
|
|
263
|
+
await database.insert(threadRelayTable).values({
|
|
264
|
+
key: `report:${ids[3]}:0`,
|
|
265
|
+
threadId: ids[3],
|
|
266
|
+
bot: "Alpha",
|
|
267
|
+
text: "Result 3",
|
|
268
|
+
kind: "report",
|
|
269
|
+
});
|
|
270
|
+
assert.ok(
|
|
271
|
+
(await listInboxThreads()).some((thread) => thread.id === ids[3]),
|
|
272
|
+
);
|
|
273
|
+
await markSeen([ids[3]]);
|
|
274
|
+
assert.ok(
|
|
275
|
+
!(await listInboxThreads()).some((thread) => thread.id === ids[3]),
|
|
276
|
+
);
|
|
277
|
+
assert.ok(
|
|
278
|
+
needsThreadReply({
|
|
279
|
+
status: "running",
|
|
280
|
+
room: {
|
|
281
|
+
participants: [],
|
|
282
|
+
questions: [{ id: "question", bot: "Beta", text: "Which address?" }],
|
|
283
|
+
deliveries: [],
|
|
284
|
+
relays: [],
|
|
285
|
+
},
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
288
|
+
assert.equal(
|
|
289
|
+
needsThreadReply({
|
|
290
|
+
status: "running",
|
|
291
|
+
room: { participants: [], questions: [], deliveries: [], relays: [] },
|
|
292
|
+
}),
|
|
293
|
+
false,
|
|
294
|
+
);
|
|
295
|
+
} finally {
|
|
296
|
+
for (const id of ids) await deleteThread(id);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("natural turns send asynchronously and retain participant histories", async () => {
|
|
301
|
+
plans.set("Alpha", [
|
|
302
|
+
() =>
|
|
303
|
+
call(T.bash, {
|
|
304
|
+
command: "printf ALPHA_PRIVATE",
|
|
305
|
+
description: "Read a private value.",
|
|
306
|
+
}),
|
|
307
|
+
() => ask("Beta", "Find the shared result"),
|
|
308
|
+
() => text("I can work while Beta works."),
|
|
309
|
+
(prompt) => {
|
|
310
|
+
assert.ok(prompt.includes("BETA_SHARED"));
|
|
311
|
+
assert.ok(!prompt.includes("BETA_PRIVATE"));
|
|
312
|
+
return text("Final report");
|
|
313
|
+
},
|
|
314
|
+
]);
|
|
315
|
+
plans.set("Beta", [
|
|
316
|
+
() =>
|
|
317
|
+
call(T.bash, {
|
|
318
|
+
command: "sleep 0.1; printf BETA_PRIVATE",
|
|
319
|
+
description: "Read Beta's private value.",
|
|
320
|
+
}),
|
|
321
|
+
() => text("BETA_SHARED"),
|
|
322
|
+
]);
|
|
323
|
+
const id = await startThread({
|
|
324
|
+
bot: "Alpha",
|
|
325
|
+
request: "Work together",
|
|
326
|
+
label: "Room",
|
|
327
|
+
from: "user",
|
|
328
|
+
});
|
|
329
|
+
await waitFor(id, "done");
|
|
330
|
+
assert.equal((await findThread(id))?.outcome, "Final report");
|
|
331
|
+
assert.equal(
|
|
332
|
+
(await listRoomWork(id)).filter((row) => row.bot === "Beta").length,
|
|
333
|
+
1,
|
|
334
|
+
);
|
|
335
|
+
assert.equal(inputs.get("Beta")?.length, 2);
|
|
336
|
+
const own = JSON.stringify(await listParticipantTranscript(id, "Beta"));
|
|
337
|
+
assert.ok(own.includes("BETA_PRIVATE"));
|
|
338
|
+
assert.ok(!own.includes("ALPHA_PRIVATE"));
|
|
339
|
+
assert.ok(
|
|
340
|
+
!(await rowsOf(id)).some((row) =>
|
|
341
|
+
JSON.stringify(row.content).includes('"toolName":"answer"'),
|
|
342
|
+
),
|
|
343
|
+
);
|
|
344
|
+
plans.set("Alpha", [
|
|
345
|
+
() => ask("Beta", "Follow up"),
|
|
346
|
+
() => text("Waiting for the follow-up."),
|
|
347
|
+
() => text("Follow-up report"),
|
|
348
|
+
]);
|
|
349
|
+
plans.set("Beta", [
|
|
350
|
+
(prompt) => {
|
|
351
|
+
assert.ok(prompt.includes("BETA_PRIVATE"));
|
|
352
|
+
return text("Follow-up findings");
|
|
353
|
+
},
|
|
354
|
+
]);
|
|
355
|
+
await answerThread(id, "Continue the work");
|
|
356
|
+
await waitFor(id, "done");
|
|
357
|
+
assert.equal((await findThread(id))?.outcome, "Follow-up report");
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test("a waiting participant answers a side question without an alternate model invocation", async () => {
|
|
361
|
+
plans.set("Alpha", [
|
|
362
|
+
() => ask("Beta", "Research"),
|
|
363
|
+
() => text("Waiting."),
|
|
364
|
+
(prompt) => {
|
|
365
|
+
assert.ok(prompt.includes("Which format"));
|
|
366
|
+
return text("Use a table.");
|
|
367
|
+
},
|
|
368
|
+
(prompt) => {
|
|
369
|
+
assert.ok(prompt.includes("Research complete"));
|
|
370
|
+
return text("Combined report");
|
|
371
|
+
},
|
|
372
|
+
]);
|
|
373
|
+
plans.set("Beta", [
|
|
374
|
+
() => ask("Alpha", "Which format?"),
|
|
375
|
+
() => text("Waiting for the format."),
|
|
376
|
+
(prompt) => {
|
|
377
|
+
assert.ok(prompt.includes("Use a table."));
|
|
378
|
+
return text("Research complete");
|
|
379
|
+
},
|
|
380
|
+
]);
|
|
381
|
+
const id = await startThread({
|
|
382
|
+
bot: "Alpha",
|
|
383
|
+
request: "Choose the format and research",
|
|
384
|
+
label: "Question",
|
|
385
|
+
from: "user",
|
|
386
|
+
});
|
|
387
|
+
await waitFor(id, "done");
|
|
388
|
+
assert.equal((await findThread(id))?.outcome, "Combined report");
|
|
389
|
+
assert.equal((await listRoomWork(id)).length, 3);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("silent turns remain resumable without a forced answer or retry loop", async () => {
|
|
393
|
+
plans.set("Alpha", [() => []]);
|
|
394
|
+
const id = await startThread({
|
|
395
|
+
bot: "Alpha",
|
|
396
|
+
request: "Wait quietly",
|
|
397
|
+
label: "Quiet",
|
|
398
|
+
from: "user",
|
|
399
|
+
});
|
|
400
|
+
await waitFor(id, "waiting");
|
|
401
|
+
assert.match((await findThread(id))?.outcome ?? "", /idle/);
|
|
402
|
+
plans.set("Alpha", [() => text("Resumed normally")]);
|
|
403
|
+
await answerThread(id, "Continue");
|
|
404
|
+
await waitFor(id, "done");
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test("a question pauses the bot that asked, and words to that bot answer it", async () => {
|
|
408
|
+
plans.set("Alpha", [
|
|
409
|
+
() => ask("Beta", "Prepare the work"),
|
|
410
|
+
() => text("Waiting for Beta."),
|
|
411
|
+
() => text("Ready report"),
|
|
412
|
+
]);
|
|
413
|
+
plans.set("Beta", [
|
|
414
|
+
() =>
|
|
415
|
+
call(T.send_message, {
|
|
416
|
+
to: "Thursday",
|
|
417
|
+
text: "Choose a destination.",
|
|
418
|
+
kind: "question",
|
|
419
|
+
options: ["Destination one", "Destination two"],
|
|
420
|
+
}),
|
|
421
|
+
(prompt) => {
|
|
422
|
+
assert.ok(prompt.includes("Destination one"));
|
|
423
|
+
assert.ok(prompt.includes("answers your question to the user"));
|
|
424
|
+
return text("Prepared");
|
|
425
|
+
},
|
|
426
|
+
]);
|
|
427
|
+
const betaCalls = inputs.get("Beta")?.length ?? 0;
|
|
428
|
+
const id = await startThread({
|
|
429
|
+
bot: "Alpha",
|
|
430
|
+
request: "Prepare",
|
|
431
|
+
label: "User input",
|
|
432
|
+
from: "user",
|
|
433
|
+
});
|
|
434
|
+
const stopped = await waitFor(id, "waiting");
|
|
435
|
+
assert.equal(stopped.pending?.bot, "Beta");
|
|
436
|
+
// The question ended Beta's turn: no second step before the answer
|
|
437
|
+
assert.equal((inputs.get("Beta")?.length ?? 0) - betaCalls, 1);
|
|
438
|
+
assert.deepEqual((await findThreadView(id))?.room?.questions[0].options, [
|
|
439
|
+
"Destination one",
|
|
440
|
+
"Destination two",
|
|
441
|
+
]);
|
|
442
|
+
// A spoken answer names the bot, not the question
|
|
443
|
+
const told = await answerThread(id, "Destination one", "thursday", "Beta");
|
|
444
|
+
assert.equal(told?.answered?.bot, "Beta");
|
|
445
|
+
await waitFor(id, "done");
|
|
446
|
+
assert.equal((await findThread(id))?.outcome, "Ready report");
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test("Continue offered as a choice answers the question rather than resuming a stop", async () => {
|
|
450
|
+
plans.set("Alpha", [
|
|
451
|
+
() =>
|
|
452
|
+
call(T.send_message, {
|
|
453
|
+
to: "Thursday",
|
|
454
|
+
text: "Keep going with the long version?",
|
|
455
|
+
kind: "question",
|
|
456
|
+
options: ["Continue", "Stop here"],
|
|
457
|
+
}),
|
|
458
|
+
(prompt) => {
|
|
459
|
+
assert.ok(prompt.includes("answers your question to the user"));
|
|
460
|
+
return text("Went on with the long version");
|
|
461
|
+
},
|
|
462
|
+
]);
|
|
463
|
+
const id = await startThread({
|
|
464
|
+
bot: "Alpha",
|
|
465
|
+
request: "Choose",
|
|
466
|
+
label: "Continue as a choice",
|
|
467
|
+
from: "user",
|
|
468
|
+
});
|
|
469
|
+
const asking = await waitFor(id, "waiting");
|
|
470
|
+
assert.ok(asking.pending?.messageId);
|
|
471
|
+
const told = await answerThread(id, "Continue");
|
|
472
|
+
assert.equal(told?.answered?.bot, "Alpha");
|
|
473
|
+
assert.equal(
|
|
474
|
+
(await waitFor(id, "done")).outcome,
|
|
475
|
+
"Went on with the long version",
|
|
476
|
+
);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
test("resume repairs only missing local tool results and preserves real ones", () => {
|
|
480
|
+
const transcript: any[] = [
|
|
481
|
+
{
|
|
482
|
+
role: "assistant",
|
|
483
|
+
content: [
|
|
484
|
+
{ type: "tool-call", toolCallId: "one", toolName: T.bash, input: {} },
|
|
485
|
+
{ type: "tool-call", toolCallId: "two", toolName: T.bash, input: {} },
|
|
486
|
+
],
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
role: "tool",
|
|
490
|
+
content: [
|
|
491
|
+
{
|
|
492
|
+
type: "tool-result",
|
|
493
|
+
toolCallId: "one",
|
|
494
|
+
toolName: T.bash,
|
|
495
|
+
output: { type: "text", value: "Recorded result" },
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
},
|
|
499
|
+
];
|
|
500
|
+
const restored = resumeTranscript(transcript);
|
|
501
|
+
const output = JSON.stringify(restored);
|
|
502
|
+
assert.equal(output.match(/Recorded result/g)?.length, 1);
|
|
503
|
+
assert.equal(output.match(/No result:/g)?.length, 1);
|
|
504
|
+
assert.deepEqual(resumeTranscript(restored), restored);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test("same-bot requests serialize and committed sends deduplicate", async () => {
|
|
508
|
+
const { insertThread } = await import("../features/bot/thread.query.ts");
|
|
509
|
+
const { claimRoomWork, finishRoomWork, cancelRoom, consumeRoomInbox } =
|
|
510
|
+
await import("../features/bot/room.query.ts");
|
|
511
|
+
const thread = await insertThread({
|
|
512
|
+
bot: "Alpha",
|
|
513
|
+
request: "Queue",
|
|
514
|
+
label: "Queue",
|
|
515
|
+
opening: "Queue",
|
|
516
|
+
});
|
|
517
|
+
const root = (await claimRoomWork(thread.id))!;
|
|
518
|
+
const receipt = await sendRoomMessage(root, {
|
|
519
|
+
id: "same-send",
|
|
520
|
+
to: "Beta",
|
|
521
|
+
text: "One",
|
|
522
|
+
});
|
|
523
|
+
assert.deepEqual(
|
|
524
|
+
await sendRoomMessage(root, { id: "same-send", to: "Beta", text: "One" }),
|
|
525
|
+
receipt,
|
|
526
|
+
);
|
|
527
|
+
await sendRoomMessage(root, { id: "second-send", to: "Beta", text: "Two" });
|
|
528
|
+
const beta = (await claimRoomWork(thread.id))!;
|
|
529
|
+
assert.equal(beta.bot, "Beta");
|
|
530
|
+
assert.equal(await claimRoomWork(thread.id), null);
|
|
531
|
+
await consumeRoomInbox(beta);
|
|
532
|
+
await finishRoomWork(beta, "First result");
|
|
533
|
+
const second = (await claimRoomWork(thread.id))!;
|
|
534
|
+
assert.equal(second.bot, "Beta");
|
|
535
|
+
assert.notEqual(beta.id, second.id);
|
|
536
|
+
await cancelRoom(thread.id);
|
|
537
|
+
await assert.rejects(
|
|
538
|
+
sendRoomMessage(root, { id: "stale", to: "Gamma", text: "Too late" }),
|
|
539
|
+
/no longer running/,
|
|
540
|
+
);
|
|
541
|
+
assert.equal((await listRoomWork(thread.id)).length, 3);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test("Step in is durable and reaches a running B before its next step", async () => {
|
|
545
|
+
plans.set("Alpha", [
|
|
546
|
+
() => ask("Beta", "Work for me"),
|
|
547
|
+
() => text("Waiting."),
|
|
548
|
+
() => text("Owner report"),
|
|
549
|
+
]);
|
|
550
|
+
plans.set("Beta", [
|
|
551
|
+
() =>
|
|
552
|
+
call(T.bash, {
|
|
553
|
+
command: "sleep 0.2; printf BEFORE_INTERJECTION",
|
|
554
|
+
description: "Do current work.",
|
|
555
|
+
}),
|
|
556
|
+
(prompt) => {
|
|
557
|
+
assert.ok(prompt.includes("Use the revised destination"));
|
|
558
|
+
return text("Revised work");
|
|
559
|
+
},
|
|
560
|
+
]);
|
|
561
|
+
const id = await startThread({
|
|
562
|
+
bot: "Alpha",
|
|
563
|
+
request: "Work",
|
|
564
|
+
label: "Step in",
|
|
565
|
+
from: "user",
|
|
566
|
+
});
|
|
567
|
+
const until = Date.now() + 2000;
|
|
568
|
+
while (
|
|
569
|
+
!(await rowsOf(id)).some((row) =>
|
|
570
|
+
JSON.stringify(row.content).includes("BEFORE_INTERJECTION"),
|
|
571
|
+
)
|
|
572
|
+
) {
|
|
573
|
+
assert.ok(Date.now() < until);
|
|
574
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
575
|
+
}
|
|
576
|
+
await answerThread(id, "Use the revised destination", "user", "Beta");
|
|
577
|
+
await waitFor(id, "done");
|
|
578
|
+
const view = (await findThreadView(id))!;
|
|
579
|
+
assert.equal(
|
|
580
|
+
view.room?.deliveries.find(
|
|
581
|
+
(row) => row.text === "Use the revised destination",
|
|
582
|
+
)?.delivered,
|
|
583
|
+
true,
|
|
584
|
+
);
|
|
585
|
+
assert.ok(
|
|
586
|
+
view.lines.some((line) => line.kind === "user" && line.to === "Beta"),
|
|
587
|
+
);
|
|
588
|
+
assert.equal(
|
|
589
|
+
view.room?.relays.filter((row) => row.kind === "report").length,
|
|
590
|
+
1,
|
|
591
|
+
);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
test("cancellation drains tools and preserves the participant on follow-up", async () => {
|
|
595
|
+
plans.set("Alpha", [() => ask("Beta", "Long work"), () => text("Waiting.")]);
|
|
596
|
+
plans.set("Beta", [
|
|
597
|
+
() =>
|
|
598
|
+
call(T.bash, {
|
|
599
|
+
command: "sleep 5; printf TOO_LATE",
|
|
600
|
+
description: "Wait in a cancellable process.",
|
|
601
|
+
}),
|
|
602
|
+
]);
|
|
603
|
+
const id = await startThread({
|
|
604
|
+
bot: "Alpha",
|
|
605
|
+
request: "Long work",
|
|
606
|
+
label: "Cancel",
|
|
607
|
+
from: "user",
|
|
608
|
+
});
|
|
609
|
+
const until = Date.now() + 2000;
|
|
610
|
+
while (
|
|
611
|
+
!(await rowsOf(id)).some((row) =>
|
|
612
|
+
JSON.stringify(row.content).includes("TOO_LATE"),
|
|
613
|
+
)
|
|
614
|
+
) {
|
|
615
|
+
assert.ok(Date.now() < until);
|
|
616
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
617
|
+
}
|
|
618
|
+
await cancelThread(id);
|
|
619
|
+
const rows = await rowsOf(id);
|
|
620
|
+
assert.equal((await findThread(id))?.status, "cancelled");
|
|
621
|
+
assert.ok(
|
|
622
|
+
(await listRoomWork(id)).every(
|
|
623
|
+
(row) => row.state === "done" || row.state === "cancelled",
|
|
624
|
+
),
|
|
625
|
+
);
|
|
626
|
+
plans.set("Alpha", [
|
|
627
|
+
() => ask("Beta", "Pick up your saved work"),
|
|
628
|
+
() => text("Waiting again."),
|
|
629
|
+
() => text("Recovered report"),
|
|
630
|
+
]);
|
|
631
|
+
plans.set("Beta", [
|
|
632
|
+
(prompt) => {
|
|
633
|
+
assert.ok(prompt.includes("TOO_LATE"));
|
|
634
|
+
assert.ok(prompt.includes("Aborted"));
|
|
635
|
+
return text("Checked current state and recovered");
|
|
636
|
+
},
|
|
637
|
+
]);
|
|
638
|
+
await answerThread(id, "Continue after cancellation");
|
|
639
|
+
await waitFor(id, "done");
|
|
640
|
+
assert.ok((await rowsOf(id)).length > rows.length);
|
|
641
|
+
assert.equal(botBrowserSession(id, "Beta"), botBrowserSession(id, " beta "));
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
test("compaction and an arriving message preserve the same inbox on resume", async () => {
|
|
645
|
+
plans.set("Alpha", [() => text("Before compaction")]);
|
|
646
|
+
const id = await startThread({
|
|
647
|
+
bot: "Alpha",
|
|
648
|
+
request: "Compact",
|
|
649
|
+
label: "Compaction",
|
|
650
|
+
from: "user",
|
|
651
|
+
});
|
|
652
|
+
await waitFor(id, "done");
|
|
653
|
+
const work = (await listRoomWork(id))[0];
|
|
654
|
+
await upsertMessage(id, (await lastSeq(id)) + 1, {
|
|
655
|
+
bot: "Alpha",
|
|
656
|
+
parent: work.id,
|
|
657
|
+
role: "assistant",
|
|
658
|
+
content: "old material ".repeat(5000),
|
|
659
|
+
});
|
|
660
|
+
replies.set("Alpha", [() => "Preserved private summary."]);
|
|
661
|
+
plans.set("Alpha", [
|
|
662
|
+
(prompt) => {
|
|
663
|
+
assert.ok(prompt.includes("Preserved private summary"));
|
|
664
|
+
return text("After compaction");
|
|
665
|
+
},
|
|
666
|
+
]);
|
|
667
|
+
await answerThread(id, "Continue the same work");
|
|
668
|
+
await waitFor(id, "done");
|
|
669
|
+
const history = JSON.stringify(await listParticipantTranscript(id, "Alpha"));
|
|
670
|
+
assert.ok(history.includes("Preserved private summary"));
|
|
671
|
+
assert.ok(history.includes("After compaction"));
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
test("interrupted provider operations and incomplete arguments are not invented as local tool results", () => {
|
|
675
|
+
const restored = resumeTranscript([
|
|
676
|
+
{
|
|
677
|
+
role: "assistant",
|
|
678
|
+
content: [
|
|
679
|
+
{
|
|
680
|
+
type: "tool-call",
|
|
681
|
+
toolCallId: "remote",
|
|
682
|
+
toolName: T.web_search,
|
|
683
|
+
input: {},
|
|
684
|
+
providerExecuted: true,
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
type: "tool-call",
|
|
688
|
+
toolCallId: "partial",
|
|
689
|
+
toolName: T.bash,
|
|
690
|
+
input: '{"command":',
|
|
691
|
+
},
|
|
692
|
+
],
|
|
693
|
+
},
|
|
694
|
+
]);
|
|
695
|
+
assert.ok(!restored.some((message) => message.role === "tool"));
|
|
696
|
+
const projection = JSON.stringify(restored);
|
|
697
|
+
assert.ok(projection.includes("remote execution state is unknown"));
|
|
698
|
+
assert.ok(projection.includes("argument stream was interrupted"));
|
|
699
|
+
assert.deepEqual(resumeTranscript(restored), restored);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
test("B keeps its history when C contacts it later in the same room", async () => {
|
|
703
|
+
plans.set("Alpha", [
|
|
704
|
+
() => ask("Beta", "First request"),
|
|
705
|
+
() => text("Waiting for Beta"),
|
|
706
|
+
() => ask("Gamma", "Ask Beta for the next part"),
|
|
707
|
+
() => text("Waiting for Gamma"),
|
|
708
|
+
(prompt) => {
|
|
709
|
+
assert.ok(prompt.includes("Gamma shared result"));
|
|
710
|
+
assert.ok(!prompt.includes("BETA_DESK_SECRET"));
|
|
711
|
+
return text("Room complete");
|
|
712
|
+
},
|
|
713
|
+
]);
|
|
714
|
+
plans.set("Beta", [
|
|
715
|
+
() =>
|
|
716
|
+
call(T.bash, {
|
|
717
|
+
command: "printf BETA_DESK_SECRET",
|
|
718
|
+
description: "Read private work.",
|
|
719
|
+
}),
|
|
720
|
+
() => text("Beta first result"),
|
|
721
|
+
(prompt) => {
|
|
722
|
+
assert.ok(prompt.includes("BETA_DESK_SECRET"));
|
|
723
|
+
assert.ok(prompt.includes("Gamma"));
|
|
724
|
+
return text("Beta second result");
|
|
725
|
+
},
|
|
726
|
+
]);
|
|
727
|
+
plans.set("Gamma", [
|
|
728
|
+
() => ask("Beta", "Next part"),
|
|
729
|
+
() => text("Waiting for Beta"),
|
|
730
|
+
(prompt) => {
|
|
731
|
+
assert.ok(prompt.includes("Beta second result"));
|
|
732
|
+
assert.ok(!prompt.includes("BETA_DESK_SECRET"));
|
|
733
|
+
return text("Gamma shared result");
|
|
734
|
+
},
|
|
735
|
+
]);
|
|
736
|
+
const id = await startThread({
|
|
737
|
+
bot: "Alpha",
|
|
738
|
+
request: "Keep the same team",
|
|
739
|
+
label: "Team",
|
|
740
|
+
from: "user",
|
|
741
|
+
});
|
|
742
|
+
await waitFor(id, "done");
|
|
743
|
+
assert.equal(
|
|
744
|
+
(await listRoomWork(id)).filter((work) => work.bot === "Beta").length,
|
|
745
|
+
2,
|
|
746
|
+
);
|
|
747
|
+
assert.equal((await findThreadView(id))?.room?.participants.length, 3);
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
test("a direct follow-up to an idle B still returns the room's final report through A", async () => {
|
|
751
|
+
plans.set("Alpha", [
|
|
752
|
+
() => ask("Beta", "First work"),
|
|
753
|
+
() => text("Waiting"),
|
|
754
|
+
() => text("First report"),
|
|
755
|
+
]);
|
|
756
|
+
plans.set("Beta", [() => text("First work complete")]);
|
|
757
|
+
const id = await startThread({
|
|
758
|
+
bot: "Alpha",
|
|
759
|
+
request: "First work",
|
|
760
|
+
label: "Direct follow-up",
|
|
761
|
+
from: "user",
|
|
762
|
+
});
|
|
763
|
+
await waitFor(id, "done");
|
|
764
|
+
plans.set("Beta", [
|
|
765
|
+
(prompt) => {
|
|
766
|
+
assert.ok(prompt.includes("First work complete"));
|
|
767
|
+
assert.ok(prompt.includes("Change the detail"));
|
|
768
|
+
return text("The detail is changed");
|
|
769
|
+
},
|
|
770
|
+
]);
|
|
771
|
+
plans.set("Alpha", [
|
|
772
|
+
(prompt) => {
|
|
773
|
+
assert.ok(prompt.includes("The detail is changed"));
|
|
774
|
+
return text("Updated coordinator report");
|
|
775
|
+
},
|
|
776
|
+
]);
|
|
777
|
+
await answerThread(id, "Change the detail", "user", "Beta");
|
|
778
|
+
await waitFor(id, "done");
|
|
779
|
+
assert.equal((await findThread(id))?.outcome, "Updated coordinator report");
|
|
780
|
+
assert.ok(
|
|
781
|
+
(await listRoomRelays())
|
|
782
|
+
.filter((relay) => relay.threadId === id && relay.kind === "report")
|
|
783
|
+
.every((relay) => relay.bot === "Alpha"),
|
|
784
|
+
);
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
test("simultaneous questions keep their own reply routes", async () => {
|
|
788
|
+
plans.set("Alpha", [
|
|
789
|
+
() => [
|
|
790
|
+
...ask("Beta", "Check first decision"),
|
|
791
|
+
...ask("Gamma", "Check second decision"),
|
|
792
|
+
],
|
|
793
|
+
() => text("Waiting for decisions"),
|
|
794
|
+
() => text("First answer received"),
|
|
795
|
+
() => text("Both decisions applied"),
|
|
796
|
+
]);
|
|
797
|
+
plans.set("Beta", [
|
|
798
|
+
() => ask("Thursday", "Which destination?"),
|
|
799
|
+
(prompt) => {
|
|
800
|
+
assert.ok(prompt.includes("Destination One"));
|
|
801
|
+
assert.ok(!prompt.includes("Format Two"));
|
|
802
|
+
return text("Destination applied");
|
|
803
|
+
},
|
|
804
|
+
]);
|
|
805
|
+
plans.set("Gamma", [
|
|
806
|
+
() => ask("Thursday", "Which format?"),
|
|
807
|
+
(prompt) => {
|
|
808
|
+
assert.ok(prompt.includes("Format Two"));
|
|
809
|
+
assert.ok(!prompt.includes("Destination One"));
|
|
810
|
+
return text("Format applied");
|
|
811
|
+
},
|
|
812
|
+
]);
|
|
813
|
+
const id = await startThread({
|
|
814
|
+
bot: "Alpha",
|
|
815
|
+
request: "Ask independently",
|
|
816
|
+
label: "Questions",
|
|
817
|
+
from: "user",
|
|
818
|
+
});
|
|
819
|
+
await waitFor(id, "waiting");
|
|
820
|
+
const questions = (await findThreadView(id))!.room!.questions;
|
|
821
|
+
assert.equal(questions.length, 2);
|
|
822
|
+
// Two open and none named: the call's tool answers with both, nothing delivered
|
|
823
|
+
const { loadTools } = await import("../features/ai/load-tools.ts");
|
|
824
|
+
const tools = await loadTools({ target: "thursday" });
|
|
825
|
+
const unclear = await tools[T.thread].execute!(
|
|
826
|
+
{ action: "answer", thread: id, answer: "Unclear" },
|
|
827
|
+
{ toolCallId: "unclear", messages: [], context: {} },
|
|
828
|
+
);
|
|
829
|
+
assert.match(String(unclear), /Several questions are waiting/);
|
|
830
|
+
assert.equal((await findThreadView(id))!.room!.questions.length, 2);
|
|
831
|
+
await answerThread(
|
|
832
|
+
id,
|
|
833
|
+
"Destination One",
|
|
834
|
+
"user",
|
|
835
|
+
"Beta",
|
|
836
|
+
questions.find((q) => q.bot === "Beta")!.id,
|
|
837
|
+
);
|
|
838
|
+
await waitFor(id, "waiting");
|
|
839
|
+
assert.equal((await findThreadView(id))!.room!.questions.length, 1);
|
|
840
|
+
await answerThread(
|
|
841
|
+
id,
|
|
842
|
+
"Format Two",
|
|
843
|
+
"thursday",
|
|
844
|
+
"Gamma",
|
|
845
|
+
questions.find((q) => q.bot === "Gamma")!.id,
|
|
846
|
+
);
|
|
847
|
+
await waitFor(id, "done");
|
|
848
|
+
assert.equal((await findThread(id))?.outcome, "Both decisions applied");
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
test("a bot waiting on the user holds other messages until the answer", async () => {
|
|
852
|
+
plans.set("Alpha", [
|
|
853
|
+
() => ask("Beta", "Prepare the draft"),
|
|
854
|
+
() => text("Waiting for Beta."),
|
|
855
|
+
(prompt) => {
|
|
856
|
+
assert.ok(prompt.includes("Draft ready"));
|
|
857
|
+
return text("Final report");
|
|
858
|
+
},
|
|
859
|
+
]);
|
|
860
|
+
plans.set("Beta", [
|
|
861
|
+
() => [
|
|
862
|
+
...ask("Gamma", "Check the numbers"),
|
|
863
|
+
...ask("Thursday", "Which tone?"),
|
|
864
|
+
],
|
|
865
|
+
(prompt) => {
|
|
866
|
+
assert.ok(prompt.includes("Warm"));
|
|
867
|
+
assert.ok(prompt.includes("Numbers are 42"));
|
|
868
|
+
return text("Draft ready");
|
|
869
|
+
},
|
|
870
|
+
]);
|
|
871
|
+
plans.set("Gamma", [() => text("Numbers are 42")]);
|
|
872
|
+
const betaCalls = inputs.get("Beta")?.length ?? 0;
|
|
873
|
+
const id = await startThread({
|
|
874
|
+
bot: "Alpha",
|
|
875
|
+
request: "Draft with checked numbers",
|
|
876
|
+
label: "Held inbox",
|
|
877
|
+
from: "user",
|
|
878
|
+
});
|
|
879
|
+
await waitFor(id, "waiting");
|
|
880
|
+
// Gamma's return arrived while Beta waited on the user; Beta has not run for it
|
|
881
|
+
assert.equal((inputs.get("Beta")?.length ?? 0) - betaCalls, 1);
|
|
882
|
+
const beta = (await listRoomWork(id)).filter((row) => row.bot === "Beta");
|
|
883
|
+
assert.ok(
|
|
884
|
+
beta.every((row) => row.state !== "queued" && row.state !== "running"),
|
|
885
|
+
);
|
|
886
|
+
// No job, no bot, no question named: the call's tool gives it to the only open question
|
|
887
|
+
const { loadTools } = await import("../features/ai/load-tools.ts");
|
|
888
|
+
const tools = await loadTools({ target: "thursday" });
|
|
889
|
+
const told = (await tools[T.thread].execute!(
|
|
890
|
+
{ action: "answer", thread: null, answer: "Warm" },
|
|
891
|
+
{ toolCallId: "warm", messages: [], context: {} },
|
|
892
|
+
)) as { answered?: { bot: string } };
|
|
893
|
+
assert.equal(told.answered?.bot, "Beta");
|
|
894
|
+
await waitFor(id, "done");
|
|
895
|
+
assert.equal((inputs.get("Beta")?.length ?? 0) - betaCalls, 2);
|
|
896
|
+
assert.equal((await findThread(id))?.outcome, "Final report");
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
test("a consumed inbox survives a crash before model execution and restart waits for a person", async () => {
|
|
900
|
+
const { insertThread, deleteThread } = await import(
|
|
901
|
+
"../features/bot/thread.query.ts"
|
|
902
|
+
);
|
|
903
|
+
const { claimRoomWork, consumeRoomInbox } = await import(
|
|
904
|
+
"../features/bot/room.query.ts"
|
|
905
|
+
);
|
|
906
|
+
const { sweepThreads, resumeStoppedThreads } = await import(
|
|
907
|
+
"../features/bot/bot.runner.ts"
|
|
908
|
+
);
|
|
909
|
+
const thread = await insertThread({
|
|
910
|
+
bot: "Alpha",
|
|
911
|
+
request: "Crash window",
|
|
912
|
+
label: "Crash window",
|
|
913
|
+
opening: "Crash opening",
|
|
914
|
+
});
|
|
915
|
+
const root = (await claimRoomWork(thread.id))!;
|
|
916
|
+
await sendRoomMessage(root, {
|
|
917
|
+
id: "crash-message",
|
|
918
|
+
to: "Beta",
|
|
919
|
+
text: "Durable incoming message",
|
|
920
|
+
});
|
|
921
|
+
const beta = (await claimRoomWork(thread.id))!;
|
|
922
|
+
await consumeRoomInbox(beta);
|
|
923
|
+
await sweepThreads();
|
|
924
|
+
await resumeStoppedThreads();
|
|
925
|
+
assert.equal((await findThread(thread.id))?.status, "waiting");
|
|
926
|
+
assert.ok(!(await findThread(thread.id))?.pending?.auto);
|
|
927
|
+
plans.set("Beta", [
|
|
928
|
+
(prompt) => {
|
|
929
|
+
assert.equal(prompt.split("Durable incoming message").length - 1, 1);
|
|
930
|
+
return text("Recovered delivery");
|
|
931
|
+
},
|
|
932
|
+
]);
|
|
933
|
+
plans.set("Alpha", [
|
|
934
|
+
() => text("Awaiting recovery"),
|
|
935
|
+
(prompt) => {
|
|
936
|
+
assert.ok(prompt.includes("Recovered delivery"));
|
|
937
|
+
return text("Crash recovered");
|
|
938
|
+
},
|
|
939
|
+
]);
|
|
940
|
+
await answerThread(thread.id, "Continue");
|
|
941
|
+
await waitFor(thread.id, "done");
|
|
942
|
+
await deleteThread(thread.id);
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
test("presence pauses automatically but a manually stopped room stays stopped", async () => {
|
|
946
|
+
const { pauseThreads, resumeStoppedThreads } = await import(
|
|
947
|
+
"../features/bot/bot.runner.ts"
|
|
948
|
+
);
|
|
949
|
+
plans.set("Alpha", [
|
|
950
|
+
() =>
|
|
951
|
+
call(T.bash, {
|
|
952
|
+
command: "sleep 5; printf PRESENCE_BOUNDARY",
|
|
953
|
+
description: "Wait for the presence boundary.",
|
|
954
|
+
}),
|
|
955
|
+
]);
|
|
956
|
+
const id = await startThread({
|
|
957
|
+
bot: "Alpha",
|
|
958
|
+
request: "Presence",
|
|
959
|
+
label: "Presence",
|
|
960
|
+
from: "user",
|
|
961
|
+
});
|
|
962
|
+
const until = Date.now() + 2000;
|
|
963
|
+
while (
|
|
964
|
+
!(await rowsOf(id)).some((row) =>
|
|
965
|
+
JSON.stringify(row.content).includes("PRESENCE_BOUNDARY"),
|
|
966
|
+
)
|
|
967
|
+
) {
|
|
968
|
+
assert.ok(Date.now() < until);
|
|
969
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
970
|
+
}
|
|
971
|
+
await pauseThreads("The browser closed.", true);
|
|
972
|
+
assert.equal((await findThread(id))?.pending?.auto, true);
|
|
973
|
+
plans.set("Alpha", [
|
|
974
|
+
(prompt) => {
|
|
975
|
+
assert.ok(prompt.includes("PRESENCE_BOUNDARY"));
|
|
976
|
+
return text("Presence recovered");
|
|
977
|
+
},
|
|
978
|
+
]);
|
|
979
|
+
await resumeStoppedThreads();
|
|
980
|
+
await waitFor(id, "done");
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
test("a late inbox message queues another turn atomically with completion", async () => {
|
|
984
|
+
const { insertThread, deleteThread } = await import(
|
|
985
|
+
"../features/bot/thread.query.ts"
|
|
986
|
+
);
|
|
987
|
+
const { claimRoomWork, consumeRoomInbox, finishRoomWork, tellRoom } =
|
|
988
|
+
await import("../features/bot/room.query.ts");
|
|
989
|
+
const thread = await insertThread({
|
|
990
|
+
bot: "Alpha",
|
|
991
|
+
request: "Late input",
|
|
992
|
+
label: "Late input",
|
|
993
|
+
opening: "Opening",
|
|
994
|
+
});
|
|
995
|
+
const run = (await claimRoomWork(thread.id))!;
|
|
996
|
+
await consumeRoomInbox(run);
|
|
997
|
+
await tellRoom(thread.id, "Arrived at completion", "The user");
|
|
998
|
+
await finishRoomWork(run, "Earlier result");
|
|
999
|
+
assert.equal((await findThread(thread.id))?.status, "running");
|
|
1000
|
+
const next = (await claimRoomWork(thread.id))!;
|
|
1001
|
+
assert.equal(next.id, run.id);
|
|
1002
|
+
assert.deepEqual(await consumeRoomInbox(next), [
|
|
1003
|
+
"The user, on screen: Arrived at completion",
|
|
1004
|
+
]);
|
|
1005
|
+
await finishRoomWork(next, "Result after the message");
|
|
1006
|
+
assert.equal((await findThread(thread.id))?.status, "done");
|
|
1007
|
+
await deleteThread(thread.id);
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
test("provider adapters serialize interrupted tool history as complete exchanges", async () => {
|
|
1011
|
+
const { generateText, tool } = await import("ai");
|
|
1012
|
+
const { z } = await import("zod");
|
|
1013
|
+
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
1014
|
+
const { createAnthropic } = await import("@ai-sdk/anthropic");
|
|
1015
|
+
const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
|
|
1016
|
+
const messages = resumeTranscript([
|
|
1017
|
+
{ role: "user", content: "Inspect the existing file." },
|
|
1018
|
+
{
|
|
1019
|
+
role: "assistant",
|
|
1020
|
+
content: [
|
|
1021
|
+
{
|
|
1022
|
+
type: "tool-call",
|
|
1023
|
+
toolCallId: "interrupted-call",
|
|
1024
|
+
toolName: T.bash,
|
|
1025
|
+
input: { command: "cat saved.txt" },
|
|
1026
|
+
providerOptions: { google: { thoughtSignature: "saved-signature" } },
|
|
1027
|
+
},
|
|
1028
|
+
],
|
|
1029
|
+
},
|
|
1030
|
+
{ role: "user", content: "Continue after the interruption." },
|
|
1031
|
+
]);
|
|
1032
|
+
let captured: any;
|
|
1033
|
+
const fetch = async (_url: unknown, init: any) => {
|
|
1034
|
+
captured = JSON.parse(init.body);
|
|
1035
|
+
throw new Error("WIRE_CAPTURED");
|
|
1036
|
+
};
|
|
1037
|
+
const factories = [
|
|
1038
|
+
() => createOpenAI({ apiKey: "test-key", fetch }).responses("test-model"),
|
|
1039
|
+
() => createAnthropic({ apiKey: "test-key", fetch })("test-model"),
|
|
1040
|
+
() => createGoogleGenerativeAI({ apiKey: "test-key", fetch })("test-model"),
|
|
1041
|
+
];
|
|
1042
|
+
for (const [index, factory] of factories.entries()) {
|
|
1043
|
+
captured = undefined;
|
|
1044
|
+
await assert.rejects(
|
|
1045
|
+
generateText({
|
|
1046
|
+
model: factory(),
|
|
1047
|
+
messages,
|
|
1048
|
+
maxRetries: 0,
|
|
1049
|
+
tools: {
|
|
1050
|
+
[T.bash]: tool({ inputSchema: z.object({ command: z.string() }) }),
|
|
1051
|
+
},
|
|
1052
|
+
}),
|
|
1053
|
+
/WIRE_CAPTURED/,
|
|
1054
|
+
);
|
|
1055
|
+
assert.ok(captured, "The adapter reaches the HTTP boundary");
|
|
1056
|
+
const body = JSON.stringify(captured);
|
|
1057
|
+
assert.ok(body.includes("No result: execution was interrupted"));
|
|
1058
|
+
if (index === 0) assert.ok(body.includes("function_call_output"));
|
|
1059
|
+
if (index === 1) assert.ok(body.includes("tool_result"));
|
|
1060
|
+
if (index === 2) {
|
|
1061
|
+
assert.ok(body.includes("functionResponse"));
|
|
1062
|
+
assert.ok(body.includes("saved-signature"));
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
test("the assembled participant prompt names its return route and exposes asynchronous collaboration", async () => {
|
|
1068
|
+
const { loadBotPrompt } = await import(
|
|
1069
|
+
"../features/ai/prompts/bot.prompt.ts"
|
|
1070
|
+
);
|
|
1071
|
+
const { sendMessageSpec } = await import("../features/ai/tools/bot.tool.ts");
|
|
1072
|
+
const prompt = await loadBotPrompt("Beta", "Use precise findings.", {
|
|
1073
|
+
owner: "Alpha",
|
|
1074
|
+
caller: "Gamma",
|
|
1075
|
+
messageId: "incoming-message",
|
|
1076
|
+
});
|
|
1077
|
+
assert.ok(prompt.text.includes("Gamma → Beta"));
|
|
1078
|
+
assert.ok(prompt.text.includes("Your final text goes back to Gamma"));
|
|
1079
|
+
assert.ok(
|
|
1080
|
+
prompt.text.includes("End your turn when you have nothing more to do now"),
|
|
1081
|
+
);
|
|
1082
|
+
if (process.env.THURSDAY_TEST_SHOW_PROMPT)
|
|
1083
|
+
console.log(prompt.text, "\nTool:", sendMessageSpec.description);
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
test("a committed message recovers its real receipt after the tool result is lost", async () => {
|
|
1087
|
+
const { insertThread, deleteThread } = await import(
|
|
1088
|
+
"../features/bot/thread.query.ts"
|
|
1089
|
+
);
|
|
1090
|
+
const { claimRoomWork, listRoomReceipts } = await import(
|
|
1091
|
+
"../features/bot/room.query.ts"
|
|
1092
|
+
);
|
|
1093
|
+
const thread = await insertThread({
|
|
1094
|
+
bot: "Alpha",
|
|
1095
|
+
request: "Receipt",
|
|
1096
|
+
label: "Receipt",
|
|
1097
|
+
opening: "Opening",
|
|
1098
|
+
});
|
|
1099
|
+
const run = (await claimRoomWork(thread.id))!;
|
|
1100
|
+
const receipt = await sendRoomMessage(run, {
|
|
1101
|
+
id: "lost-receipt",
|
|
1102
|
+
to: "Beta",
|
|
1103
|
+
text: "Already committed",
|
|
1104
|
+
});
|
|
1105
|
+
const history: import("ai").ModelMessage[] = [
|
|
1106
|
+
{
|
|
1107
|
+
role: "assistant",
|
|
1108
|
+
content: [
|
|
1109
|
+
{
|
|
1110
|
+
type: "tool-call",
|
|
1111
|
+
toolCallId: "lost-receipt",
|
|
1112
|
+
toolName: T.send_message,
|
|
1113
|
+
input: { to: "Beta", text: "Already committed" },
|
|
1114
|
+
},
|
|
1115
|
+
],
|
|
1116
|
+
},
|
|
1117
|
+
];
|
|
1118
|
+
const restored = resumeTranscript(
|
|
1119
|
+
history,
|
|
1120
|
+
await listRoomReceipts(thread.id, "Alpha", history),
|
|
1121
|
+
);
|
|
1122
|
+
assert.ok(JSON.stringify(restored).includes(receipt.messageId));
|
|
1123
|
+
assert.ok(!JSON.stringify(restored).includes("No result:"));
|
|
1124
|
+
assert.equal((await listRoomWork(thread.id)).length, 2);
|
|
1125
|
+
await deleteThread(thread.id);
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
test("a failed participant drains its peers before manual resume", async () => {
|
|
1129
|
+
plans.set("Alpha", [
|
|
1130
|
+
() => ask("Beta", "Work alongside me"),
|
|
1131
|
+
() =>
|
|
1132
|
+
call(T.bash, {
|
|
1133
|
+
command: "sleep 0.1",
|
|
1134
|
+
description: "Continue independent work.",
|
|
1135
|
+
}),
|
|
1136
|
+
() => [
|
|
1137
|
+
{
|
|
1138
|
+
type: "error",
|
|
1139
|
+
error: "Provider unavailable in this test (expected interruption)",
|
|
1140
|
+
},
|
|
1141
|
+
],
|
|
1142
|
+
() => [
|
|
1143
|
+
{
|
|
1144
|
+
type: "error",
|
|
1145
|
+
error:
|
|
1146
|
+
"Provider still unavailable on the retry (expected interruption)",
|
|
1147
|
+
},
|
|
1148
|
+
],
|
|
1149
|
+
]);
|
|
1150
|
+
plans.set("Beta", [
|
|
1151
|
+
() =>
|
|
1152
|
+
call(T.bash, {
|
|
1153
|
+
command: "sleep 5; printf PEER_INTERRUPTED",
|
|
1154
|
+
description: "Work until the room pauses.",
|
|
1155
|
+
}),
|
|
1156
|
+
]);
|
|
1157
|
+
const id = await startThread({
|
|
1158
|
+
bot: "Alpha",
|
|
1159
|
+
request: "Pause the team coherently",
|
|
1160
|
+
label: "Failure",
|
|
1161
|
+
from: "user",
|
|
1162
|
+
});
|
|
1163
|
+
await waitFor(id, "waiting");
|
|
1164
|
+
plans.set("Beta", [
|
|
1165
|
+
(prompt) => {
|
|
1166
|
+
assert.ok(prompt.includes("Aborted"));
|
|
1167
|
+
return text("Peer resumed safely");
|
|
1168
|
+
},
|
|
1169
|
+
]);
|
|
1170
|
+
plans.set("Alpha", [
|
|
1171
|
+
() => text("Waiting for resumed peer"),
|
|
1172
|
+
(prompt) => {
|
|
1173
|
+
assert.ok(prompt.includes("Peer resumed safely"));
|
|
1174
|
+
return text("Team recovered");
|
|
1175
|
+
},
|
|
1176
|
+
]);
|
|
1177
|
+
await answerThread(id, "Continue");
|
|
1178
|
+
await waitFor(id, "done");
|
|
1179
|
+
assert.equal((await findThread(id))?.outcome, "Team recovered");
|
|
1180
|
+
assert.ok((await listRoomWork(id)).every((work) => work.state === "done"));
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
test("a provider failure streamed as its parsed body pauses in the provider's words", async () => {
|
|
1184
|
+
const { roomContextBudget } = await import("../features/bot/room.query.ts");
|
|
1185
|
+
// A streamed body with no `message` string reaches the run as the object it was
|
|
1186
|
+
// parsed into: the sdk wraps only bodies it can read words from.
|
|
1187
|
+
const unavailable = { error: { code: 503, type: "upstream_unavailable" } };
|
|
1188
|
+
const tooLong = "This model's maximum context length is 128000 tokens.";
|
|
1189
|
+
const broken = () => [{ type: "error", error: unavailable }];
|
|
1190
|
+
plans.set("Alpha", [broken, broken]);
|
|
1191
|
+
const id = await startThread({
|
|
1192
|
+
bot: "Alpha",
|
|
1193
|
+
request: "Fail with a provider body",
|
|
1194
|
+
label: "Provider body",
|
|
1195
|
+
from: "user",
|
|
1196
|
+
});
|
|
1197
|
+
const paused = await waitFor(id, "waiting");
|
|
1198
|
+
assert.equal(paused.outcome, JSON.stringify(unavailable));
|
|
1199
|
+
// One note before the retry, one where the room paused
|
|
1200
|
+
const rows = await rowsOf(id);
|
|
1201
|
+
const notes = rows.filter((row) => row.note);
|
|
1202
|
+
assert.equal(notes.length, 2);
|
|
1203
|
+
assert.equal(notes.at(-1)?.seq, rows.at(-1)?.seq);
|
|
1204
|
+
for (const note of notes)
|
|
1205
|
+
assert.ok(String(note.content).startsWith(`${paused.outcome} Resume`));
|
|
1206
|
+
assert.ok(
|
|
1207
|
+
(await listRoomRelays()).some(
|
|
1208
|
+
(relay) => relay.threadId === id && relay.text === paused.outcome,
|
|
1209
|
+
),
|
|
1210
|
+
);
|
|
1211
|
+
assert.equal(await roomContextBudget(id, "Alpha"), undefined);
|
|
1212
|
+
|
|
1213
|
+
// Read as an overflow: the retry compacts earlier instead of pausing
|
|
1214
|
+
plans.set("Alpha", [
|
|
1215
|
+
() => [{ type: "error", error: { error: tooLong } }],
|
|
1216
|
+
() => text("Recovered after the retry"),
|
|
1217
|
+
]);
|
|
1218
|
+
await answerThread(id, "Continue");
|
|
1219
|
+
assert.equal(
|
|
1220
|
+
(await waitFor(id, "done")).outcome,
|
|
1221
|
+
"Recovered after the retry",
|
|
1222
|
+
);
|
|
1223
|
+
assert.ok(await roomContextBudget(id, "Alpha"));
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
test("a provider's refusal waits for a person at once; a break is tried once more on its own", async () => {
|
|
1227
|
+
const refused = new APICallError({
|
|
1228
|
+
message: "Invalid API key",
|
|
1229
|
+
url: "https://provider.test/v1",
|
|
1230
|
+
requestBodyValues: {},
|
|
1231
|
+
statusCode: 401,
|
|
1232
|
+
isRetryable: false,
|
|
1233
|
+
});
|
|
1234
|
+
plans.set("Alpha", [() => [{ type: "error", error: refused }]]);
|
|
1235
|
+
const id = await startThread({
|
|
1236
|
+
bot: "Alpha",
|
|
1237
|
+
request: "Fail with a refusal",
|
|
1238
|
+
label: "Refusal",
|
|
1239
|
+
from: "user",
|
|
1240
|
+
});
|
|
1241
|
+
const paused = await waitFor(id, "waiting");
|
|
1242
|
+
assert.equal(paused.outcome, "Invalid API key (401)");
|
|
1243
|
+
const notes = async () => (await rowsOf(id)).filter((row) => row.note).length;
|
|
1244
|
+
assert.equal(await notes(), 1);
|
|
1245
|
+
|
|
1246
|
+
plans.set("Alpha", [
|
|
1247
|
+
() => [{ type: "error", error: "The stream broke in this test" }],
|
|
1248
|
+
() => text("Finished on the retry"),
|
|
1249
|
+
]);
|
|
1250
|
+
await answerThread(id, "Continue");
|
|
1251
|
+
assert.equal((await waitFor(id, "done")).outcome, "Finished on the retry");
|
|
1252
|
+
assert.equal(await notes(), 2);
|
|
1253
|
+
// Resuming settles the stop it picked up from, and the retry is not an
|
|
1254
|
+
// interruption the call hears of
|
|
1255
|
+
assert.equal(
|
|
1256
|
+
(await listRoomRelays()).filter(
|
|
1257
|
+
(relay) => relay.threadId === id && relay.kind === "interrupted",
|
|
1258
|
+
).length,
|
|
1259
|
+
0,
|
|
1260
|
+
);
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
test("compaction thresholds belong to the participant across different callers", async () => {
|
|
1264
|
+
const { insertThread, deleteThread } = await import(
|
|
1265
|
+
"../features/bot/thread.query.ts"
|
|
1266
|
+
);
|
|
1267
|
+
const { claimRoomWork, lowerRoomContextBudget, roomContextBudget } =
|
|
1268
|
+
await import("../features/bot/room.query.ts");
|
|
1269
|
+
const thread = await insertThread({
|
|
1270
|
+
bot: "Alpha",
|
|
1271
|
+
request: "Budget",
|
|
1272
|
+
label: "Budget",
|
|
1273
|
+
opening: "Opening",
|
|
1274
|
+
});
|
|
1275
|
+
const root = (await claimRoomWork(thread.id))!;
|
|
1276
|
+
await sendRoomMessage(root, {
|
|
1277
|
+
id: "budget-message",
|
|
1278
|
+
to: "Beta",
|
|
1279
|
+
text: "Work within the accepted context",
|
|
1280
|
+
});
|
|
1281
|
+
const beta = (await claimRoomWork(thread.id))!;
|
|
1282
|
+
await lowerRoomContextBudget(beta, 12000);
|
|
1283
|
+
assert.equal(await roomContextBudget(thread.id, "Beta"), 12000);
|
|
1284
|
+
assert.equal(await roomContextBudget(thread.id, "Alpha"), undefined);
|
|
1285
|
+
await deleteThread(thread.id);
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
test("native provider data without a complete native message resumes as a truthful note", () => {
|
|
1289
|
+
const restored = resumeTranscript([
|
|
1290
|
+
{
|
|
1291
|
+
role: "assistant",
|
|
1292
|
+
content: [
|
|
1293
|
+
{
|
|
1294
|
+
type: "tool-call",
|
|
1295
|
+
toolCallId: "native-partial",
|
|
1296
|
+
toolName: T.web_search,
|
|
1297
|
+
input: {},
|
|
1298
|
+
providerExecuted: true,
|
|
1299
|
+
},
|
|
1300
|
+
],
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
role: "tool",
|
|
1304
|
+
content: [
|
|
1305
|
+
{
|
|
1306
|
+
type: "tool-result",
|
|
1307
|
+
toolCallId: "native-partial",
|
|
1308
|
+
toolName: T.web_search,
|
|
1309
|
+
output: {
|
|
1310
|
+
type: "json",
|
|
1311
|
+
value: { finding: "Recorded provider finding" },
|
|
1312
|
+
},
|
|
1313
|
+
},
|
|
1314
|
+
],
|
|
1315
|
+
},
|
|
1316
|
+
]);
|
|
1317
|
+
assert.ok(!restored.some((row) => row.role === "tool"));
|
|
1318
|
+
assert.ok(JSON.stringify(restored).includes("Recorded provider finding"));
|
|
1319
|
+
assert.ok(
|
|
1320
|
+
JSON.stringify(restored).includes("native continuation was interrupted"),
|
|
1321
|
+
);
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
test("the coordinator can address Thursday using the original incoming message ID", async () => {
|
|
1325
|
+
const { insertThread, deleteThread } = await import(
|
|
1326
|
+
"../features/bot/thread.query.ts"
|
|
1327
|
+
);
|
|
1328
|
+
const { claimRoomWork } = await import("../features/bot/room.query.ts");
|
|
1329
|
+
const thread = await insertThread({
|
|
1330
|
+
bot: "Alpha",
|
|
1331
|
+
request: "Use the original conversation",
|
|
1332
|
+
label: "Thursday reply",
|
|
1333
|
+
opening: "Opening",
|
|
1334
|
+
});
|
|
1335
|
+
const root = (await claimRoomWork(thread.id))!;
|
|
1336
|
+
const sent = await sendRoomMessage(root, {
|
|
1337
|
+
id: "reply-to-thursday",
|
|
1338
|
+
to: "Thursday",
|
|
1339
|
+
text: "Which destination should I use?",
|
|
1340
|
+
kind: "question",
|
|
1341
|
+
replyTo: root.id,
|
|
1342
|
+
});
|
|
1343
|
+
assert.equal(
|
|
1344
|
+
(await listRoomWork(thread.id)).find((work) => work.id === sent.messageId)
|
|
1345
|
+
?.state,
|
|
1346
|
+
"external",
|
|
1347
|
+
);
|
|
1348
|
+
assert.equal(
|
|
1349
|
+
(await listRoomRelays()).find((relay) => relay.messageId === sent.messageId)
|
|
1350
|
+
?.bot,
|
|
1351
|
+
"Alpha",
|
|
1352
|
+
);
|
|
1353
|
+
await deleteThread(thread.id);
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
test("Thursday updates do not create questions or prevent the final report", async () => {
|
|
1357
|
+
plans.set("Alpha", [
|
|
1358
|
+
() =>
|
|
1359
|
+
call(T.send_message, {
|
|
1360
|
+
to: "Thursday",
|
|
1361
|
+
text: "The first part is ready.",
|
|
1362
|
+
}),
|
|
1363
|
+
() =>
|
|
1364
|
+
call(T.send_message, {
|
|
1365
|
+
to: "Thursday",
|
|
1366
|
+
text: "The requested work is complete.",
|
|
1367
|
+
kind: "message",
|
|
1368
|
+
}),
|
|
1369
|
+
() => text("All work is complete."),
|
|
1370
|
+
]);
|
|
1371
|
+
const id = await startThread({
|
|
1372
|
+
bot: "Alpha",
|
|
1373
|
+
request: "Send progress and finish",
|
|
1374
|
+
label: "Notifications",
|
|
1375
|
+
from: "user",
|
|
1376
|
+
});
|
|
1377
|
+
await waitFor(id, "done");
|
|
1378
|
+
const thread = (await findThreadView(id))!;
|
|
1379
|
+
assert.equal(thread.room!.questions.length, 0);
|
|
1380
|
+
assert.equal(thread.ask, null);
|
|
1381
|
+
assert.equal(thread.outcome, "All work is complete.");
|
|
1382
|
+
const messages = thread.lines.filter((line) => line.kind === "ask");
|
|
1383
|
+
assert.equal(messages.length, 2);
|
|
1384
|
+
for (const message of messages)
|
|
1385
|
+
assert.equal(message.kind === "ask" && message.question, false);
|
|
1386
|
+
const relays = (await listRoomRelays()).filter(
|
|
1387
|
+
(relay) => relay.threadId === id,
|
|
1388
|
+
);
|
|
1389
|
+
assert.deepEqual(
|
|
1390
|
+
relays.map((relay) => relay.kind),
|
|
1391
|
+
["message", "message", "report"],
|
|
1392
|
+
);
|
|
1393
|
+
assert.ok(relays.every((relay) => relay.messageId === null));
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
test("answer drafts remain separate for two questions from the same bot", async () => {
|
|
1397
|
+
const { threadDrafts } = await import("../features/bot/thread.store.ts");
|
|
1398
|
+
threadDrafts.set("draft-room", "Alpha", "First answer", "first");
|
|
1399
|
+
threadDrafts.set("draft-room", "Alpha", "Second answer", "second");
|
|
1400
|
+
threadDrafts.set("draft-room", "Alpha", "A general message");
|
|
1401
|
+
threadDrafts.set("draft-room", "Alpha", "", "first");
|
|
1402
|
+
assert.equal(threadDrafts.get("draft-room", "Alpha", "first"), "");
|
|
1403
|
+
assert.equal(
|
|
1404
|
+
threadDrafts.get("draft-room", "Alpha", "second"),
|
|
1405
|
+
"Second answer",
|
|
1406
|
+
);
|
|
1407
|
+
assert.equal(threadDrafts.get("draft-room", "Alpha"), "A general message");
|
|
1408
|
+
});
|