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
|
@@ -6,9 +6,8 @@ import { splitFrontmatter } from "@/features/skills/skills.query";
|
|
|
6
6
|
import type { Sandbox } from "@/lib/sandbox";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* The tool set is built per run, so the `loaded` set lives in this closure.
|
|
9
|
+
* Reuse instructions only while their full text remains in this bot's context.
|
|
10
|
+
* A resume can carry them; compaction can remove them.
|
|
12
11
|
*/
|
|
13
12
|
export const createSkillTools = ({
|
|
14
13
|
skills,
|
|
@@ -17,7 +16,6 @@ export const createSkillTools = ({
|
|
|
17
16
|
skills: SkillMetadata[];
|
|
18
17
|
sandbox: Sandbox;
|
|
19
18
|
}) => {
|
|
20
|
-
const loaded = new Set<string>();
|
|
21
19
|
return {
|
|
22
20
|
[TOOL_NAMES.load_skill]: tool({
|
|
23
21
|
description:
|
|
@@ -29,7 +27,7 @@ export const createSkillTools = ({
|
|
|
29
27
|
"Exact name from the Skills list. Do not guess names that are not on it.",
|
|
30
28
|
),
|
|
31
29
|
}),
|
|
32
|
-
execute: async ({ name }) => {
|
|
30
|
+
execute: async ({ name }, { messages }) => {
|
|
33
31
|
const skill = skills.find(
|
|
34
32
|
(s) => s.name.toLowerCase() === name.toLowerCase(),
|
|
35
33
|
);
|
|
@@ -41,10 +39,30 @@ export const createSkillTools = ({
|
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
const skillFile = `${skill.path}/SKILL.md`;
|
|
44
|
-
|
|
42
|
+
const carried = messages.some(
|
|
43
|
+
(message) =>
|
|
44
|
+
Array.isArray(message.content) &&
|
|
45
|
+
message.content.some((part) => {
|
|
46
|
+
if (
|
|
47
|
+
part.type !== "tool-result" ||
|
|
48
|
+
part.toolName !== TOOL_NAMES.load_skill ||
|
|
49
|
+
part.output.type !== "json"
|
|
50
|
+
)
|
|
51
|
+
return false;
|
|
52
|
+
const value = part.output.value as {
|
|
53
|
+
skillDirectory?: string;
|
|
54
|
+
content?: string;
|
|
55
|
+
} | null;
|
|
56
|
+
return (
|
|
57
|
+
value?.skillDirectory === skill.path &&
|
|
58
|
+
typeof value.content === "string"
|
|
59
|
+
);
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
if (carried) {
|
|
45
63
|
return {
|
|
46
64
|
skillDirectory: skill.path,
|
|
47
|
-
note: `
|
|
65
|
+
note: `Use the full '${skill.name}' instructions already in your conversation. To check for changes on disk, read ${skillFile} with the shell.`,
|
|
48
66
|
};
|
|
49
67
|
}
|
|
50
68
|
|
|
@@ -55,7 +73,6 @@ export const createSkillTools = ({
|
|
|
55
73
|
path: skill.path,
|
|
56
74
|
limit: 50,
|
|
57
75
|
});
|
|
58
|
-
loaded.add(skill.path);
|
|
59
76
|
|
|
60
77
|
return {
|
|
61
78
|
skillDirectory: skill.path,
|
|
@@ -18,6 +18,8 @@ export const createWorkspaceTools = (
|
|
|
18
18
|
env?: Record<string, string>;
|
|
19
19
|
/** Attach the shell guide to this run's first `bash` result (bots only). */
|
|
20
20
|
guide?: boolean;
|
|
21
|
+
/** How long one command may run; unset is the sandbox's own limit (config EXEC_TIMEOUT_MS). */
|
|
22
|
+
timeoutMs?: number;
|
|
21
23
|
},
|
|
22
24
|
): ToolSet => {
|
|
23
25
|
/** Fold absolute sandbox paths against cwd; they still resolve when handed back. */
|
|
@@ -45,6 +47,7 @@ export const createWorkspaceTools = (
|
|
|
45
47
|
const result = await sandbox.exec(command, {
|
|
46
48
|
signal: abortSignal,
|
|
47
49
|
env: options.env,
|
|
50
|
+
timeoutMs: options.timeoutMs,
|
|
48
51
|
});
|
|
49
52
|
if (!owed) return result;
|
|
50
53
|
owed = false;
|
|
@@ -110,7 +110,12 @@ export const startTaskAction = serverAction(
|
|
|
110
110
|
const worker = await findBot(bot);
|
|
111
111
|
if (!worker) publicError(`No bot called "${bot}".`);
|
|
112
112
|
const label = labelFor(said);
|
|
113
|
-
const id = await startTask({
|
|
113
|
+
const id = await startTask({
|
|
114
|
+
bot: worker.name,
|
|
115
|
+
request: said,
|
|
116
|
+
label,
|
|
117
|
+
from: "user",
|
|
118
|
+
});
|
|
114
119
|
return { id, label, bot: worker.name };
|
|
115
120
|
},
|
|
116
121
|
);
|
package/features/bot/bot.run.ts
CHANGED
|
@@ -41,17 +41,21 @@ import {
|
|
|
41
41
|
type TokenUsage,
|
|
42
42
|
} from "@/features/bot/bot.schema";
|
|
43
43
|
import {
|
|
44
|
-
|
|
44
|
+
botBrowserSession,
|
|
45
45
|
filesOnDisk,
|
|
46
46
|
openBotFolder,
|
|
47
47
|
openJobScratch,
|
|
48
48
|
} from "@/features/workspace/workspace";
|
|
49
49
|
import { logger } from "@/lib/logger";
|
|
50
50
|
import { publicError } from "@/lib/public-error";
|
|
51
|
+
import { createKeyedLock } from "@/lib/queue";
|
|
51
52
|
import { estimateTokens } from "@/lib/tokens";
|
|
53
|
+
import { PromiseChain } from "@/lib/utils";
|
|
52
54
|
import { findJobBot } from "./bot.query";
|
|
53
55
|
import {
|
|
54
56
|
findTask,
|
|
57
|
+
listBorrowedTails,
|
|
58
|
+
listBotThread,
|
|
55
59
|
listWrittenPaths,
|
|
56
60
|
optionsOf,
|
|
57
61
|
writtenPathsIn,
|
|
@@ -65,6 +69,11 @@ import {
|
|
|
65
69
|
|
|
66
70
|
/** One thing that happened in a run. The runner adds which bot and inside which `ask_bot` call. */
|
|
67
71
|
export type BotEvent =
|
|
72
|
+
/** A request enters this participant's own thread before its model runs. */
|
|
73
|
+
| {
|
|
74
|
+
type: "request";
|
|
75
|
+
content: Extract<ModelMessage, { role: "user" }>["content"];
|
|
76
|
+
}
|
|
68
77
|
/** A finished chunk of prose. */
|
|
69
78
|
| { type: "text"; text: string }
|
|
70
79
|
/** A tool call. `id` is where the result lands. */
|
|
@@ -147,6 +156,8 @@ export type RunOptions = {
|
|
|
147
156
|
* in one window.
|
|
148
157
|
*/
|
|
149
158
|
session?: string | null;
|
|
159
|
+
/** Shared by every participant in this live segment; history itself lives in the DB. */
|
|
160
|
+
desks?: ReturnType<typeof createBotDesks>;
|
|
150
161
|
/**
|
|
151
162
|
* Drained once per step boundary; each string goes in front of the model as
|
|
152
163
|
* a user turn before the next step. Only the job's own bot has one.
|
|
@@ -189,11 +200,11 @@ export async function runBot(
|
|
|
189
200
|
): Promise<void> {
|
|
190
201
|
const depth = options.depth ?? 0;
|
|
191
202
|
const parent = options.parent ?? null;
|
|
192
|
-
const
|
|
203
|
+
const bot = await findJobBot(input.bot.trim());
|
|
204
|
+
const name = bot?.name ?? input.bot.trim();
|
|
193
205
|
const emit = (event: BotEvent) =>
|
|
194
206
|
options.emit({ ...event, bot: name, parent });
|
|
195
207
|
|
|
196
|
-
const bot = await findJobBot(name);
|
|
197
208
|
if (!bot) {
|
|
198
209
|
await emit({
|
|
199
210
|
type: "error",
|
|
@@ -205,7 +216,11 @@ export async function runBot(
|
|
|
205
216
|
// Switched off is not a bot to pick — the backstop for every fresh start
|
|
206
217
|
// (`delegate`, `ask_bot`, the screen). A resume carries `messages` instead and
|
|
207
218
|
// is let through: its thread already exists and the user can still answer it.
|
|
208
|
-
|
|
219
|
+
const previous =
|
|
220
|
+
"askedBy" in input && options.taskId
|
|
221
|
+
? await listBotThread(options.taskId, name)
|
|
222
|
+
: [];
|
|
223
|
+
if (bot.disabled && "request" in input && !previous.length) {
|
|
209
224
|
await emit({
|
|
210
225
|
type: "error",
|
|
211
226
|
message: `${bot.name} is switched off. Use a name from the list.`,
|
|
@@ -214,6 +229,30 @@ export async function runBot(
|
|
|
214
229
|
return;
|
|
215
230
|
}
|
|
216
231
|
|
|
232
|
+
options = { ...options, desks: options.desks ?? createBotDesks() };
|
|
233
|
+
let history: ModelMessage[];
|
|
234
|
+
if ("messages" in input) {
|
|
235
|
+
history = input.messages;
|
|
236
|
+
} else {
|
|
237
|
+
const content = previous.length
|
|
238
|
+
? `## ${input.askedBy} → ${name}: the next request\n\n${input.request.trim()}${input.context?.trim() ? `\n\n## What ${input.askedBy} passes on\n\n${input.context.trim()}` : ""}`
|
|
239
|
+
: buildHandoff({
|
|
240
|
+
chain: input.chain,
|
|
241
|
+
bots: [...input.above, name],
|
|
242
|
+
did: input.context ?? null,
|
|
243
|
+
part: input.request,
|
|
244
|
+
});
|
|
245
|
+
history = [
|
|
246
|
+
...resumeThread(
|
|
247
|
+
previous,
|
|
248
|
+
options.taskId ? await listBorrowedTails(options.taskId) : undefined,
|
|
249
|
+
),
|
|
250
|
+
{ role: "user", content },
|
|
251
|
+
];
|
|
252
|
+
options.signal?.throwIfAborted();
|
|
253
|
+
await emit({ type: "request", content });
|
|
254
|
+
}
|
|
255
|
+
|
|
217
256
|
// Tools are built on the model: web search runs on this bot's model (load-tools).
|
|
218
257
|
const model = await resolveModel(bot);
|
|
219
258
|
// The job's row: the same one for every seat on the job, a borrowed bot's too
|
|
@@ -254,21 +293,6 @@ export async function runBot(
|
|
|
254
293
|
("askedBy" in input ? 0 : row?.contextBudget) || Number.POSITIVE_INFINITY,
|
|
255
294
|
);
|
|
256
295
|
|
|
257
|
-
const history: ModelMessage[] =
|
|
258
|
-
"messages" in input
|
|
259
|
-
? input.messages
|
|
260
|
-
: [
|
|
261
|
-
{
|
|
262
|
-
role: "user",
|
|
263
|
-
content: buildHandoff({
|
|
264
|
-
chain: input.chain,
|
|
265
|
-
bots: [...input.above, name],
|
|
266
|
-
did: input.context ?? null,
|
|
267
|
-
part: input.request,
|
|
268
|
-
}),
|
|
269
|
-
},
|
|
270
|
-
];
|
|
271
|
-
|
|
272
296
|
/** Remaining `ask_back` calls for a borrowed bot; the tool counts it down. */
|
|
273
297
|
const asks = { left: BOT_RUN.askBack };
|
|
274
298
|
// A borrowed bot asks back instead of asking Thursday. Any seat above the
|
|
@@ -344,6 +368,7 @@ export async function runBot(
|
|
|
344
368
|
const summary = await compact(model.model, agentTools, messages, {
|
|
345
369
|
signal: options.signal,
|
|
346
370
|
budget,
|
|
371
|
+
instructions: prompt.text,
|
|
347
372
|
}).finally(quiet.release);
|
|
348
373
|
logger.debug(
|
|
349
374
|
`compacted ${messages.length} messages at ${size} tokens (budget ${budget})`,
|
|
@@ -701,17 +726,19 @@ async function filesUnder(
|
|
|
701
726
|
/**
|
|
702
727
|
* The model summarizes its own context and continues from the summary. Tools
|
|
703
728
|
* are passed with calling off: providers refuse a history of tool calls
|
|
704
|
-
* without the tools that made them.
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
729
|
+
* without the tools that made them. The run's own instructions go too: the
|
|
730
|
+
* summary is written by the bot the thread belongs to, and the provider's
|
|
731
|
+
* cached prefix, instructions first, still matches the run's. A context too
|
|
732
|
+
* long to summarise whole — the very thing a compaction is for — is tried once
|
|
733
|
+
* more without the tool calls and results but the last few. A failure throws
|
|
734
|
+
* with its cause kept: the runner parks what a retry or a smaller budget can
|
|
735
|
+
* fix (bot.runner parkTask) and fails the rest; the thread stays.
|
|
709
736
|
*/
|
|
710
737
|
async function compact(
|
|
711
738
|
model: LanguageModel,
|
|
712
739
|
tools: ToolSet,
|
|
713
740
|
messages: ModelMessage[],
|
|
714
|
-
options: { signal?: AbortSignal; budget: number },
|
|
741
|
+
options: { signal?: AbortSignal; budget: number; instructions: string },
|
|
715
742
|
): Promise<{ text: string; usage: TokenUsage }> {
|
|
716
743
|
let failure: unknown;
|
|
717
744
|
try {
|
|
@@ -745,10 +772,11 @@ async function summarize(
|
|
|
745
772
|
model: LanguageModel,
|
|
746
773
|
tools: ToolSet,
|
|
747
774
|
messages: ModelMessage[],
|
|
748
|
-
options: { signal?: AbortSignal; budget: number },
|
|
775
|
+
options: { signal?: AbortSignal; budget: number; instructions: string },
|
|
749
776
|
): Promise<{ text: string; usage: TokenUsage }> {
|
|
750
777
|
const { text, usage } = await generateText({
|
|
751
778
|
model,
|
|
779
|
+
system: options.instructions,
|
|
752
780
|
tools,
|
|
753
781
|
toolChoice: "none",
|
|
754
782
|
messages: [
|
|
@@ -875,15 +903,20 @@ export async function runBotToText(
|
|
|
875
903
|
await runBot(input, {
|
|
876
904
|
...options,
|
|
877
905
|
emit: async (event) => {
|
|
878
|
-
if (
|
|
906
|
+
if (
|
|
907
|
+
event.parent === (options.parent ?? null) &&
|
|
908
|
+
event.type === "answer"
|
|
909
|
+
) {
|
|
879
910
|
outcome = event.stopped
|
|
880
911
|
? `${event.text}\n\n(As far as they got — they stopped before finishing.)`
|
|
881
912
|
: event.text;
|
|
882
913
|
}
|
|
883
|
-
if (event.type === "error")
|
|
914
|
+
if (event.parent === (options.parent ?? null) && event.type === "error")
|
|
915
|
+
failure = event.message;
|
|
884
916
|
await options.emit(event);
|
|
885
917
|
},
|
|
886
918
|
});
|
|
919
|
+
options.signal?.throwIfAborted();
|
|
887
920
|
if (failure) publicError(failure);
|
|
888
921
|
return outcome;
|
|
889
922
|
}
|
|
@@ -914,6 +947,11 @@ function withAskBot(
|
|
|
914
947
|
},
|
|
915
948
|
): ToolSet {
|
|
916
949
|
if (asker.peers.length === 0) return tools;
|
|
950
|
+
// Calls made in the same step answer from one evolving conversation.
|
|
951
|
+
const conversations = new WeakMap<
|
|
952
|
+
ModelMessage[],
|
|
953
|
+
{ messages: ModelMessage[]; answer: ReturnType<typeof PromiseChain> }
|
|
954
|
+
>();
|
|
917
955
|
|
|
918
956
|
const agentTools: ToolSet = {
|
|
919
957
|
...tools,
|
|
@@ -921,66 +959,143 @@ function withAskBot(
|
|
|
921
959
|
description: askBotSpec.description,
|
|
922
960
|
inputSchema: askBotSpec.parameters,
|
|
923
961
|
execute: async ({ bot, request, context }, call) => {
|
|
924
|
-
|
|
962
|
+
const found = await findJobBot(bot);
|
|
963
|
+
if (!found) return `No bot named "${bot}". Use a name from the list.`;
|
|
964
|
+
bot = found.name;
|
|
925
965
|
if (bot === asker.name || asker.above.includes(bot)) {
|
|
926
|
-
return `${bot} is already
|
|
927
|
-
}
|
|
928
|
-
// The borrowed bot works in the job's folder but drives a browser session
|
|
929
|
-
// of its own, under this call's id. When it answers, the same rule as a
|
|
930
|
-
// job's end: what it showed on their screen stays, what nobody can see
|
|
931
|
-
// closes (workspace.ts closeHiddenBrowser).
|
|
932
|
-
const session = asker.options.taskId
|
|
933
|
-
? `${asker.options.session ?? asker.options.taskId}-${call.toolCallId.slice(0, 8)}`
|
|
934
|
-
: null;
|
|
935
|
-
const exchanges: { question: string; answer: string }[] = [];
|
|
936
|
-
const answer = async (question: string) => {
|
|
937
|
-
const reply = await answerBack({
|
|
938
|
-
model: asker.model,
|
|
939
|
-
instructions: asker.instructions,
|
|
940
|
-
// Same tools, calling off (see compact).
|
|
941
|
-
tools: agentTools,
|
|
942
|
-
messages: call.messages,
|
|
943
|
-
asked: { bot, request },
|
|
944
|
-
question,
|
|
945
|
-
signal: asker.options.signal,
|
|
946
|
-
});
|
|
947
|
-
await asker.emit({ type: "answered", question, ...reply });
|
|
948
|
-
exchanges.push({ question, answer: reply.text });
|
|
949
|
-
return reply.text;
|
|
950
|
-
};
|
|
951
|
-
try {
|
|
952
|
-
const outcome = await runBotToText(
|
|
953
|
-
{
|
|
954
|
-
bot,
|
|
955
|
-
request,
|
|
956
|
-
context,
|
|
957
|
-
askedBy: asker.name,
|
|
958
|
-
chain: asker.chain,
|
|
959
|
-
above: [...asker.above, asker.name],
|
|
960
|
-
},
|
|
961
|
-
{
|
|
962
|
-
...asker.options,
|
|
963
|
-
// This call's signal, not the job's: a stream that ends — the job
|
|
964
|
-
// stopping, or the model going quiet — ends the part it waited on
|
|
965
|
-
signal: call.abortSignal ?? asker.options.signal,
|
|
966
|
-
depth: asker.depth + 1,
|
|
967
|
-
parent: call.toolCallId,
|
|
968
|
-
session,
|
|
969
|
-
answer,
|
|
970
|
-
},
|
|
971
|
-
);
|
|
972
|
-
return exchanges.length
|
|
973
|
-
? `${outcome}\n\n${exchangeLines(bot, exchanges)}`
|
|
974
|
-
: outcome;
|
|
975
|
-
} finally {
|
|
976
|
-
if (session) void closeHiddenBrowser(session);
|
|
966
|
+
return `${bot} is already above you and waiting on this part. Use ask_back for a question, or pick another bot.`;
|
|
977
967
|
}
|
|
968
|
+
const signal = call.abortSignal ?? asker.options.signal;
|
|
969
|
+
const desks = asker.options.desks!;
|
|
970
|
+
return desks.run(asker.name, bot, signal, async () => {
|
|
971
|
+
const session = asker.options.taskId
|
|
972
|
+
? botBrowserSession(asker.options.taskId, bot)
|
|
973
|
+
: null;
|
|
974
|
+
const exchanges: { question: string; answer: string }[] = [];
|
|
975
|
+
let conversation = conversations.get(call.messages);
|
|
976
|
+
if (!conversation) {
|
|
977
|
+
conversation = {
|
|
978
|
+
messages: [...call.messages],
|
|
979
|
+
answer: PromiseChain(),
|
|
980
|
+
};
|
|
981
|
+
conversations.set(call.messages, conversation);
|
|
982
|
+
}
|
|
983
|
+
const current = conversation;
|
|
984
|
+
const answer = (question: string) =>
|
|
985
|
+
current.answer(async () => {
|
|
986
|
+
signal?.throwIfAborted();
|
|
987
|
+
const reply = await answerBack({
|
|
988
|
+
model: asker.model,
|
|
989
|
+
instructions: asker.instructions,
|
|
990
|
+
tools: agentTools,
|
|
991
|
+
messages: current.messages,
|
|
992
|
+
asked: { bot, request },
|
|
993
|
+
question,
|
|
994
|
+
signal,
|
|
995
|
+
});
|
|
996
|
+
current.messages.push(
|
|
997
|
+
{
|
|
998
|
+
role: "user",
|
|
999
|
+
content: `${bot} asks about the part you handed over:\n\n${question}`,
|
|
1000
|
+
},
|
|
1001
|
+
{ role: "assistant", content: reply.text },
|
|
1002
|
+
);
|
|
1003
|
+
await asker.emit({ type: "answered", question, ...reply });
|
|
1004
|
+
exchanges.push({ question, answer: reply.text });
|
|
1005
|
+
return reply.text;
|
|
1006
|
+
});
|
|
1007
|
+
try {
|
|
1008
|
+
const outcome = await runBotToText(
|
|
1009
|
+
{
|
|
1010
|
+
bot,
|
|
1011
|
+
request,
|
|
1012
|
+
context,
|
|
1013
|
+
askedBy: asker.name,
|
|
1014
|
+
chain: asker.chain,
|
|
1015
|
+
above: [...asker.above, asker.name],
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
...asker.options,
|
|
1019
|
+
signal,
|
|
1020
|
+
depth: asker.depth + 1,
|
|
1021
|
+
parent: call.toolCallId,
|
|
1022
|
+
session,
|
|
1023
|
+
// Only the task's owner drains words sent by the user.
|
|
1024
|
+
notes: undefined,
|
|
1025
|
+
answer,
|
|
1026
|
+
},
|
|
1027
|
+
);
|
|
1028
|
+
return exchanges.length
|
|
1029
|
+
? `${outcome}\n\n${exchangeLines(bot, exchanges)}`
|
|
1030
|
+
: outcome;
|
|
1031
|
+
} catch (cause) {
|
|
1032
|
+
signal?.throwIfAborted();
|
|
1033
|
+
publicError(
|
|
1034
|
+
[modelErrorToString(cause), exchangeLines(bot, exchanges)]
|
|
1035
|
+
.filter(Boolean)
|
|
1036
|
+
.join("\n\n"),
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
978
1040
|
},
|
|
979
1041
|
}),
|
|
980
1042
|
};
|
|
981
1043
|
return agentTools;
|
|
982
1044
|
}
|
|
983
1045
|
|
|
1046
|
+
/** One execution per participant, with wait-cycle detection across concurrent branches. */
|
|
1047
|
+
export function createBotDesks() {
|
|
1048
|
+
const lock = createKeyedLock();
|
|
1049
|
+
const waiting = new Map<string, Map<string, number>>();
|
|
1050
|
+
const pending = new Set<Promise<unknown>>();
|
|
1051
|
+
const reaches = (
|
|
1052
|
+
from: string,
|
|
1053
|
+
to: string,
|
|
1054
|
+
seen = new Set<string>(),
|
|
1055
|
+
): boolean => {
|
|
1056
|
+
if (from === to) return true;
|
|
1057
|
+
if (seen.has(from)) return false;
|
|
1058
|
+
seen.add(from);
|
|
1059
|
+
return [...(waiting.get(from)?.keys() ?? [])].some((next) =>
|
|
1060
|
+
reaches(next, to, seen),
|
|
1061
|
+
);
|
|
1062
|
+
};
|
|
1063
|
+
return {
|
|
1064
|
+
async run<T>(
|
|
1065
|
+
from: string,
|
|
1066
|
+
to: string,
|
|
1067
|
+
signal: AbortSignal | undefined,
|
|
1068
|
+
work: () => Promise<T>,
|
|
1069
|
+
): Promise<T> {
|
|
1070
|
+
signal?.throwIfAborted();
|
|
1071
|
+
if (reaches(to, from))
|
|
1072
|
+
publicError(
|
|
1073
|
+
`${to} is waiting on your work. Finish this part before asking them, or choose another bot.`,
|
|
1074
|
+
);
|
|
1075
|
+
const edges = waiting.get(from) ?? new Map<string, number>();
|
|
1076
|
+
waiting.set(from, edges);
|
|
1077
|
+
edges.set(to, (edges.get(to) ?? 0) + 1);
|
|
1078
|
+
const run = lock(to, async () => {
|
|
1079
|
+
signal?.throwIfAborted();
|
|
1080
|
+
return work();
|
|
1081
|
+
});
|
|
1082
|
+
pending.add(run);
|
|
1083
|
+
try {
|
|
1084
|
+
return await run;
|
|
1085
|
+
} finally {
|
|
1086
|
+
pending.delete(run);
|
|
1087
|
+
const left = (edges.get(to) ?? 1) - 1;
|
|
1088
|
+
if (left) edges.set(to, left);
|
|
1089
|
+
else edges.delete(to);
|
|
1090
|
+
if (!edges.size) waiting.delete(from);
|
|
1091
|
+
}
|
|
1092
|
+
},
|
|
1093
|
+
async settle() {
|
|
1094
|
+
while (pending.size) await Promise.allSettled([...pending]);
|
|
1095
|
+
},
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
|
|
984
1099
|
/** Appended under the answer: what the borrowed bot asked and what this bot answered. */
|
|
985
1100
|
function exchangeLines(
|
|
986
1101
|
bot: string,
|