thursday-agent 0.3.0 → 0.5.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/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- 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 +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/artifact/route.js +4 -4
- package/.next/server/app/api/artifact/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/files/route.js +3 -3
- package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/task/[id]/route.js +8 -8
- package/.next/server/app/api/bot/task/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/task/route.js +6 -6
- package/.next/server/app/api/bot/task/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- 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 +2 -2
- package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/credits/route.js +2 -2
- package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/route.js +2 -2
- package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/subscription/route.js +2 -2
- package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/edit/route.js +11 -11
- package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/call/route.js +6 -6
- package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/skills/route.js +6 -6
- package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/tool-call/route.js +8 -8
- package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/route.js +3 -2
- 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 +2 -2
- 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 +40 -40
- package/.next/server/app/page.js +3 -3
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__026u77e._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +1 -1
- package/.next/server/chunks/{[root-of-the-server]__02xjoto._.js → [root-of-the-server]__050zri4._.js} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0bg9710._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0in-d5s._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0ncs9uu._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0pa9k-f._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__0qi_m0e._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__0tq0m_k._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0xep6j_._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1-ga0kh._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__144l7qt._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__1_04mcf._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__1anc1p9._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1jg8t86._.js +8 -0
- package/.next/server/chunks/{[root-of-the-server]__12cabg9._.js → [root-of-the-server]__1lldzy9._.js} +1 -1
- package/.next/server/chunks/[root-of-the-server]__1orn-5b._.js +4 -3
- package/.next/server/chunks/{[root-of-the-server]__01dkp4a._.js → [root-of-the-server]__1peqvsi._.js} +1 -1
- package/.next/server/chunks/[root-of-the-server]__1w8gm5h._.js +10 -0
- package/.next/server/chunks/[root-of-the-server]__1xt39nn._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__1zb8mfo._.js +8 -0
- package/.next/server/chunks/_0-fjql4._.js +11 -6
- package/.next/server/chunks/_00xh_q0._.js +4 -6
- package/.next/server/chunks/_05h2cii._.js +1 -1
- package/.next/server/chunks/{_1_9mjx-._.js → _091bi8d._.js} +1 -1
- package/.next/server/chunks/_0fzqt2y._.js +57 -45
- package/.next/server/chunks/{_0u4aiy2._.js → _0lr92vd._.js} +1 -1
- package/.next/server/chunks/_0mecv0f._.js +11 -6
- package/.next/server/chunks/_0mh8j4m._.js +1 -1
- package/.next/server/chunks/{_0sbynis._.js → _0si7xzn._.js} +1 -1
- package/.next/server/chunks/_0wp4le3._.js +1 -1
- package/.next/server/chunks/_0y5fg-x._.js +1 -1
- package/.next/server/chunks/_1230wbx._.js +5 -5
- package/.next/server/chunks/_15hq89o._.js +1 -1
- package/.next/server/chunks/{_0obojvv._.js → _18d7-5p._.js} +1 -1
- package/.next/server/chunks/{_03kp0jp._.js → _1c78vw5._.js} +71 -61
- package/.next/server/chunks/_1d0j961._.js +2 -2
- package/.next/server/chunks/_1j8k_j5._.js +1 -1
- package/.next/server/chunks/_1krd9rf._.js +1 -1
- package/.next/server/chunks/_1php4eq._.js +1 -1
- package/.next/server/chunks/{_03a0897._.js → _1zkfbaa._.js} +1 -1
- package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
- package/.next/server/chunks/features_1qf3cei._.js +6 -1
- package/.next/server/chunks/features_memory_1uqvksn._.js +2 -2
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0r28-sm._.js → node_modules_consola_dist_chunks_prompt_mjs_17k8irj._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_1xoq2rl._.js → node_modules_consola_dist_chunks_prompt_mjs_1jidylq._.js} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0-nob52._.js +4 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__092knlj._.js +3 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__0_69wqr._.js → [root-of-the-server]__0s4wb56._.js} +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__1eh7hid._.js → [root-of-the-server]__0s9dq5u._.js} +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__0zkv7z_._.js → [root-of-the-server]__0sd3izg._.js} +2 -2
- package/.next/server/chunks/ssr/{[root-of-the-server]__1gtlw6v._.js → [root-of-the-server]__1-13tcn._.js} +3 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__10-dz5o._.js +13 -9
- package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__1cyjywu._.js → [root-of-the-server]__1zidpfl._.js} +1 -1
- package/.next/server/chunks/ssr/_001_ms1._.js +6 -1
- package/.next/server/chunks/ssr/{_07uchnc._.js → _04z2c7b._.js} +2 -2
- package/.next/server/chunks/ssr/{_0kbkmar._.js → _06tssji._.js} +2 -2
- package/.next/server/chunks/ssr/_0fk4c7d._.js +3 -0
- package/.next/server/chunks/ssr/_0i_6q1w._.js +2 -2
- package/.next/server/chunks/ssr/{_1xvgn77._.js → _0m2lsd1._.js} +2 -2
- package/.next/server/chunks/ssr/_0mggveb._.js +3 -0
- package/.next/server/chunks/ssr/_0nbpc_8._.js +3 -0
- package/.next/server/chunks/ssr/{_0y0tiht._.js → _0rzucul._.js} +3 -3
- package/.next/server/chunks/ssr/{_0mb8zc8._.js → _0s99bgb._.js} +2 -2
- package/.next/server/chunks/ssr/_0zcjrbg._.js +3 -0
- package/.next/server/chunks/ssr/_1-teee5._.js +4 -4
- package/.next/server/chunks/ssr/{_0c-ql2m._.js → _11j92tu._.js} +2 -2
- package/.next/server/chunks/ssr/_13fke0x._.js +1 -1
- package/.next/server/chunks/ssr/{_01_6m32._.js → _1ebxla1._.js} +2 -2
- package/.next/server/chunks/ssr/_1jdgjv9._.js +3 -0
- package/.next/server/chunks/ssr/{_0u2jv_x._.js → _1lz_cq2._.js} +60 -50
- package/.next/server/chunks/ssr/{_20kd_n0._.js → _1nj4niv._.js} +3 -3
- package/.next/server/chunks/ssr/_1ovmc9s._.js +3 -0
- package/.next/server/chunks/ssr/_1sji22n._.js +1 -1
- package/.next/server/chunks/ssr/_1vyd1ji._.js +1 -1
- 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/{node_modules_1-hc6uv._.js → node_modules_0-dr_o7._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_124bue5._.js → node_modules_04sksn9._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1uuj9x1._.js → node_modules_05-n6dz._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1i7u1ia._.js → node_modules_056qgf-._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0lm7_vm._.js → node_modules_07dsgkx._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1h4a4pp._.js → node_modules_0bvopy2._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1bc7gu8._.js → node_modules_0fkeku3._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0h50b8n._.js → node_modules_0ifp389._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0zp9r92._.js → node_modules_0iur59s._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_063xwpp._.js → node_modules_0m383r2._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1bgb6-f._.js → node_modules_0u9da1h._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0bvraul._.js → node_modules_0zeg3x_._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1t9cyee._.js → node_modules_1-y7nni._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1_n3ztw._.js → node_modules_16mkxuo._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0eu1va6._.js → node_modules_1a0kmjl._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0z5tsu8._.js → node_modules_1chvq5f._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0h7u60n._.js → node_modules_1j8j4pt._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0rjg6t1._.js → node_modules_1k6n600._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1xx28h1._.js → node_modules_1k7z2j6._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0h5hnr1._.js → node_modules_1k8j8n0._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0mwym0g._.js → node_modules_1lz0oxc._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1vqb6po._.js → node_modules_1t77e4n._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1l0qtdu._.js → node_modules_1uyk91w._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0y_cded._.js → node_modules_1x0ztf4._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_113tz0l._.js → node_modules_1y79i83._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1juq3hs._.js → node_modules_20c28js._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0d3skpl._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0bkwd0u._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0i44kk5._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0p9a9su._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@vercel_oidc_dist_0qe30a_._.js → node_modules_@vercel_oidc_dist_1o553h-._.js} +1 -1
- 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 +41 -41
- package/.next/static/chunks/{17donp69y4do7.js → 0n3jle9kbqfr2.js} +8 -8
- package/.next/static/chunks/{3ok9_x74rip3v.js → 11r2yp_yh9_qn.js} +1 -1
- package/.next/static/chunks/{26-pr2c9ro222.js → 126fj4pga_72k.js} +1 -1
- package/.next/static/chunks/{2a0k6jlq0_v1u.js → 159udiexf6dom.js} +2 -2
- package/.next/static/chunks/18gi7o2ibrgh8.js +3 -0
- package/.next/static/chunks/{0x79tx04dxbpp.js → 1q9z1tohil7xn.js} +1 -1
- package/.next/static/chunks/{3cfi_q6d_wtci.js → 1sjgfets224re.js} +1 -1
- package/.next/static/chunks/{3agpgkwaa321p.js → 1wszsr83t01gh.js} +1 -1
- package/.next/static/chunks/22_zkas21rpna.js +1 -0
- package/.next/static/chunks/{1ekc7ul556kbl.js → 2szintyibu0s5.js} +1 -1
- package/.next/static/chunks/{1okqiww3pnwka.js → 3-b6b7entqazs.js} +1 -1
- package/.next/static/chunks/3cq5g494yuke8.css +1 -0
- package/.next/static/chunks/{2fzs9fm8yarvq.js → 3dmbhsv2u_8kj.js} +1 -1
- package/.next/static/chunks/{1grhjo7a5a0_-.js → 3i0a5px-oufgc.js} +1 -1
- package/.next/static/chunks/{280t0_e99i276.js → 3wexk9qo8zuj3.js} +3 -3
- package/.next/static/chunks/3xre8bu_pk1p_.js +1 -0
- package/.next/static/chunks/3zv6t3i77xhr_.js +1 -0
- package/.next/static/chunks/{28cizcdaabd4h.js → 408ueu4hfvcg7.js} +1 -1
- package/.next/static/chunks/{3xpbkl66ssib5.js → 435atsnhs7ksu.js} +8 -8
- package/README.md +46 -23
- package/app/api/events/app-event.ts +0 -2
- package/bin/port.mjs +65 -0
- package/bin/thursday.mjs +2 -33
- package/docs/how-it-works.md +11 -7
- package/docs/images/face-dark.png +0 -0
- package/docs/images/face-light.png +0 -0
- package/docs/images/keeps-going.png +0 -0
- package/features/ai/chatgpt.ts +42 -20
- package/features/ai/load-tools.ts +20 -14
- package/features/ai/prompts/bot.prompt.ts +91 -89
- package/features/ai/prompts/thursday.prompt.ts +48 -41
- package/features/ai/tools/bot.tool.ts +3 -3
- package/features/ai/tools/memory.tool.ts +65 -62
- package/features/ai/tools/skills.tool.ts +25 -8
- package/features/ai/tools/tool-name.ts +0 -1
- package/features/ai/tools/workspace.tool.ts +3 -0
- package/features/bot/bot.action.ts +6 -1
- package/features/bot/bot.run.ts +195 -80
- package/features/bot/bot.runner.ts +82 -39
- package/features/bot/bot.schema.ts +25 -0
- package/features/bot/components/bot-room.tsx +57 -4
- package/features/bot/components/bot-tool.tsx +0 -1
- package/features/bot/task.query.ts +132 -10
- package/features/bot/task.store.ts +26 -9
- package/features/memory/components/memory-edit.tsx +7 -5
- package/features/thursday/components/thursday.tsx +0 -3
- package/features/thursday/tool-line.ts +0 -1
- package/features/thursday/use-thursday.ts +1 -1
- package/features/workspace/workspace.ts +27 -20
- package/package.json +1 -1
- package/skills/browser/SKILL.md +6 -5
- package/.next/server/chunks/[root-of-the-server]__04q--0f._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__0bl-e63._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0jq4ohy._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__0ls6_8y._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__1339jnv._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__18_teo8._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__1ccon1w._.js +0 -10
- package/.next/server/chunks/[root-of-the-server]__1wa1k7r._.js +0 -8
- package/.next/server/chunks/_1zjrdag._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1ox7hbk._.js +0 -4
- package/.next/server/chunks/ssr/[root-of-the-server]__1pduf5n._.js +0 -3
- package/.next/server/chunks/ssr/_0mo2w06._.js +0 -3
- package/.next/server/chunks/ssr/_13z3f85._.js +0 -3
- package/.next/server/chunks/ssr/_14_lw10._.js +0 -3
- package/.next/server/chunks/ssr/_1eoqg_g._.js +0 -3
- package/.next/server/chunks/ssr/_1spzm8z._.js +0 -3
- package/.next/server/chunks/ssr/lib_date-like_ts_02is0pw._.js +0 -3
- package/.next/static/chunks/0tmgrnlje14-s.js +0 -3
- package/.next/static/chunks/0xzgvc0fkdb6t.js +0 -1
- package/.next/static/chunks/14hgtsjg-kael.css +0 -1
- package/.next/static/chunks/2hw2f6zmt7fmt.js +0 -1
- package/.next/static/chunks/3111pj5wnjgos.js +0 -1
- package/features/memory/components/memory-view.tsx +0 -84
- /package/.next/static/{x1cCqY0kWpR_VcYx_lWO5 → G6-gNoyGDEcgAsq-L4PE1}/_buildManifest.js +0 -0
- /package/.next/static/{x1cCqY0kWpR_VcYx_lWO5 → G6-gNoyGDEcgAsq-L4PE1}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{x1cCqY0kWpR_VcYx_lWO5 → G6-gNoyGDEcgAsq-L4PE1}/_ssgManifest.js +0 -0
|
@@ -33,12 +33,22 @@ import { desktopNotify } from "@/lib/desktop-notify";
|
|
|
33
33
|
import { logger } from "@/lib/logger";
|
|
34
34
|
import { publicError } from "@/lib/public-error";
|
|
35
35
|
import { createKeyedLock } from "@/lib/queue";
|
|
36
|
-
import {
|
|
37
|
-
|
|
36
|
+
import {
|
|
37
|
+
createBotDesks,
|
|
38
|
+
resumeThread,
|
|
39
|
+
runBot,
|
|
40
|
+
type TaskEvent,
|
|
41
|
+
} from "./bot.run";
|
|
42
|
+
import {
|
|
43
|
+
TASK_CONTINUE,
|
|
44
|
+
type TaskPending,
|
|
45
|
+
type TaskSpeaker,
|
|
46
|
+
type TaskStatus,
|
|
47
|
+
tagSpeaker,
|
|
48
|
+
} from "./bot.schema";
|
|
38
49
|
import {
|
|
39
50
|
addTaskUsage,
|
|
40
51
|
countStopsSinceSpoken,
|
|
41
|
-
deleteFinishedTasks,
|
|
42
52
|
deleteMessages,
|
|
43
53
|
deleteTask,
|
|
44
54
|
findTask,
|
|
@@ -49,6 +59,7 @@ import {
|
|
|
49
59
|
listRunningTaskIds,
|
|
50
60
|
listTaskFolders,
|
|
51
61
|
listThread,
|
|
62
|
+
stopNote,
|
|
52
63
|
type TaskMessageInput,
|
|
53
64
|
updateTask,
|
|
54
65
|
upsertMessage,
|
|
@@ -99,24 +110,28 @@ const pausing = ((globalThis as Pinned).__botPausing ??= {
|
|
|
99
110
|
current: Promise.resolve(),
|
|
100
111
|
});
|
|
101
112
|
|
|
102
|
-
/** Opens a job. The opening message — who is
|
|
113
|
+
/** Opens a job. The opening message — who is on it, the job, the call it came from (bot.prompt buildTaskOpening) — is the thread's first row. */
|
|
103
114
|
export async function startTask(input: {
|
|
104
115
|
bot: string;
|
|
105
116
|
request: string;
|
|
106
117
|
label: string;
|
|
107
118
|
callId?: string | null;
|
|
119
|
+
/** Who handed it over: Thursday during a call, or the user on screen. */
|
|
120
|
+
from: TaskSpeaker;
|
|
108
121
|
}) {
|
|
109
|
-
const
|
|
110
|
-
|
|
122
|
+
const { from, ...row } = input;
|
|
123
|
+
const conversation = row.callId
|
|
124
|
+
? await listCallTurns(row.callId, OPENING_TURNS)
|
|
111
125
|
: [];
|
|
112
126
|
const opening = buildTaskOpening({
|
|
113
|
-
bot:
|
|
114
|
-
request:
|
|
127
|
+
bot: row.bot,
|
|
128
|
+
request: row.request,
|
|
115
129
|
conversation,
|
|
130
|
+
from,
|
|
116
131
|
});
|
|
117
|
-
const task = await insertTask({ ...
|
|
132
|
+
const task = await insertTask({ ...row, opening });
|
|
118
133
|
launch(task.id, {
|
|
119
|
-
bot:
|
|
134
|
+
bot: row.bot,
|
|
120
135
|
messages: [{ role: "user", content: opening }],
|
|
121
136
|
});
|
|
122
137
|
return task.id;
|
|
@@ -128,14 +143,25 @@ export async function startTask(input: {
|
|
|
128
143
|
* before its next step; the step in flight finishes), or a follow-up to a
|
|
129
144
|
* finished one. Only when the run has already ended does the running case
|
|
130
145
|
* fall through to stop, append, relaunch.
|
|
146
|
+
*
|
|
147
|
+
* @param from Who said it. The words go on the thread tagged with it (bot.schema
|
|
148
|
+
* tagSpeaker): Thursday passing something on from a call and the user typing on
|
|
149
|
+
* screen reach the bot down this one pipe, and a go-ahead means something
|
|
150
|
+
* different from each. Unset for words that already carry their tags — the notes
|
|
151
|
+
* a finished run left unread (drive).
|
|
131
152
|
*/
|
|
132
|
-
export async function answerTask(
|
|
153
|
+
export async function answerTask(
|
|
154
|
+
id: string,
|
|
155
|
+
answer: string,
|
|
156
|
+
from?: TaskSpeaker,
|
|
157
|
+
) {
|
|
158
|
+
const said = from ? tagSpeaker(from, answer) : answer;
|
|
133
159
|
return taskLock(id, async () => {
|
|
134
160
|
const task = await findTask(id);
|
|
135
161
|
if (!task) publicError("No such job.");
|
|
136
162
|
|
|
137
163
|
const live = running.get(id);
|
|
138
|
-
if (live && task.status === "running" && live.note(
|
|
164
|
+
if (live && task.status === "running" && live.note(said)) {
|
|
139
165
|
logger.debug(`task ${id}: interjection queued`);
|
|
140
166
|
return;
|
|
141
167
|
}
|
|
@@ -147,7 +173,7 @@ export async function answerTask(id: string, answer: string) {
|
|
|
147
173
|
forgetResume(id);
|
|
148
174
|
|
|
149
175
|
const thread = await listThread(id);
|
|
150
|
-
const { reply, as } = joinThread(task,
|
|
176
|
+
const { reply, as } = joinThread(task, said, thread);
|
|
151
177
|
logger.debug(`task ${id}: ${as}`);
|
|
152
178
|
|
|
153
179
|
await upsertMessage(id, (await lastSeq(id)) + 1, {
|
|
@@ -253,7 +279,8 @@ export async function cancelTask(id: string) {
|
|
|
253
279
|
// write "Cancelled." over the thing it was run for.
|
|
254
280
|
if (task.endedAt) publicError("That job has already ended.");
|
|
255
281
|
forgetResume(id);
|
|
256
|
-
running.get(id)
|
|
282
|
+
const live = running.get(id);
|
|
283
|
+
live?.stop.abort();
|
|
257
284
|
await updateTask(id, {
|
|
258
285
|
status: "failed",
|
|
259
286
|
outcome: "Cancelled.",
|
|
@@ -264,24 +291,27 @@ export async function cancelTask(id: string) {
|
|
|
264
291
|
});
|
|
265
292
|
// Cancel is a real end, so the browser session closes here; the abort in
|
|
266
293
|
// answerTask is followed by a relaunch and must not close it.
|
|
267
|
-
|
|
294
|
+
await live?.done;
|
|
295
|
+
await closeJobShell(id);
|
|
268
296
|
});
|
|
269
297
|
}
|
|
270
298
|
|
|
271
299
|
/** Deletes the job, stopping it first if it is still running. */
|
|
272
300
|
export async function removeTask(id: string) {
|
|
273
|
-
return taskLock(id,
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
301
|
+
return taskLock(id, () => removeLockedTask(id));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** The caller holds taskLock, so a follow-up cannot race deletion or browser cleanup. */
|
|
305
|
+
async function removeLockedTask(id: string) {
|
|
306
|
+
const task = await findTask(id);
|
|
307
|
+
forgetResume(id);
|
|
308
|
+
const live = running.get(id);
|
|
309
|
+
live?.stop.abort();
|
|
310
|
+
await live?.done;
|
|
311
|
+
await closeJobShell(id);
|
|
312
|
+
const gone = await deleteTask(id);
|
|
313
|
+
if (gone && task) await removeJobScratch(id, task.label);
|
|
314
|
+
return gone;
|
|
285
315
|
}
|
|
286
316
|
|
|
287
317
|
/**
|
|
@@ -290,9 +320,16 @@ export async function removeTask(id: string) {
|
|
|
290
320
|
* and only one of the two is the database's.
|
|
291
321
|
*/
|
|
292
322
|
export async function removeFinishedTasks(): Promise<number> {
|
|
293
|
-
|
|
294
|
-
for (const task of
|
|
295
|
-
|
|
323
|
+
let removed = 0;
|
|
324
|
+
for (const task of await listTaskFolders()) {
|
|
325
|
+
if (task.status !== "done" && task.status !== "failed") continue;
|
|
326
|
+
await taskLock(task.id, async () => {
|
|
327
|
+
const current = await findTask(task.id);
|
|
328
|
+
if (current?.status !== "done" && current?.status !== "failed") return;
|
|
329
|
+
if (await removeLockedTask(task.id)) removed += 1;
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return removed;
|
|
296
333
|
}
|
|
297
334
|
|
|
298
335
|
/**
|
|
@@ -323,6 +360,7 @@ export async function sweepJobFiles(): Promise<string[]> {
|
|
|
323
360
|
await taskLock(task.id, async () => {
|
|
324
361
|
const now = await findTask(task.id);
|
|
325
362
|
if (!now || !stale(now)) return;
|
|
363
|
+
await closeHiddenBrowser(task.id);
|
|
326
364
|
await removeJobScratch(task.id, task.label);
|
|
327
365
|
removed.push(folder);
|
|
328
366
|
});
|
|
@@ -465,7 +503,7 @@ async function parkTask(
|
|
|
465
503
|
const inMs =
|
|
466
504
|
auto && stop.retry ? waits[Math.min(stops, waits.length - 1)] : 0;
|
|
467
505
|
|
|
468
|
-
await writeNote(id,
|
|
506
|
+
await writeNote(id, stopNote(stop.why)).catch((cause) =>
|
|
469
507
|
logger.warn(`task ${id}: stop not written`, cause),
|
|
470
508
|
);
|
|
471
509
|
await writeEnding(id, {
|
|
@@ -485,10 +523,6 @@ async function parkTask(
|
|
|
485
523
|
if (auto) scheduleResume(id, inMs);
|
|
486
524
|
}
|
|
487
525
|
|
|
488
|
-
/** Said to a resumed run after why it stopped. */
|
|
489
|
-
const IN_FLIGHT =
|
|
490
|
-
"Anything that was under way — a command, a page loading, a download — may not have finished: check before relying on it, then carry on.";
|
|
491
|
-
|
|
492
526
|
const spell = (ms: number) =>
|
|
493
527
|
ms < 60_000
|
|
494
528
|
? `${Math.round(ms / 1000)} seconds`
|
|
@@ -634,6 +668,12 @@ class ThreadWriter {
|
|
|
634
668
|
if (this.closed) return;
|
|
635
669
|
const step = this.step(event);
|
|
636
670
|
switch (event.type) {
|
|
671
|
+
case "request":
|
|
672
|
+
return this.write(++this.seq, event, {
|
|
673
|
+
role: "user",
|
|
674
|
+
content: event.content,
|
|
675
|
+
});
|
|
676
|
+
|
|
637
677
|
case "text":
|
|
638
678
|
step.assistant ??= ++this.seq;
|
|
639
679
|
step.parts.assistant.push({ type: "text", text: event.text });
|
|
@@ -729,6 +769,7 @@ async function drive(
|
|
|
729
769
|
notes: string[],
|
|
730
770
|
) {
|
|
731
771
|
const thread = new ThreadWriter(id, await lastSeq(id));
|
|
772
|
+
const desks = createBotDesks();
|
|
732
773
|
|
|
733
774
|
let ending: {
|
|
734
775
|
status: TaskStatus;
|
|
@@ -749,6 +790,7 @@ async function drive(
|
|
|
749
790
|
await runBot(input, {
|
|
750
791
|
signal,
|
|
751
792
|
taskId: id,
|
|
793
|
+
desks,
|
|
752
794
|
notes: () => notes.splice(0),
|
|
753
795
|
emit: async (event) => {
|
|
754
796
|
await thread.on(event);
|
|
@@ -811,6 +853,8 @@ async function drive(
|
|
|
811
853
|
pending: null,
|
|
812
854
|
};
|
|
813
855
|
} finally {
|
|
856
|
+
// A resumed segment cannot overtake a participant still unwinding its tools.
|
|
857
|
+
await desks.settle();
|
|
814
858
|
thread.close();
|
|
815
859
|
running.delete(id);
|
|
816
860
|
}
|
|
@@ -863,10 +907,9 @@ async function drive(
|
|
|
863
907
|
return;
|
|
864
908
|
}
|
|
865
909
|
|
|
866
|
-
//
|
|
867
|
-
//
|
|
868
|
-
|
|
869
|
-
else void closeHiddenBrowser(id);
|
|
910
|
+
// Follow-ups can reopen a completed job. Its participants keep their browsers
|
|
911
|
+
// until cancellation, deletion, or the workspace retention sweep.
|
|
912
|
+
void pruneJobFiles();
|
|
870
913
|
|
|
871
914
|
const task = await findTask(id);
|
|
872
915
|
if (!task) return;
|
|
@@ -205,6 +205,29 @@ export const TASK_HISTORY_PAGE = PAGE_SIZE;
|
|
|
205
205
|
/** The single option offered when the app stopped a job and asks whether to go on. Button text and spoken word alike. */
|
|
206
206
|
export const TASK_CONTINUE = "Continue";
|
|
207
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Who a person's words to a job came from (bot.runner answerTask). Thursday
|
|
210
|
+
* passing something on from a call and the user typing on screen reach a bot
|
|
211
|
+
* down one pipe, so the words carry it in front of them.
|
|
212
|
+
*/
|
|
213
|
+
export type TaskSpeaker = "thursday" | "user";
|
|
214
|
+
|
|
215
|
+
const SPEAKER_TAGS: Record<TaskSpeaker, string> = {
|
|
216
|
+
thursday: "Thursday, on the call:",
|
|
217
|
+
user: "The user, on screen:",
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export const tagSpeaker = (from: TaskSpeaker, text: string): string =>
|
|
221
|
+
`${SPEAKER_TAGS[from]} ${text}`;
|
|
222
|
+
|
|
223
|
+
/** The words without their tag, as the screen draws a person's line (task.query linesOf). */
|
|
224
|
+
export const untagSpeaker = (text: string): string => {
|
|
225
|
+
for (const tag of Object.values(SPEAKER_TAGS)) {
|
|
226
|
+
if (text.startsWith(`${tag} `)) return text.slice(tag.length + 1);
|
|
227
|
+
}
|
|
228
|
+
return text;
|
|
229
|
+
};
|
|
230
|
+
|
|
208
231
|
/**
|
|
209
232
|
* What a `waiting` job waits on (database task.pending). `toolCallId` is the
|
|
210
233
|
* `ask_thursday` call the answer resolves, null when the app stopped the run and
|
|
@@ -254,6 +277,8 @@ export const TaskLineSchema = z.discriminatedUnion("kind", [
|
|
|
254
277
|
LineBase.extend({ kind: z.literal("text"), text: z.string() }),
|
|
255
278
|
/** Compaction summary (bot.run compact). The model resumes from here; the screen draws it as a divider. */
|
|
256
279
|
LineBase.extend({ kind: z.literal("note"), text: z.string() }),
|
|
280
|
+
/** Why the app stopped the run (bot.runner parkTask, a run that broke), without what the resumed run is told to check. */
|
|
281
|
+
LineBase.extend({ kind: z.literal("stop"), text: z.string() }),
|
|
257
282
|
LineBase.extend({
|
|
258
283
|
kind: z.literal("tool"),
|
|
259
284
|
callId: z.string(),
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { format } from "date-fns";
|
|
3
4
|
import {
|
|
4
5
|
ArrowUp,
|
|
5
6
|
Check,
|
|
7
|
+
ChevronDown,
|
|
6
8
|
ChevronLeft,
|
|
7
9
|
ChevronRight,
|
|
8
10
|
ChevronsRight,
|
|
@@ -10,6 +12,7 @@ import {
|
|
|
10
12
|
History,
|
|
11
13
|
Loader2,
|
|
12
14
|
Plus,
|
|
15
|
+
RotateCw,
|
|
13
16
|
X,
|
|
14
17
|
} from "lucide-react";
|
|
15
18
|
import {
|
|
@@ -45,7 +48,7 @@ import { TaskReply, useAnswerTask } from "@/features/bot/components/task-reply";
|
|
|
45
48
|
import { openSettings } from "@/features/settings/settings.store";
|
|
46
49
|
import { ThursdayMark } from "@/features/thursday/components/thursday-mark";
|
|
47
50
|
import { FileViewer } from "@/features/workspace/components/file-view";
|
|
48
|
-
import { shortAgo } from "@/lib/date-like";
|
|
51
|
+
import { shortAgo, toDate } from "@/lib/date-like";
|
|
49
52
|
import { useServerAction } from "@/lib/protocol/use-server-action";
|
|
50
53
|
import { revalidate, useServerRoute } from "@/lib/protocol/use-server-route";
|
|
51
54
|
import {
|
|
@@ -1630,6 +1633,8 @@ function Group({ group, task }: { group: ChatterGroup; task: TaskView }) {
|
|
|
1630
1633
|
{runs(group.lines).map((run) =>
|
|
1631
1634
|
run.kind === "tools" ? (
|
|
1632
1635
|
<Steps key={run.key} lines={run.lines} taskId={task.id} />
|
|
1636
|
+
) : run.kind === "stops" ? (
|
|
1637
|
+
<Stops key={run.key} lines={run.lines} />
|
|
1633
1638
|
) : (
|
|
1634
1639
|
run.lines.map((line) => (
|
|
1635
1640
|
<Line key={line.id} line={line} taskId={task.id} />
|
|
@@ -1793,17 +1798,65 @@ function Steps({ lines, taskId }: { lines: Chatter[]; taskId: string }) {
|
|
|
1793
1798
|
);
|
|
1794
1799
|
}
|
|
1795
1800
|
|
|
1796
|
-
/**
|
|
1801
|
+
/**
|
|
1802
|
+
* Where the app stopped the run (bot.runner parkTask): a failed model call, a
|
|
1803
|
+
* restart, a closed browser. Muted and in the bot's turn, since the bot goes on
|
|
1804
|
+
* from here. The same reason in a row is one line with a count; opening it lists
|
|
1805
|
+
* each stop by the time it happened.
|
|
1806
|
+
*/
|
|
1807
|
+
function Stops({ lines }: { lines: Chatter[] }) {
|
|
1808
|
+
return (
|
|
1809
|
+
<details className="group min-w-0 px-1 text-muted-foreground">
|
|
1810
|
+
<summary className="flex cursor-pointer list-none items-center gap-1.5 text-[12.5px] leading-relaxed outline-none [&::-webkit-details-marker]:hidden">
|
|
1811
|
+
<RotateCw className="size-3 shrink-0" />
|
|
1812
|
+
<span className="min-w-0 truncate">{leadOf(lines[0].text)}</span>
|
|
1813
|
+
{lines.length > 1 && (
|
|
1814
|
+
<span className="shrink-0 font-mono text-[10px] text-muted-foreground/70">
|
|
1815
|
+
×{lines.length}
|
|
1816
|
+
</span>
|
|
1817
|
+
)}
|
|
1818
|
+
<ChevronDown className="size-2.5 shrink-0 text-muted-foreground/70 transition-transform group-open:rotate-180" />
|
|
1819
|
+
</summary>
|
|
1820
|
+
<div className="mt-1 space-y-1 pl-4.5 text-[11px] leading-relaxed">
|
|
1821
|
+
{lines.map((line) => (
|
|
1822
|
+
<p key={line.id} className="flex gap-2 break-keep">
|
|
1823
|
+
{line.at && (
|
|
1824
|
+
<span className="shrink-0 font-mono text-muted-foreground/70 tabular-nums">
|
|
1825
|
+
{format(toDate(line.at), "HH:mm:ss")}
|
|
1826
|
+
</span>
|
|
1827
|
+
)}
|
|
1828
|
+
<span className="min-w-0">{line.text}</span>
|
|
1829
|
+
</p>
|
|
1830
|
+
))}
|
|
1831
|
+
</div>
|
|
1832
|
+
</details>
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
/** What stopped it, without the words behind it: a stop names those in parentheses (bot.runner parkTask). */
|
|
1837
|
+
const leadOf = (text: string) => text.split(" (")[0].replace(/\.$/, "");
|
|
1838
|
+
|
|
1839
|
+
/** Splits one speaker's lines into runs of tool calls, of stops, and of everything else. */
|
|
1797
1840
|
type Run =
|
|
1798
1841
|
| { kind: "tools"; key: string; lines: Chatter[] }
|
|
1842
|
+
| { kind: "stops"; key: string; lines: Chatter[] }
|
|
1799
1843
|
| { kind: "said"; key: string; lines: Chatter[] };
|
|
1800
1844
|
|
|
1801
1845
|
function runs(lines: Chatter[]): Run[] {
|
|
1802
1846
|
const out: Run[] = [];
|
|
1803
1847
|
for (const line of lines) {
|
|
1804
|
-
const kind =
|
|
1848
|
+
const kind =
|
|
1849
|
+
line.kind === "tool" && line.tool
|
|
1850
|
+
? "tools"
|
|
1851
|
+
: line.kind === "stop"
|
|
1852
|
+
? "stops"
|
|
1853
|
+
: "said";
|
|
1805
1854
|
const open = out.at(-1);
|
|
1806
|
-
|
|
1855
|
+
// Stops fold only while the reason repeats; another reason is a line of its own
|
|
1856
|
+
const same =
|
|
1857
|
+
open?.kind === kind &&
|
|
1858
|
+
(kind !== "stops" || leadOf(open.lines[0].text) === leadOf(line.text));
|
|
1859
|
+
if (open && same) open.lines.push(line);
|
|
1807
1860
|
else out.push({ kind, key: line.id, lines: [line] });
|
|
1808
1861
|
}
|
|
1809
1862
|
return out;
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type TaskPending,
|
|
26
26
|
type TaskStatus,
|
|
27
27
|
type TokenUsage,
|
|
28
|
+
untagSpeaker,
|
|
28
29
|
} from "./bot.schema";
|
|
29
30
|
|
|
30
31
|
// Tasks and their threads. Bots themselves (roster, pinned tools) are bot.query.
|
|
@@ -343,21 +344,23 @@ export async function listBorrowedTails(
|
|
|
343
344
|
and(
|
|
344
345
|
eq(taskMessageTable.taskId, taskId),
|
|
345
346
|
isNotNull(taskMessageTable.parent),
|
|
346
|
-
|
|
347
|
+
inArray(taskMessageTable.role, ["assistant", "tool"]),
|
|
347
348
|
),
|
|
348
349
|
)
|
|
349
350
|
.orderBy(taskMessageTable.seq);
|
|
350
351
|
|
|
351
352
|
const calls = new Map<
|
|
352
353
|
string,
|
|
353
|
-
{ bot: string; texts: string[]; then: string | null }
|
|
354
|
+
{ bot: string; texts: string[]; then: string | null; exchanges: string[] }
|
|
354
355
|
>();
|
|
356
|
+
const questions = new Map<string, string>();
|
|
355
357
|
for (const row of rows) {
|
|
356
358
|
if (!row.parent) continue;
|
|
357
359
|
const call = calls.get(row.parent) ?? {
|
|
358
360
|
bot: row.bot ?? "",
|
|
359
361
|
texts: [],
|
|
360
362
|
then: null,
|
|
363
|
+
exchanges: [],
|
|
361
364
|
};
|
|
362
365
|
const content = row.content as AssistantContent;
|
|
363
366
|
const parts =
|
|
@@ -371,6 +374,21 @@ export async function listBorrowedTails(
|
|
|
371
374
|
} else if (part.type === "tool-call") {
|
|
372
375
|
call.then =
|
|
373
376
|
`${part.toolName} ${toolLine(part.toolName, part.input)}`.trim();
|
|
377
|
+
if (part.toolName === TOOL_NAMES.ask_back)
|
|
378
|
+
questions.set(
|
|
379
|
+
part.toolCallId,
|
|
380
|
+
String((part.input as { question?: string }).question ?? ""),
|
|
381
|
+
);
|
|
382
|
+
} else if (
|
|
383
|
+
part.type === "tool-result" &&
|
|
384
|
+
part.toolName === TOOL_NAMES.ask_back
|
|
385
|
+
) {
|
|
386
|
+
const answer = resultParts(part.output, Number.POSITIVE_INFINITY)
|
|
387
|
+
.flatMap((part) => (part.type === "text" ? [part.text] : []))
|
|
388
|
+
.join("\n");
|
|
389
|
+
call.exchanges.push(
|
|
390
|
+
`They asked you: ${questions.get(part.toolCallId) ?? ""}\nYou answered: ${answer}`,
|
|
391
|
+
);
|
|
374
392
|
}
|
|
375
393
|
}
|
|
376
394
|
calls.set(row.parent, call);
|
|
@@ -380,6 +398,7 @@ export async function listBorrowedTails(
|
|
|
380
398
|
for (const [id, call] of calls) {
|
|
381
399
|
const tail = [
|
|
382
400
|
...call.texts.slice(-TAIL.texts).map((text) => clip(text, TAIL.chars)),
|
|
401
|
+
...call.exchanges.map((text) => clip(text, TAIL.chars)),
|
|
383
402
|
call.then ? `Then: ${call.then}` : "",
|
|
384
403
|
]
|
|
385
404
|
.filter(Boolean)
|
|
@@ -541,6 +560,63 @@ export async function listThread(taskId: string): Promise<ModelMessage[]> {
|
|
|
541
560
|
);
|
|
542
561
|
}
|
|
543
562
|
|
|
563
|
+
/** A participant keeps its own work across every request, including requests from different bots. */
|
|
564
|
+
export async function listBotThread(taskId: string, bot: string) {
|
|
565
|
+
const rows = await database
|
|
566
|
+
.select()
|
|
567
|
+
.from(taskMessageTable)
|
|
568
|
+
.where(
|
|
569
|
+
and(
|
|
570
|
+
eq(taskMessageTable.taskId, taskId),
|
|
571
|
+
isNotNull(taskMessageTable.parent),
|
|
572
|
+
eq(sql`lower(${taskMessageTable.bot})`, bot.toLowerCase()),
|
|
573
|
+
),
|
|
574
|
+
)
|
|
575
|
+
.orderBy(taskMessageTable.seq);
|
|
576
|
+
// Older runs store their opening only in the caller's tool arguments.
|
|
577
|
+
const requests = await database
|
|
578
|
+
.select({ content: taskMessageTable.content, bot: taskMessageTable.bot })
|
|
579
|
+
.from(taskMessageTable)
|
|
580
|
+
.where(
|
|
581
|
+
and(
|
|
582
|
+
eq(taskMessageTable.taskId, taskId),
|
|
583
|
+
eq(taskMessageTable.role, "assistant"),
|
|
584
|
+
),
|
|
585
|
+
)
|
|
586
|
+
.orderBy(taskMessageTable.seq);
|
|
587
|
+
const openings = new Map<string, ModelMessage>();
|
|
588
|
+
for (const row of requests) {
|
|
589
|
+
if (!Array.isArray(row.content)) continue;
|
|
590
|
+
for (const part of row.content) {
|
|
591
|
+
if (part.type !== "tool-call" || part.toolName !== TOOL_NAMES.ask_bot)
|
|
592
|
+
continue;
|
|
593
|
+
const input = part.input as { request?: string; context?: string };
|
|
594
|
+
openings.set(part.toolCallId, {
|
|
595
|
+
role: "user",
|
|
596
|
+
content: `${row.bot} → ${bot}:\n\n${input.request ?? ""}\n\n${input.context ?? ""}`,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
const history: { message: ModelMessage; compact: boolean }[] = [];
|
|
601
|
+
const seen = new Set<string>();
|
|
602
|
+
for (const row of rows) {
|
|
603
|
+
if (row.parent && !seen.has(row.parent)) {
|
|
604
|
+
seen.add(row.parent);
|
|
605
|
+
if (row.role !== "user" || row.compact) {
|
|
606
|
+
const opening = openings.get(row.parent);
|
|
607
|
+
if (opening) history.push({ message: opening, compact: false });
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
history.push({
|
|
611
|
+
message: { role: row.role, content: row.content } as ModelMessage,
|
|
612
|
+
compact: row.compact,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
const from = history.findLastIndex((row) => row.compact);
|
|
616
|
+
const kept = from > 0 ? [history[0], ...history.slice(from)] : history;
|
|
617
|
+
return kept.map((row) => row.message);
|
|
618
|
+
}
|
|
619
|
+
|
|
544
620
|
/** One row in the screen's shape; `pending` folds into `ask`. */
|
|
545
621
|
function viewOf(row: TaskRow, lines: TaskLine[]): Task {
|
|
546
622
|
// contextTokens and contextBudget pass through in `rest`
|
|
@@ -576,10 +652,33 @@ async function withLines(rows: TaskRow[]): Promise<Task[]> {
|
|
|
576
652
|
|
|
577
653
|
// ask_back lines name no addressee; the task's own bot is the addressee
|
|
578
654
|
const owners = new Map(rows.map((row) => [row.id, row.bot]));
|
|
655
|
+
const askers = new Map<string, string>();
|
|
656
|
+
for (const message of messages) {
|
|
657
|
+
if (message.role !== "assistant" || !Array.isArray(message.content))
|
|
658
|
+
continue;
|
|
659
|
+
for (const part of message.content) {
|
|
660
|
+
if (
|
|
661
|
+
part.type === "tool-call" &&
|
|
662
|
+
part.toolName === TOOL_NAMES.ask_bot &&
|
|
663
|
+
message.bot
|
|
664
|
+
) {
|
|
665
|
+
askers.set(`${message.taskId}:${part.toolCallId}`, message.bot);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
579
669
|
const byTask = new Map<string, TaskLine[]>();
|
|
580
670
|
for (const message of messages) {
|
|
581
671
|
const list = byTask.get(message.taskId) ?? [];
|
|
582
|
-
list.push(
|
|
672
|
+
list.push(
|
|
673
|
+
...linesOf(
|
|
674
|
+
message,
|
|
675
|
+
(message.parent
|
|
676
|
+
? askers.get(`${message.taskId}:${message.parent}`)
|
|
677
|
+
: null) ??
|
|
678
|
+
owners.get(message.taskId) ??
|
|
679
|
+
"",
|
|
680
|
+
),
|
|
681
|
+
);
|
|
583
682
|
byTask.set(message.taskId, list);
|
|
584
683
|
}
|
|
585
684
|
return rows.map((row) => viewOf(row, byTask.get(row.id) ?? []));
|
|
@@ -688,6 +787,13 @@ export async function readToolResult(
|
|
|
688
787
|
return null;
|
|
689
788
|
}
|
|
690
789
|
|
|
790
|
+
/** Said to a resumed run after why it stopped. The screen shows only the why (linesOf). */
|
|
791
|
+
const IN_FLIGHT =
|
|
792
|
+
"Anything that was under way — a command, a page loading, a download — may not have finished: check before relying on it, then carry on.";
|
|
793
|
+
|
|
794
|
+
/** The thread line a stop the app made leaves (bot.runner parkTask). */
|
|
795
|
+
export const stopNote = (why: string) => `${why} ${IN_FLIGHT}`;
|
|
796
|
+
|
|
691
797
|
type StoredMessage = typeof taskMessageTable.$inferSelect;
|
|
692
798
|
|
|
693
799
|
/**
|
|
@@ -707,14 +813,25 @@ function linesOf(message: StoredMessage, owner: string): TaskLine[] {
|
|
|
707
813
|
const content = message.content;
|
|
708
814
|
|
|
709
815
|
if (message.role === "user") {
|
|
710
|
-
|
|
816
|
+
// Participant requests already appear as the caller's ask_bot line.
|
|
817
|
+
if (
|
|
818
|
+
message.seq === 0 ||
|
|
819
|
+
(message.parent && !message.compact && !message.note)
|
|
820
|
+
)
|
|
821
|
+
return [];
|
|
711
822
|
const text = typeof content === "string" ? content : textOf(content);
|
|
712
823
|
if (!text) return [];
|
|
713
|
-
// The app's own
|
|
714
|
-
//
|
|
715
|
-
if (message.note
|
|
716
|
-
|
|
717
|
-
|
|
824
|
+
// The app's own lines, never the user's words. `compact` alone marks a
|
|
825
|
+
// compaction: rows written before `note` existed carry only that.
|
|
826
|
+
if (message.compact) return [{ ...base, id: id(0), kind: "note", text }];
|
|
827
|
+
if (message.note) {
|
|
828
|
+
const why = text.endsWith(IN_FLIGHT)
|
|
829
|
+
? text.slice(0, -IN_FLIGHT.length).trimEnd()
|
|
830
|
+
: text;
|
|
831
|
+
return [{ ...base, id: id(0), kind: "stop", text: why }];
|
|
832
|
+
}
|
|
833
|
+
// Tagged with who said it for the bot (bot.schema tagSpeaker); the screen draws the words
|
|
834
|
+
return [{ ...base, id: id(0), kind: "user", text: untagSpeaker(text) }];
|
|
718
835
|
}
|
|
719
836
|
|
|
720
837
|
if (message.role === "assistant") {
|
|
@@ -817,7 +934,12 @@ function linesOf(message: StoredMessage, owner: string): TaskLine[] {
|
|
|
817
934
|
const text = resultParts(part.output, RESULT_LINES)
|
|
818
935
|
.flatMap((result) => (result.type === "text" ? [result.text] : []))
|
|
819
936
|
.join("\n");
|
|
820
|
-
lines.push({
|
|
937
|
+
lines.push({
|
|
938
|
+
...base,
|
|
939
|
+
id: id(index),
|
|
940
|
+
kind: "user",
|
|
941
|
+
text: untagSpeaker(text),
|
|
942
|
+
});
|
|
821
943
|
} else if (
|
|
822
944
|
part.toolName === TOOL_NAMES.ask_bot ||
|
|
823
945
|
part.toolName === TOOL_NAMES.ask_back
|