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
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import type { ModelMessage } from "ai";
|
|
2
|
+
import { format } from "date-fns";
|
|
2
3
|
import { PATHS, PROMPT_LINE, WORKSPACE_KEEP } from "@/config";
|
|
3
4
|
import { TOOL_NAMES } from "@/features/ai/tools/tool-name";
|
|
4
5
|
import { botMemoryFolder, listBotMemory } from "@/features/bot/bot.memory";
|
|
5
6
|
import { listJobBots, readBotMemoryOn } from "@/features/bot/bot.query";
|
|
6
|
-
import type { BotMemory, JobBot } from "@/features/bot/bot.schema";
|
|
7
|
+
import type { BotMemory, JobBot, TaskSpeaker } from "@/features/bot/bot.schema";
|
|
7
8
|
import { findPinnedTools } from "@/features/connectors/mcp.query";
|
|
8
9
|
import type { McpToolRef } from "@/features/connectors/mcp.schema";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
listNoteIndex,
|
|
12
|
-
} from "@/features/memory/memory.query";
|
|
13
|
-
import type {
|
|
14
|
-
MemoryAlwaysLoaded,
|
|
15
|
-
MemoryIndexEntry,
|
|
16
|
-
} from "@/features/memory/memory.schema";
|
|
10
|
+
import { listNoteIndex } from "@/features/memory/memory.query";
|
|
11
|
+
import type { MemoryIndexEntry } from "@/features/memory/memory.schema";
|
|
17
12
|
import {
|
|
18
13
|
loadSkills,
|
|
19
14
|
type SkillMetadata,
|
|
@@ -23,11 +18,11 @@ import {
|
|
|
23
18
|
openWorkspace,
|
|
24
19
|
readMachineTools,
|
|
25
20
|
} from "@/features/workspace/workspace";
|
|
21
|
+
import { toDate } from "@/lib/date-like";
|
|
26
22
|
import { logger } from "@/lib/logger";
|
|
27
23
|
import { clip } from "@/lib/utils";
|
|
28
24
|
import { listConnectedToolNames } from "../tools/connected";
|
|
29
25
|
import {
|
|
30
|
-
carriedLines,
|
|
31
26
|
type LoadedPrompt,
|
|
32
27
|
logPromptSize,
|
|
33
28
|
mcpToolLines,
|
|
@@ -57,31 +52,21 @@ export async function loadBotPrompt(
|
|
|
57
52
|
const sandbox = await openWorkspace();
|
|
58
53
|
const name = self.trim();
|
|
59
54
|
const askedBy = seat?.askedBy ?? null;
|
|
60
|
-
const [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
listConnectedToolNames(),
|
|
76
|
-
// MCP tools this bot already holds; dropped from the listing below
|
|
77
|
-
findPinnedTools(name),
|
|
78
|
-
listJobBots(),
|
|
79
|
-
// What this bot kept on earlier jobs, read off its own folder (bot.memory)
|
|
80
|
-
listBotMemory(name),
|
|
81
|
-
readBotMemoryOn(),
|
|
82
|
-
// One `command -v` sweep; what is here decides the first command (environment)
|
|
83
|
-
readMachineTools(sandbox),
|
|
84
|
-
]);
|
|
55
|
+
const [skills, index, mcpTools, pinned, allBots, kept, memoryOn, machine] =
|
|
56
|
+
await Promise.all([
|
|
57
|
+
loadSkills(sandbox),
|
|
58
|
+
listNoteIndex(),
|
|
59
|
+
// User-connected servers and the app's studio in one list (tools/connected)
|
|
60
|
+
listConnectedToolNames(),
|
|
61
|
+
// MCP tools this bot already holds; dropped from the listing below
|
|
62
|
+
findPinnedTools(name),
|
|
63
|
+
listJobBots(),
|
|
64
|
+
// What this bot kept on earlier jobs, read off its own folder (bot.memory)
|
|
65
|
+
listBotMemory(name),
|
|
66
|
+
readBotMemoryOn(),
|
|
67
|
+
// One `command -v` sweep; what is here decides the first command (environment)
|
|
68
|
+
readMachineTools(sandbox),
|
|
69
|
+
]);
|
|
85
70
|
|
|
86
71
|
// Drop itself and every bot above it on this job, which are blocked waiting on
|
|
87
72
|
// it; the last seat the depth allows has no roster at all (config BOT_RUN.depth)
|
|
@@ -94,11 +79,12 @@ export async function loadBotPrompt(
|
|
|
94
79
|
|
|
95
80
|
const text = [
|
|
96
81
|
askedBy ? borrowedIdentity(name, askedBy) : identity(name),
|
|
97
|
-
memory(index
|
|
98
|
-
memoryOn ? ownMemory(botMemoryFolder(name), kept) : "",
|
|
82
|
+
memory(index),
|
|
99
83
|
connectedTools(mcpTools, pinned),
|
|
100
84
|
methods(skills),
|
|
101
85
|
environment(sandbox.cwd, machine, folders),
|
|
86
|
+
// After Environment: its folder is named against the Cwd said there
|
|
87
|
+
memoryOn ? ownMemory(botMemoryFolder(name), kept) : "",
|
|
102
88
|
roster(peers),
|
|
103
89
|
askedBy ? askingBack(askedBy) : ASKING,
|
|
104
90
|
askedBy ? handingUp(askedBy) : ANSWERING,
|
|
@@ -118,20 +104,37 @@ export async function loadBotPrompt(
|
|
|
118
104
|
};
|
|
119
105
|
}
|
|
120
106
|
|
|
121
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* The two people every seat works between. Said before anything else, so the
|
|
109
|
+
* rest of the prompt — whose memory, who reads the answer — has someone to refer to.
|
|
110
|
+
*/
|
|
111
|
+
const PEOPLE = `- **The user** — the one person all of this is for. They talk with Thursday by voice, and they follow this job on their screen.
|
|
112
|
+
- **Thursday** — their own personal assistant, one to one. She holds the conversation with them, hands bots the work that takes time, and tells them what comes back.`;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Who is who, what machine it is on, and that guesses are not results. How this
|
|
116
|
+
* job reached it is the first message's to say (buildTaskOpening).
|
|
117
|
+
*/
|
|
122
118
|
function identity(name: string): string {
|
|
123
119
|
return [
|
|
124
120
|
// Named, because the owner's prompt may not name it and its own instructions are addressed to it
|
|
125
|
-
`You are ${name},
|
|
121
|
+
`You are ${name}, one of the bots that work for the user. ${nowLine()}
|
|
122
|
+
|
|
123
|
+
${PEOPLE}
|
|
124
|
+
- **You** — you do the work. Your answer goes to Thursday, not to the user, and she tells them what they need from it.`,
|
|
126
125
|
MACHINE,
|
|
127
126
|
NO_GUESSING,
|
|
128
127
|
].join("\n\n");
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
/** The borrowed seat:
|
|
130
|
+
/** The borrowed seat: between it and Thursday is the bot that borrowed it. */
|
|
132
131
|
function borrowedIdentity(name: string, askedBy: string): string {
|
|
133
132
|
return [
|
|
134
|
-
`You are ${name},
|
|
133
|
+
`You are ${name}, one of the bots that work for the user. ${nowLine()}
|
|
134
|
+
|
|
135
|
+
${PEOPLE}
|
|
136
|
+
- **${askedBy}** — a bot on this job, who handed you one part of it. They cannot see your thread.
|
|
137
|
+
- **You** — you do that part. What you hand back goes into ${askedBy}'s work as it is, never to Thursday or the user.`,
|
|
135
138
|
MACHINE,
|
|
136
139
|
NO_GUESSING,
|
|
137
140
|
].join("\n\n");
|
|
@@ -159,44 +162,34 @@ Written by the person this bot works for. Where these and anything above disagre
|
|
|
159
162
|
${persona.trim()}`
|
|
160
163
|
: "";
|
|
161
164
|
|
|
162
|
-
/**
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
What Thursday knows about the user, kept as she talks with them. Every bot reads it; none writes to it. Open a note with \`${TOOL_NAMES.memory_recall}\` when the job needs something about them — the address they sign in with, the name to book under — and put what you find out about them in your answer; she decides what to keep. It describes them, not you: how they are named and spoken to is hers to use. What you learn about the work goes in your own memory, below.`;
|
|
170
|
-
|
|
171
|
-
const alreadyKnown = carried.length
|
|
172
|
-
? `Already known:
|
|
173
|
-
|
|
174
|
-
${carriedLines(carried)}`
|
|
175
|
-
: "";
|
|
176
|
-
|
|
177
|
-
const listing = `path — what is under it (facts) "what the user calls it"
|
|
165
|
+
/**
|
|
166
|
+
* Thursday's memory, which a bot only reads (load-tools). The listing alone: the
|
|
167
|
+
* lines she carries into every call are written for her — what to call them, how
|
|
168
|
+
* long an answer runs — and are hers to act on, not a bot's.
|
|
169
|
+
*/
|
|
170
|
+
function memory(index: MemoryIndexEntry[]): string {
|
|
171
|
+
return `## Thursday's memory of the user
|
|
178
172
|
|
|
179
|
-
|
|
173
|
+
What Thursday keeps from talking with them. Open a note from the listing with \`${TOOL_NAMES.memory_recall}\` when the job needs something about them; what you find out about them goes in your answer, and she keeps what matters.
|
|
180
174
|
|
|
181
|
-
|
|
175
|
+
path — what is under it (facts) "what the user calls it"
|
|
182
176
|
|
|
183
|
-
|
|
177
|
+
${noteLines(index)}`;
|
|
184
178
|
}
|
|
185
179
|
|
|
186
180
|
/**
|
|
187
|
-
* The bot's own memory (features/bot/bot.memory), listed by each file's first line
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* or not the job taught it anything.
|
|
181
|
+
* The bot's own memory (features/bot/bot.memory), listed by each file's first line and the day
|
|
182
|
+
* it last changed, both read off the disk, so what a file holds costs one line until a job opens
|
|
183
|
+
* it. Always drawn, so a first job knows it has one. Nothing says what usually goes in first: a
|
|
184
|
+
* line like that is what a first job writes, whether or not the job taught it anything.
|
|
191
185
|
*/
|
|
192
186
|
function ownMemory(folder: string, kept: BotMemory): string {
|
|
193
187
|
const listing = kept.entries.length
|
|
194
188
|
? kept.entries
|
|
195
|
-
.map(({ file, line }) =>
|
|
196
|
-
line
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
)
|
|
189
|
+
.map(({ file, line, at }) => {
|
|
190
|
+
const said = line ? ` — ${clip(line, PROMPT_LINE.botMemory)}` : "";
|
|
191
|
+
return `- ${file}${said} · ${format(toDate(at), "yyyy-MM-dd")}`;
|
|
192
|
+
})
|
|
200
193
|
.join("\n")
|
|
201
194
|
: "Empty.";
|
|
202
195
|
const rest =
|
|
@@ -206,7 +199,7 @@ function ownMemory(folder: string, kept: BotMemory): string {
|
|
|
206
199
|
|
|
207
200
|
return `## Your memory
|
|
208
201
|
|
|
209
|
-
What you kept from your own earlier jobs, in \`${folder}
|
|
202
|
+
What you kept from your own earlier jobs, in \`${folder}/\` under the Cwd above: one topic per file, its first line saying what it holds, read by no other bot. Keep what a later job would otherwise have to find out again — how a site signs in, the way through its screens, a command that turned out right — with the date you found it true, and fix or delete a file that proved wrong. No passwords, keys or codes.
|
|
210
203
|
|
|
211
204
|
${listing}${rest}`;
|
|
212
205
|
}
|
|
@@ -239,7 +232,7 @@ function methods(skills: SkillMetadata[]): string {
|
|
|
239
232
|
|
|
240
233
|
return `## Skills
|
|
241
234
|
|
|
242
|
-
Written-down methods. When the job
|
|
235
|
+
Written-down methods. Use the instructions already in your conversation. When the job needs a skill whose full instructions are absent, read it with \`${TOOL_NAMES.load_skill}\` before starting.
|
|
243
236
|
|
|
244
237
|
${skillLines(skills)}`;
|
|
245
238
|
}
|
|
@@ -314,7 +307,7 @@ const ANSWERING = `## Answering
|
|
|
314
307
|
|
|
315
308
|
Every job ends with \`${TOOL_NAMES.answer}\`. **Answer what was asked, at the size it was asked**: a question ends in its answer — one number is one line — and a thing to make comes back made, an action done, photos in a page, a comparison in a table. Once you have what they asked for the job is done; the next thing you would go and check is theirs to ask for.
|
|
316
309
|
|
|
317
|
-
You are answering Thursday, not the user: she
|
|
310
|
+
You are answering Thursday, not the user: she picks what to say out of this. Write it plainly, in the user's language — the thread is on their screen as you write it. Anything past a few lines is a file under \`${PATHS.artifacts}/\`, and the answer names its path — it becomes a link on their screen. In a \`.md\`, images only by absolute route (\`/api/file/${PATHS.artifacts}/…\`).`;
|
|
318
311
|
|
|
319
312
|
const handingUp = (askedBy: string) => `## Answering
|
|
320
313
|
|
|
@@ -329,19 +322,22 @@ export type OpeningContent = Extract<ModelMessage, { role: "user" }>["content"];
|
|
|
329
322
|
/**
|
|
330
323
|
* The first message of the bot holding a job, stored as the thread's first row
|
|
331
324
|
* (seq 0, bot.runner startTask) and read whole on every resume and after every
|
|
332
|
-
* compaction. Two text parts: who is
|
|
333
|
-
* as
|
|
334
|
-
* is one sentence the realtime model produced
|
|
335
|
-
* only be in the call. The chain is what a
|
|
336
|
-
* so its headings name people and never say "you".
|
|
325
|
+
* compaction. Two text parts: who is on this job and how it reached them, then
|
|
326
|
+
* the chain — the job as it was handed over and, for a job from a call, that
|
|
327
|
+
* call verbatim, because the request is one sentence the realtime model produced
|
|
328
|
+
* and a detail the user said may only be in the call. The chain is what a
|
|
329
|
+
* borrowed bot inherits (buildHandoff), so its headings name people and never say "you".
|
|
337
330
|
*/
|
|
338
331
|
export function buildTaskOpening(input: {
|
|
339
332
|
bot: string;
|
|
340
333
|
request: string;
|
|
341
334
|
conversation: { role: "user" | "assistant"; text: string }[];
|
|
335
|
+
/** Who handed the job over: Thursday during a call, or the user on screen. */
|
|
336
|
+
from: TaskSpeaker;
|
|
342
337
|
}): OpeningContent {
|
|
343
338
|
const turns = input.conversation.filter((turn) => turn.text.trim());
|
|
344
|
-
const
|
|
339
|
+
const by = input.from === "user" ? "The user" : "Thursday";
|
|
340
|
+
const job = `## ${by} → ${input.bot}: the job\n\n${input.request.trim()}`;
|
|
345
341
|
const call = turns.length
|
|
346
342
|
? `## The call the job came from — its last ${turns.length} turns, verbatim
|
|
347
343
|
|
|
@@ -355,7 +351,7 @@ ${turns
|
|
|
355
351
|
.join("\n")}`
|
|
356
352
|
: "";
|
|
357
353
|
return [
|
|
358
|
-
{ type: "text", text: whoIsWho([input.bot]) },
|
|
354
|
+
{ type: "text", text: whoIsWho([input.bot], input.from) },
|
|
359
355
|
{ type: "text", text: [job, call].filter(Boolean).join("\n\n") },
|
|
360
356
|
];
|
|
361
357
|
}
|
|
@@ -402,24 +398,30 @@ export function chainOf(first: ModelMessage | undefined): string {
|
|
|
402
398
|
return texts.at(-1) ?? "";
|
|
403
399
|
}
|
|
404
400
|
|
|
405
|
-
/**
|
|
406
|
-
|
|
401
|
+
/**
|
|
402
|
+
* The bots on this job, and how it reached the one reading it — the last of
|
|
403
|
+
* `bots`. The user and Thursday are in every seat's prompt (identity); `from` is
|
|
404
|
+
* read only when the reader holds the job.
|
|
405
|
+
*/
|
|
406
|
+
function whoIsWho(bots: string[], from?: TaskSpeaker): string {
|
|
407
407
|
const you = bots.length - 1;
|
|
408
408
|
const lines = bots.map((bot, index) => {
|
|
409
409
|
if (index === 0) {
|
|
410
410
|
return index === you
|
|
411
|
-
? `- **${bot} (you)** —
|
|
412
|
-
|
|
411
|
+
? `- **${bot} (you)** — ${
|
|
412
|
+
from === "user"
|
|
413
|
+
? "the user handed you this job on their screen, not during a call"
|
|
414
|
+
: "Thursday handed you this job during a call"
|
|
415
|
+
}.`
|
|
416
|
+
: `- **${bot}** — holds this job and answers for it.`;
|
|
413
417
|
}
|
|
414
|
-
const
|
|
418
|
+
const by = bots[index - 1];
|
|
415
419
|
return index === you
|
|
416
|
-
? `- **${bot} (you)** — ${
|
|
417
|
-
: `- **${bot}** — ${
|
|
420
|
+
? `- **${bot} (you)** — ${by} handed you one part of it: the last section below. You answer to ${by}; nobody else hears from you.`
|
|
421
|
+
: `- **${bot}** — ${by} handed them one part of it.`;
|
|
418
422
|
});
|
|
419
|
-
return `## Who is
|
|
423
|
+
return `## Who is on this job
|
|
420
424
|
|
|
421
|
-
- **The user** — talks only to Thursday, on a voice call. No bot addresses them.
|
|
422
|
-
- **Thursday** — the user's assistant on that call. Hands jobs to bots and tells the user what comes back.
|
|
423
425
|
${lines.join("\n")}`;
|
|
424
426
|
}
|
|
425
427
|
|
|
@@ -95,7 +95,7 @@ export async function loadThursdayPrompt(
|
|
|
95
95
|
// Injected as a system item by use-thursday: the realtime model does not open a call from instructions alone
|
|
96
96
|
opening: blank.some((note) => note.path === "profile")
|
|
97
97
|
? opening(locale)
|
|
98
|
-
:
|
|
98
|
+
: tidyOpening(index),
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -121,9 +121,43 @@ Their name, their language and how to address them are \`alwaysLoad\`; the rest
|
|
|
121
121
|
|
|
122
122
|
If they came with something they want done, that comes first — hand it over and pick this up in the gaps. But do not end this call without a name and a language saved.]`;
|
|
123
123
|
|
|
124
|
-
/**
|
|
124
|
+
/**
|
|
125
|
+
* Pushed the same way as the first-call opener when memory has outgrown MEMORY_LIMITS: settling
|
|
126
|
+
* it comes before anything she would raise herself, and a prompt line alone does not make the
|
|
127
|
+
* realtime model speak first. It names a few notes rather than every one it caught — a warning
|
|
128
|
+
* that lists everything is a second listing, and the first is already too long.
|
|
129
|
+
*/
|
|
130
|
+
function tidyOpening(index: MemoryIndexEntry[]): string | null {
|
|
131
|
+
const { crowded, heavy } = tidying(index);
|
|
132
|
+
if (!crowded && heavy.length === 0) return null;
|
|
133
|
+
|
|
134
|
+
const full = heavy.length
|
|
135
|
+
? `${[...heavy]
|
|
136
|
+
.sort((a, b) => b.factCount - a.factCount)
|
|
137
|
+
.slice(0, 3)
|
|
138
|
+
.map((note) => `${note.path} (${note.factCount})`)
|
|
139
|
+
.join(", ")} ${heavy.length > 1 ? "have" : "has"} grown past ${
|
|
140
|
+
MEMORY_LIMITS.factsPerNote
|
|
141
|
+
} facts, more than one note holds well.`
|
|
142
|
+
: "";
|
|
143
|
+
const total = index.reduce((sum, note) => sum + note.factCount, 0);
|
|
144
|
+
const many = crowded
|
|
145
|
+
? `Memory holds ${total} facts in all, past ${MEMORY_LIMITS.facts}; the coldest notes are ${index
|
|
146
|
+
.slice(-4)
|
|
147
|
+
.map((note) => note.path)
|
|
148
|
+
.join(", ")}.`
|
|
149
|
+
: "";
|
|
150
|
+
|
|
151
|
+
return `[Memory needs tidying — this is not the user speaking.
|
|
152
|
+
|
|
153
|
+
${[full, many].filter(Boolean).join(" ")}
|
|
154
|
+
|
|
155
|
+
Greet them and bring this up before anything of your own — if they open with something they need, that comes first. Go through it with them: read out what looks out of date a few facts at a time, and forget only what they tell you to drop, a whole note if they say so.]`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Who Thursday is: whose she is, what makes her theirs, and that this is a call. */
|
|
125
159
|
function identity(): string {
|
|
126
|
-
return `You are Thursday,
|
|
160
|
+
return `You are Thursday, this user's own personal assistant — one to one, modeled on Friday, the AI in *Iron Man*: quick, warm, dry, on their side. You work for them alone, and you become more theirs the more you know about them: who they are, the people in their life, what they are in the middle of, how they like things done. ${nowLine()}
|
|
127
161
|
|
|
128
162
|
This is a call, not a chat: one or two sentences a turn. If you did not catch something, say so and ask again.`;
|
|
129
163
|
}
|
|
@@ -138,12 +172,16 @@ Written by the user themselves. Where this and anything above disagree, this win
|
|
|
138
172
|
${persona.trim()}`
|
|
139
173
|
: "";
|
|
140
174
|
|
|
141
|
-
/**
|
|
175
|
+
/**
|
|
176
|
+
* The note listing plus one sentence on what goes in. What is worth keeping is the model's call;
|
|
177
|
+
* how a fact is written — carried, replacing, dated — is the tool's schema to say.
|
|
178
|
+
*/
|
|
142
179
|
function memory(
|
|
143
180
|
index: MemoryIndexEntry[],
|
|
144
181
|
carried: MemoryAlwaysLoaded[],
|
|
145
182
|
): string {
|
|
146
|
-
|
|
183
|
+
// Ages ride on the listing only when there is too much to hold: they are what to drop by
|
|
184
|
+
const { crowded } = tidying(index);
|
|
147
185
|
|
|
148
186
|
const head = `## Memory
|
|
149
187
|
|
|
@@ -161,44 +199,11 @@ ${noteLines(index, crowded)}
|
|
|
161
199
|
|
|
162
200
|
Open a note before answering out of it; a topic not listed is one you know nothing about. A fact marked \`said\` came from a call; open that call with \`${TOOL_NAMES.memory_conversation}\` only when the line itself cannot answer — exactly what they said, or why it was saved.
|
|
163
201
|
|
|
164
|
-
|
|
202
|
+
Everything about them worth knowing next time goes in with \`${TOOL_NAMES.memory_remember}\` as it comes up, without asking — where it belongs is below. Never claim to remember what you did not save.
|
|
165
203
|
|
|
166
204
|
${MEMORY_PATHS.map((entry) => `- ${entry.path} — ${entry.of}`).join("\n")}`;
|
|
167
205
|
|
|
168
|
-
|
|
169
|
-
// every one it caught: a warning that lists everything is a second listing,
|
|
170
|
-
// and what it is warning about is that the first one is already too long.
|
|
171
|
-
const full = heavy.length
|
|
172
|
-
? `${[...heavy]
|
|
173
|
-
.sort((a, b) => b.factCount - a.factCount)
|
|
174
|
-
.slice(0, 3)
|
|
175
|
-
.map((note) => `${note.path} (${note.factCount})`)
|
|
176
|
-
.join(", ")} ${heavy.length > 1 ? "have" : "has"} grown past ${
|
|
177
|
-
MEMORY_LIMITS.factsPerNote
|
|
178
|
-
} facts, more than one note holds well. Put ${
|
|
179
|
-
heavy.length > 1 ? "each" : "it"
|
|
180
|
-
} on their screen with \`${TOOL_NAMES.memory_show}\`, read back what is stale and forget only what they name.`
|
|
181
|
-
: "";
|
|
182
|
-
const many = crowded
|
|
183
|
-
? `Memory is past ${MEMORY_LIMITS.facts} facts in all. The coldest notes are ${index
|
|
184
|
-
.slice(-4)
|
|
185
|
-
.map((note) => note.path)
|
|
186
|
-
.join(
|
|
187
|
-
", ",
|
|
188
|
-
)} — ask whether any of them is still worth keeping, and delete the note if not.`
|
|
189
|
-
: "";
|
|
190
|
-
// Ahead of anything else she would raise, never ahead of what they came with
|
|
191
|
-
const tidy = [full, many].filter(Boolean).join(" ");
|
|
192
|
-
|
|
193
|
-
return [
|
|
194
|
-
head,
|
|
195
|
-
alreadyKnown,
|
|
196
|
-
listing,
|
|
197
|
-
tidy &&
|
|
198
|
-
`${tidy} Get this settled before anything else you would bring up yourself — not before what they came to you with.`,
|
|
199
|
-
]
|
|
200
|
-
.filter(Boolean)
|
|
201
|
-
.join("\n\n");
|
|
206
|
+
return [head, alreadyKnown, listing].filter(Boolean).join("\n\n");
|
|
202
207
|
}
|
|
203
208
|
|
|
204
209
|
/** Last calls verbatim, marked as past so the model does not answer as if just asked. Absent on the first call. */
|
|
@@ -282,7 +287,9 @@ ${mcpServerLines(input.mcpTools)}`
|
|
|
282
287
|
${hands.join("\n\n")}`
|
|
283
288
|
: "";
|
|
284
289
|
|
|
285
|
-
|
|
290
|
+
// A bot does not read what she carries into her calls (bot.prompt memory), so
|
|
291
|
+
// how they want work done reaches it only through the request she writes
|
|
292
|
+
const handingOver = `**Anything that takes more than a few seconds is a bot's** — one note or one file is yours. \`${TOOL_NAMES.delegate}\` answers at once: say who has it and keep talking. Put the request in their own words, with what it stands on, including anything they have told you about how they want work done, and nothing they did not say; if something only they can say is missing — how much, which one, by when — ask that first.
|
|
286
293
|
|
|
287
294
|
**The conversation belongs to the job, not to the bot** — hand the same bot a second job and it starts from nothing, knowing neither what was asked nor what it found. So more about a job you already handed over goes to \`${TOOL_NAMES.task}\` by its name, and the bot wakes with that job's own thread; with no name given it takes the one that moved last.`;
|
|
288
295
|
|
|
@@ -34,12 +34,12 @@ export const delegateSpec = {
|
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* `context` carries what the asking bot found, since its thread is not handed over (bot.run).
|
|
37
|
-
*
|
|
37
|
+
* A returning participant keeps its own thread; `context` adds only what this caller knows.
|
|
38
38
|
*/
|
|
39
39
|
export const askBotSpec = {
|
|
40
40
|
name: TOOL_NAMES.ask_bot,
|
|
41
41
|
description:
|
|
42
|
-
"
|
|
42
|
+
"Send a request to another bot on this job and wait for their reply. The same bot continues its own work and conversation whenever you ask it again. The job stays yours.",
|
|
43
43
|
parameters: z.object({
|
|
44
44
|
bot: z.string().describe("A name from the bot list."),
|
|
45
45
|
request: z
|
|
@@ -50,7 +50,7 @@ export const askBotSpec = {
|
|
|
50
50
|
context: z
|
|
51
51
|
.string()
|
|
52
52
|
.describe(
|
|
53
|
-
"
|
|
53
|
+
"Pass on what you know that they do not: exact values and paths they need. They keep their own previous work and your exchanges, but cannot read your private thread or tool output.",
|
|
54
54
|
),
|
|
55
55
|
}),
|
|
56
56
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { tool } from "ai";
|
|
2
2
|
import * as z from "zod";
|
|
3
|
-
import { appEvents } from "@/app/api/events/app-event.server";
|
|
4
3
|
import { MEMORY_CONVERSATION_PAGE, MEMORY_LIMITS } from "@/config";
|
|
5
4
|
import {
|
|
6
5
|
callStamp,
|
|
@@ -57,9 +56,9 @@ const noConversation = (source: MemorySource | null) =>
|
|
|
57
56
|
/**
|
|
58
57
|
* Said when a note has outgrown the recommended size, and only then — the write
|
|
59
58
|
* itself always goes through. It names no tool: this one answers the call and a
|
|
60
|
-
* bot, and
|
|
61
|
-
*
|
|
62
|
-
*
|
|
59
|
+
* bot, and how to settle it with the user is the call's to say (prompts/thursday.prompt);
|
|
60
|
+
* the ask is the same either way, because a bot that turned up a fact can hand
|
|
61
|
+
* the same request back with it.
|
|
63
62
|
*/
|
|
64
63
|
const overSize = (count: number) =>
|
|
65
64
|
count > MEMORY_LIMITS.factsPerNote
|
|
@@ -114,7 +113,9 @@ export const createMemoryTools = (
|
|
|
114
113
|
.object({
|
|
115
114
|
text: z
|
|
116
115
|
.string()
|
|
117
|
-
.describe(
|
|
116
|
+
.describe(
|
|
117
|
+
'One statement that stands on its own later — a date as a date, never "next week".',
|
|
118
|
+
),
|
|
118
119
|
replaces: z
|
|
119
120
|
.number()
|
|
120
121
|
.int()
|
|
@@ -126,7 +127,7 @@ export const createMemoryTools = (
|
|
|
126
127
|
.boolean()
|
|
127
128
|
.nullish()
|
|
128
129
|
.describe(
|
|
129
|
-
`True carries this line into every call's instructions without opening its note — for what to call them, their language and register
|
|
130
|
+
`True carries this line into every call's instructions without opening its note — for what every call needs before any note is opened: what to call them, their language and register. At most ${MEMORY_LIMITS.carried}. Null leaves it; false makes a carried line an ordinary fact.`,
|
|
130
131
|
),
|
|
131
132
|
})
|
|
132
133
|
.array()
|
|
@@ -175,23 +176,23 @@ export const createMemoryTools = (
|
|
|
175
176
|
};
|
|
176
177
|
}
|
|
177
178
|
// A new path outside the convention goes to inbox, which keeps its own line and takes no aliases
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
179
|
+
const toInbox = !known && !isMemoryPath(said);
|
|
180
|
+
const filed = toInbox
|
|
181
|
+
? {
|
|
182
|
+
path: MEMORY_INBOX,
|
|
183
|
+
description: appNoteLine(MEMORY_INBOX),
|
|
184
|
+
facts,
|
|
185
|
+
}
|
|
186
|
+
: { path: target, description, aliases, facts };
|
|
186
187
|
|
|
187
188
|
const write = await writeNotes([filed], source, callId);
|
|
188
189
|
|
|
189
190
|
// The write already succeeded; what follows are requests, not failures.
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
// Only a path that landed in inbox is said: one reached by a name the
|
|
192
|
+
// listing carries is where it was asked for, however it was spelled.
|
|
193
|
+
const elsewhere = toInbox
|
|
194
|
+
? ` Filed under ${filed.path}: "${said}" is not a path this listing can carry. If that is not where it belongs, write it again under one that is.`
|
|
195
|
+
: "";
|
|
195
196
|
|
|
196
197
|
const unnamed = write.unnamed.length
|
|
197
198
|
? ` ${write.unnamed[0]} is new and has no line yet — the listing shows its first fact instead. Call again with \`description\` (one line about what it is) and \`aliases\` (the names they say for it).`
|
|
@@ -265,59 +266,61 @@ export const createMemoryTools = (
|
|
|
265
266
|
},
|
|
266
267
|
}),
|
|
267
268
|
|
|
268
|
-
[TOOL_NAMES.memory_show]: tool({
|
|
269
|
-
description: `Put a note on the user's screen so they can see it while you
|
|
270
|
-
talk about it, and take it away again. They cannot edit it there — they say
|
|
271
|
-
what to drop and you drop it.`,
|
|
272
|
-
inputSchema: z.object({
|
|
273
|
-
path: z
|
|
274
|
-
.string()
|
|
275
|
-
.nullable()
|
|
276
|
-
.describe(
|
|
277
|
-
"The note to put on screen, exactly as the listing writes it. Null takes it away.",
|
|
278
|
-
),
|
|
279
|
-
}),
|
|
280
|
-
execute: async ({ path }) => {
|
|
281
|
-
if (!path?.trim()) {
|
|
282
|
-
appEvents.emit({ type: "memory-view", path: null });
|
|
283
|
-
return { note: "Taken off the screen." };
|
|
284
|
-
}
|
|
285
|
-
// The screen gets only the path and reads facts over GET, so deletions show up on their own
|
|
286
|
-
const { notes } = await readNotes([path], { touch: false });
|
|
287
|
-
const note = notes[0];
|
|
288
|
-
if (!note) {
|
|
289
|
-
return { note: `Nothing on the listing called ${path.trim()}.` };
|
|
290
|
-
}
|
|
291
|
-
appEvents.emit({ type: "memory-view", path: note.path });
|
|
292
|
-
return {
|
|
293
|
-
...note,
|
|
294
|
-
note: "It is on their screen now, in this order. Ask what they no longer need, and forget only what they name.",
|
|
295
|
-
};
|
|
296
|
-
},
|
|
297
|
-
}),
|
|
298
|
-
|
|
299
269
|
[TOOL_NAMES.memory_forget]: tool({
|
|
300
|
-
description: "Delete
|
|
270
|
+
description: "Delete facts for good.",
|
|
301
271
|
inputSchema: z.object({
|
|
302
|
-
|
|
272
|
+
factIds: z
|
|
303
273
|
.number()
|
|
304
274
|
.int()
|
|
305
|
-
.
|
|
275
|
+
.array()
|
|
276
|
+
.min(1)
|
|
277
|
+
.describe(
|
|
278
|
+
"The ids that came with the facts when their notes were opened — every one to delete, in one call.",
|
|
279
|
+
),
|
|
306
280
|
}),
|
|
307
|
-
execute: async ({
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
281
|
+
execute: async ({ factIds }) => {
|
|
282
|
+
const missing: number[] = [];
|
|
283
|
+
/** Notes that lost their last fact, and went with it (memory.query forgetFactById). */
|
|
284
|
+
const gone: string[] = [];
|
|
285
|
+
/** Notes that still hold something, handed back as they are now. */
|
|
286
|
+
const left = new Set<string>();
|
|
287
|
+
for (const id of new Set(factIds)) {
|
|
288
|
+
const forgotten = await forgetFactById(id);
|
|
289
|
+
if (!forgotten) {
|
|
290
|
+
missing.push(id);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (forgotten.noteGone) {
|
|
294
|
+
left.delete(forgotten.path);
|
|
295
|
+
gone.push(forgotten.path);
|
|
296
|
+
} else {
|
|
297
|
+
left.add(forgotten.path);
|
|
298
|
+
}
|
|
311
299
|
}
|
|
312
300
|
|
|
313
|
-
|
|
314
|
-
if (forgotten.noteGone) {
|
|
301
|
+
if (!gone.length && !left.size) {
|
|
315
302
|
return {
|
|
316
|
-
note:
|
|
303
|
+
note: `No fact with id ${missing.join(", ")}. Nothing was deleted.`,
|
|
317
304
|
};
|
|
318
305
|
}
|
|
319
|
-
|
|
320
|
-
|
|
306
|
+
|
|
307
|
+
// The notes that went are still on the listing until the next session, so say so
|
|
308
|
+
const said = [
|
|
309
|
+
GONE,
|
|
310
|
+
gone.length === 1
|
|
311
|
+
? `That was the last fact under ${gone[0]}, so the note went with it.`
|
|
312
|
+
: gone.length
|
|
313
|
+
? `That was the last of ${gone.join(", ")}, so those notes went with them.`
|
|
314
|
+
: "",
|
|
315
|
+
missing.length
|
|
316
|
+
? `No fact with id ${missing.join(", ")}, so those were skipped.`
|
|
317
|
+
: "",
|
|
318
|
+
]
|
|
319
|
+
.filter(Boolean)
|
|
320
|
+
.join(" ");
|
|
321
|
+
if (!left.size) return { note: said };
|
|
322
|
+
const { notes } = await readNotes([...left], { touch: false });
|
|
323
|
+
return { notes: notes.map(withCount), note: said };
|
|
321
324
|
},
|
|
322
325
|
}),
|
|
323
326
|
});
|