thursday-agent 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +1 -0
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/routes-manifest.json +6 -0
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +13 -13
- package/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/artifact/route.js +1 -1
- package/.next/server/app/api/artifact/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/notes/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/bot/notes/route/build-manifest.json +12 -0
- package/.next/server/app/api/bot/notes/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/bot/notes/route.js +14 -0
- package/.next/server/app/api/bot/notes/route.js.map +5 -0
- package/.next/server/app/api/bot/notes/route.js.nft.json +1 -0
- package/.next/server/app/api/bot/notes/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/bot/route.js +4 -4
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/task/[id]/route.js +6 -6
- 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 +3 -3
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/events/route.js +1 -1
- package/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/server/app/api/file/[...path]/route.js +1 -1
- package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/catalog/route.js +6 -6
- package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/route.js +3 -3
- package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/[name]/route.js +3 -3
- package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/oauth/callback/route.js +5 -5
- package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/route.js +3 -3
- package/.next/server/app/api/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/tools/route.js +3 -3
- package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/route.js +7 -7
- package/.next/server/app/api/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/tidy/route.js +10 -11
- package/.next/server/app/api/memory/tidy/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/[source]/[name]/route.js +1 -1
- package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +1 -1
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/call/route.js +8 -8
- package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/skills/route.js +8 -8
- package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/tool-call/route.js +11 -12
- package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/route.js +1 -1
- 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 +3 -3
- package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next/server/app/page/server-reference-manifest.json +63 -41
- package/.next/server/app/page.js +7 -7
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +1 -0
- package/.next/server/chunks/0d-b_next_dist_server_app-render_after-task-async-storage_external_0-exn7p.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__0t_dqaj._.js → [root-of-the-server]__0-npkh_._.js} +1 -1
- package/.next/server/chunks/[root-of-the-server]__005var5._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__019dx9f._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__1mzp_el._.js → [root-of-the-server]__04_8spk._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__06t77t4._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0bed6hu._.js +4 -4
- package/.next/server/chunks/[root-of-the-server]__0cro2dz._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__0emr6go._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0le4d-p._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0m8ywyo._.js +5 -5
- package/.next/server/chunks/{[root-of-the-server]__113fqg5._.js → [root-of-the-server]__0p32jfz._.js} +3 -3
- package/.next/server/chunks/[root-of-the-server]__0phdgco._.js +4 -4
- package/.next/server/chunks/[root-of-the-server]__0wvgrsl._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__11j52t0._.js +5 -0
- package/.next/server/chunks/{[root-of-the-server]__0j_n5l3._.js → [root-of-the-server]__1692zrv._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__16thabu._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__173nhwf._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__1bn73ih._.js +10 -0
- package/.next/server/chunks/[root-of-the-server]__1bqhuh9._.js +4 -4
- package/.next/server/chunks/[root-of-the-server]__1ccon1w._.js +10 -0
- package/.next/server/chunks/[root-of-the-server]__1cqizfq._.js +1 -1
- package/.next/server/chunks/{[root-of-the-server]__1u_qvc7._.js → [root-of-the-server]__1fboj2j._.js} +2 -2
- package/.next/server/chunks/{[root-of-the-server]__1dow0bz._.js → [root-of-the-server]__1oli73c._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__1txedx6._.js +4 -4
- package/.next/server/chunks/[root-of-the-server]__1untqv8._.js +5 -0
- package/.next/server/chunks/_0-73of8._.js +55 -0
- package/.next/server/chunks/{_2050gwn._.js → _00_584r._.js} +66 -28
- package/.next/server/chunks/{_0y8is-s._.js → _03a0897._.js} +1 -1
- package/.next/server/chunks/_05h2cii._.js +1 -1
- package/.next/server/chunks/_06mxlgn._.js +1 -1
- package/.next/server/chunks/_0a351g7._.js +1 -1
- package/.next/server/chunks/{_0hin38_._.js → _0khihjk._.js} +5 -5
- package/.next/server/chunks/_0kjjmag._.js +1 -1
- package/.next/server/chunks/{_1t8fv7b._.js → _0n74zsf._.js} +5 -5
- package/.next/server/chunks/_0nn99o2._.js +3 -0
- package/.next/server/chunks/_0pppbb5._.js +10 -6
- package/.next/server/chunks/{_1icgbi8._.js → _0rnf-6n._.js} +5 -5
- package/.next/server/chunks/_0u10wmw._.js +55 -0
- package/.next/server/chunks/{_0kl37zl._.js → _0vnutv9._.js} +1 -1
- package/.next/server/chunks/_0wp4le3._.js +1 -1
- package/.next/server/chunks/_0y5fg-x._.js +1 -1
- package/.next/server/chunks/_106co2o._.js +3 -0
- package/.next/server/chunks/_12bbv5i._.js +10 -6
- package/.next/server/chunks/_15hq89o._.js +1 -1
- package/.next/server/chunks/_15q1pig._.js +1 -1
- package/.next/server/chunks/_18-1wcc._.js +55 -0
- package/.next/server/chunks/_19wg5yn._.js +138 -0
- package/.next/server/chunks/_1_5mnq4._.js +18 -0
- package/.next/server/chunks/_1bt95p9._.js +55 -0
- package/.next/server/chunks/_1cbk7ms._.js +9 -9
- package/.next/server/chunks/_1drgfx4._.js +18 -0
- package/.next/server/chunks/_1drv2et._.js +10 -6
- package/.next/server/chunks/{_10qam6o._.js → _1e48czp._.js} +5 -5
- package/.next/server/chunks/{_1skgy7b._.js → _1kg70wc._.js} +5 -5
- package/.next/server/chunks/_1php4eq._.js +1 -1
- package/.next/server/chunks/_1rysr-r._.js +10 -0
- package/.next/server/chunks/_1u3rwbr._.js +3 -0
- package/.next/server/chunks/_1x7p319._.js +1 -1
- package/.next/server/chunks/_1yuv4vp._.js +1 -1
- package/.next/server/chunks/_next-internal_server_app_api_bot_notes_route_actions_0qcsyu7.js +3 -0
- package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
- package/.next/server/chunks/features_bot_task_query_ts_11xuzl3._.js +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0228f43._.js → node_modules_consola_dist_chunks_prompt_mjs_02s3hjt._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0ir-vo2._.js → node_modules_consola_dist_chunks_prompt_mjs_06067w6._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0otw8ef._.js → node_modules_consola_dist_chunks_prompt_mjs_096ais_._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0tf290y._.js → node_modules_consola_dist_chunks_prompt_mjs_0a1rd09._.js} +1 -1
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0gzvgfv._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0j61-ma._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0tvkfgh._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_11zjo-1._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1pycqsq._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1tpvamf._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1y9-wcr._.js +3 -0
- package/.next/server/chunks/ssr/[externals]__0dizgek._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0f6s48x._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0m1b5ky._.js +3 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__0dfwvr4._.js → [root-of-the-server]__0un-erv._.js} +2 -2
- package/.next/server/chunks/ssr/[root-of-the-server]__1-mnhyd._.js +5 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1bww500._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1qfqvmg._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1vcdast._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1xwnu-h._.js +3 -0
- package/.next/server/chunks/ssr/_03webdr._.js +3 -0
- package/.next/server/chunks/ssr/{_0gd4vaa._.js → _040sk32._.js} +71 -29
- package/.next/server/chunks/ssr/_077-g6s._.js +1 -1
- package/.next/server/chunks/ssr/{_0lc_7c8._.js → _09ja_w1._.js} +2 -2
- package/.next/server/chunks/ssr/{_1dcxzqe._.js → _0f_b_9s._.js} +2 -2
- package/.next/server/chunks/ssr/_0h7zdni._.js +1 -1
- package/.next/server/chunks/ssr/_0lu1n_x._.js +3 -0
- package/.next/server/chunks/ssr/_0sf1isi._.js +1 -1
- package/.next/server/chunks/ssr/_0su6_08._.js +3 -0
- package/.next/server/chunks/ssr/{_0nso8tp._.js → _0w5bgak._.js} +2 -2
- package/.next/server/chunks/ssr/{_19_o9d-._.js → _0x_nu5e._.js} +2 -2
- package/.next/server/chunks/ssr/_1-teee5._.js +3 -3
- package/.next/server/chunks/ssr/_13punpm._.js +1 -1
- package/.next/server/chunks/ssr/{_0ay6b01._.js → _1ciyoo3._.js} +2 -2
- package/.next/server/chunks/ssr/_1e-dgz0._.js +1 -1
- package/.next/server/chunks/ssr/_1fq7zgb._.js +1 -1
- package/.next/server/chunks/ssr/{_0cvjbuz._.js → _1g6l4uj._.js} +2 -2
- package/.next/server/chunks/ssr/{_0q_2f2x._.js → _1lip4th._.js} +2 -2
- package/.next/server/chunks/ssr/_1m3bsfr._.js +3 -0
- package/.next/server/chunks/ssr/_1mznrxk._.js +1 -1
- package/.next/server/chunks/ssr/_1ruzehw._.js +3 -0
- package/.next/server/chunks/ssr/_1vyd1ji._.js +3 -0
- package/.next/server/chunks/ssr/features_bot_components_bot-setting_tsx_01ajft6._.js +2 -2
- package/.next/server/chunks/ssr/features_config_components_14jqgy4._.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/features_memory_components_memory-setting_tsx_0oa43sw._.js +1 -1
- package/.next/server/chunks/ssr/lib_realtime_12wg_dd._.js +27 -0
- package/.next/server/chunks/ssr/lib_realtime_15h_v1v._.js +27 -0
- package/.next/server/chunks/ssr/{node_modules_108wd2o._.js → node_modules_004qlr4._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ub40gk._.js → node_modules_01qnv-a._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1y8_3vg._.js → node_modules_04n7mjs._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0_j7-g4._.js → node_modules_08d8bke._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0y2-av4._.js → node_modules_09xhur7._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_043-236._.js → node_modules_0_n241r._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ls96je._.js → node_modules_0iz1rzd._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0xwx4u-._.js → node_modules_0kl7-tn._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1jatz7w._.js → node_modules_0kxz4gt._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1t0y5jt._.js → node_modules_0lrjk5t._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_13f2md6._.js → node_modules_0m93zeg._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1nhw9qg._.js → node_modules_0n1kj4p._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1k29j1j._.js → node_modules_0ndvxub._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_16i4o_3._.js → node_modules_13r44jq._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0fbw5-u._.js → node_modules_17lrt3y._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1d-is1o._.js → node_modules_18_dk7_._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1hn_sd5._.js → node_modules_19dp7ko._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_20ynonm._.js → node_modules_1dwguw_._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_00d6675._.js → node_modules_1hd-5tv._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ee8gav._.js → node_modules_1hn8z1f._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0bue6gy._.js → node_modules_1n5ygqe._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1_588_4._.js → node_modules_1q0p85z._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ipn1ho._.js → node_modules_1toafc-._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0_fvxnt._.js → node_modules_1xyd7ov._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1v0077l._.js → node_modules_1zu988w._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_06kvvuo._.js → node_modules_20yebtj._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_092s8nx._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0m9a4y0._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1nvexgu._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_13w_wy9._.js} +1 -1
- package/.next/server/chunks/ssr/node_modules_consola_dist_chunks_prompt_mjs_05cwqv2._.js +47 -0
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +64 -42
- package/.next/static/chunks/05b6nx_o3_f_n.js +1 -0
- package/.next/static/chunks/{3aquirz5h71da.js → 07kl352c-lvcz.js} +1 -1
- package/.next/static/chunks/0dmns-gi0raa4.js +1 -0
- package/.next/static/chunks/0oc4_xoia77dg.css +1 -0
- package/.next/static/chunks/0pheo8m3a-rzi.js +1 -0
- package/.next/static/chunks/{2ihkf6l2jowo9.js → 18vhba44l1fc9.js} +3 -3
- package/.next/static/chunks/19i8h2t-5evjx.js +1 -0
- package/.next/static/chunks/{1gy3bbv7usnjo.js → 1c5fcj473025q.js} +1 -1
- package/.next/static/chunks/1pi_hbmjw-f-z.js +1 -0
- package/.next/static/chunks/{0tydnn2kybibk.js → 1twt6k09vg4np.js} +1 -1
- package/.next/static/chunks/{21rk8w7bda-su.js → 28gotuisldmom.js} +1 -1
- package/.next/static/chunks/{3xxa2j56-f3nm.js → 2998p0y7k1c8z.js} +1 -1
- package/.next/static/chunks/2l233xfycjpd8.js +1 -0
- package/.next/static/chunks/2p5n7pa3c40t-.js +2 -0
- package/.next/static/chunks/2ru1q132af7h7.js +1 -0
- package/.next/static/chunks/{2fq_86jvq9ymq.js → 2t8wqycwg3y04.js} +2 -2
- package/.next/static/chunks/32z_ypgtchw48.js +1 -0
- package/.next/static/chunks/{247d-r95j8xs0.js → 33pyjb3xd1iez.js} +1 -1
- package/.next/static/chunks/3786h5vayt80-.js +28 -0
- package/.next/static/chunks/3_h9txt3tq8t8.js +28 -0
- package/.next/static/chunks/{1khakdu_iuedc.js → 3ard_noqapnw1.js} +1 -1
- package/.next/static/chunks/{1fr5m75pwtyic.js → 3omud5xunrk1f.js} +1 -1
- package/.next/static/chunks/42dnrkbph9e-1.js +1 -0
- package/app/api/events/app-event.client.ts +24 -0
- package/app/api/events/app-event.server.ts +45 -0
- package/app/api/events/app-event.ts +44 -0
- package/app/api/llm-model/catalog/route.ts +12 -0
- package/app/api/llm-model/route.ts +21 -0
- package/app/api/query-key.ts +144 -0
- package/app/api/thursday/tool-call/route.ts +67 -0
- package/components/ui/button-group.tsx +45 -0
- package/components/ui/custom-icon.tsx +259 -0
- package/components/ui/dropdown-menu.tsx +271 -0
- package/components/ui/folded-text.tsx +68 -0
- package/components/ui/input-group.tsx +157 -0
- package/components/ui/json-view.tsx +240 -0
- package/components/ui/shiny-text.tsx +141 -0
- package/components/ui/text-type.tsx +213 -0
- package/components/ui/theme-sync.tsx +9 -0
- package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260909060813_optimal_sabretooth}/migration.sql +7 -0
- package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260909060813_optimal_sabretooth}/snapshot.json +54 -1
- package/database/migrations/20260909063821_bitter_tenebrous/migration.sql +1 -0
- package/database/migrations/20260909063821_bitter_tenebrous/snapshot.json +1285 -0
- package/docs/how-it-works.md +59 -0
- package/docs/images/keeps-going.png +0 -0
- package/docs/images/task-thread.png +0 -0
- package/docs/images/two-minds.png +0 -0
- package/features/ai/load-tools.ts +55 -5
- package/features/ai/prompts/bot.prompt.ts +121 -26
- package/features/ai/prompts/memory-tidy.prompt.ts +7 -1
- package/features/ai/prompts/prompt-helper.ts +31 -5
- package/features/ai/prompts/thursday.prompt.ts +2 -0
- package/features/ai/tools/bot.tool.ts +37 -18
- package/features/ai/tools/mcp.tool.ts +5 -5
- package/features/ai/tools/memory.tool.ts +17 -10
- package/features/ai/tools/studio.tool.ts +5 -5
- package/features/ai/tools/tool-name.ts +2 -0
- package/features/ai/tools/workspace.tool.ts +33 -5
- package/features/bot/bot.action.ts +31 -4
- package/features/bot/bot.notes.ts +120 -0
- package/features/bot/bot.query.ts +56 -1
- package/features/bot/bot.run.ts +43 -3
- package/features/bot/bot.runner.ts +94 -20
- package/features/bot/bot.schema.ts +17 -0
- package/features/bot/components/bot-room.tsx +405 -306
- package/features/bot/components/bot-setting.tsx +123 -20
- package/features/bot/task.query.ts +18 -5
- package/features/memory/components/memory-setting.tsx +11 -2
- package/features/memory/memory.query.ts +9 -2
- package/features/memory/memory.schema.ts +30 -0
- package/features/skills/components/skills-setting.tsx +9 -1
- package/features/workspace/workspace.ts +136 -3
- package/hooks/use-draft.ts +37 -0
- package/hooks/use-hotkey.ts +105 -0
- package/hooks/use-object-state.ts +51 -0
- package/hooks/use-on-visible.ts +72 -0
- package/hooks/use-theme.ts +106 -0
- package/hooks/use-wake-word.ts +262 -0
- package/lib/realtime/realtime.driver.ts +34 -1
- package/lib/realtime/realtime.protocol.ts +6 -0
- package/lib/sandbox.ts +9 -1
- package/lib/tokens.ts +19 -0
- package/package.json +1 -1
- package/.next/server/chunks/[root-of-the-server]__02kt8yn._.js +0 -10
- package/.next/server/chunks/[root-of-the-server]__0h59di-._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0qjqtwo._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0yk6s3w._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0z3gelv._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__11w_7_3._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1_0q_9w._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1gmn1cs._.js +0 -10
- package/.next/server/chunks/_0fzqt2y._.js +0 -100
- package/.next/server/chunks/_0jgm3b8._.js +0 -55
- package/.next/server/chunks/_0ks6_sg._.js +0 -55
- package/.next/server/chunks/_0tzfesa._.js +0 -3
- package/.next/server/chunks/_0zmestz._.js +0 -55
- package/.next/server/chunks/_12nr4xc._.js +0 -3
- package/.next/server/chunks/_1hzrsvg._.js +0 -55
- package/.next/server/chunks/_1uj4_io._.js +0 -10
- package/.next/server/chunks/_1yldgo4._.js +0 -55
- package/.next/server/chunks/features_0scmr4i._.js +0 -3
- package/.next/server/chunks/features_bot_bot_query_ts_12mgkl2._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0ykklj0._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1ryg3wh._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1vwbnq_._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__01id-v3._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0b-0i32._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__185grso._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1minz8_._.js +0 -3
- package/.next/server/chunks/ssr/_0j4v8of._.js +0 -3
- package/.next/server/chunks/ssr/_0l81bzy._.js +0 -3
- package/.next/server/chunks/ssr/_10uvw3l._.js +0 -3
- package/.next/server/chunks/ssr/_1jbv7m1._.js +0 -3
- package/.next/server/chunks/ssr/lib_realtime_0aetuyp._.js +0 -24
- package/.next/server/chunks/ssr/lib_realtime_18k_aj-._.js +0 -24
- package/.next/server/chunks/ssr/node_modules_zod_v4_classic_errors_05xv7vp.js +0 -3
- package/.next/static/chunks/0yysv1gzin17t.js +0 -1
- package/.next/static/chunks/1keib_gt066gc.js +0 -1
- package/.next/static/chunks/28g42tw8tsvhd.js +0 -1
- package/.next/static/chunks/2cwjgkt_hoyhj.js +0 -22
- package/.next/static/chunks/2rbf1fpd8n80-.js +0 -22
- package/.next/static/chunks/2ukj8k69y89um.js +0 -1
- package/.next/static/chunks/2wngeetfnzxkp.js +0 -2
- package/.next/static/chunks/388-t2v72jh39.css +0 -1
- package/.next/static/chunks/3fqvc0cg-atq_.js +0 -1
- package/.next/static/chunks/3p4egvfjxpatb.js +0 -1
- package/.next/static/chunks/3qe1w1ei-huhl.js +0 -1
- package/.next/static/chunks/3y40riycdip-i.js +0 -1
- package/.next/static/chunks/45a6zbtkelph9.js +0 -1
- /package/.next/static/{X34BUd9bfdH27ripuwwBm → YR4NWcDDuzwLbTTU7U7Vr}/_buildManifest.js +0 -0
- /package/.next/static/{X34BUd9bfdH27ripuwwBm → YR4NWcDDuzwLbTTU7U7Vr}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{X34BUd9bfdH27ripuwwBm → YR4NWcDDuzwLbTTU7U7Vr}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# How it works
|
|
2
|
+
|
|
3
|
+
The README says what Thursday is. This says what each part may touch, so you can guess what will happen before you say it.
|
|
4
|
+
|
|
5
|
+
## The call
|
|
6
|
+
|
|
7
|
+
A call is a session with a realtime speech model — OpenAI Realtime or xAI Grok Voice, whichever key you gave. Audio in, audio out, interruptions and all. The model hears a prompt assembled the moment the line opens: who she is, what she remembers about you, which bots exist, what was said on the last calls.
|
|
8
|
+
|
|
9
|
+
She holds three things and nothing else:
|
|
10
|
+
|
|
11
|
+
- **Memory.** A folder of notes about you. She reads a note before answering out of it and writes the moment something worth keeping comes up — one fact per line, dates as dates. A rule you lay down ("answer in Korean", "keep it short") is carried into every call.
|
|
12
|
+
- **One command.** A shell, for the things that take a second: open a file, play something, look at what is in a folder. One command is something she does, not a job she hands over.
|
|
13
|
+
- **The roster.** The bots, by name, and what each is for.
|
|
14
|
+
|
|
15
|
+
Everything else — anything that takes more than a few seconds — she hands to a bot with `delegate` and keeps talking. That is deliberate: the call must never go silent, and a speech model that runs a browser would be silent for minutes.
|
|
16
|
+
|
|
17
|
+
When a job comes back, it arrives as a system note, not as you speaking. She tells you in one sentence, in her own words. A file the job made opens on your screen by itself; a question the job asks becomes buttons on screen and words in your ear.
|
|
18
|
+
|
|
19
|
+
## Bots
|
|
20
|
+
|
|
21
|
+
A bot is a text model — any of OpenAI, Anthropic, Google, xAI, or whatever the Vercel AI Gateway carries — with the whole machine:
|
|
22
|
+
|
|
23
|
+
- a **shell** in a workspace folder, with no pager and no secrets in its environment
|
|
24
|
+
- a **real browser** (Chromium through Playwright), headless until a sign-in or something you should see, and able to attach to the Chrome you already use
|
|
25
|
+
- the **filesystem**, read anywhere, written only inside the workspace and where you pointed it
|
|
26
|
+
- **skills** — written-down methods it reads before starting: the browser, this Mac, building a page, finding and writing skills
|
|
27
|
+
- **MCP servers** you connected, searched by name and called by schema, with OAuth handled
|
|
28
|
+
- a **studio** of image, video, speech and transcription models
|
|
29
|
+
- **each other**: a bot hands a part of its job to the bot that exists for it, and folds the answer into its own report
|
|
30
|
+
|
|
31
|
+
A job ends in the thing that was asked for — the account made, the page built, the comparison in a table — and a report, written to be heard, because she reads it out loud. Anything that does not fit in a few lines is a file under `artifacts/`, and the report names it.
|
|
32
|
+
|
|
33
|
+
What only you can give — a password, a one-time code, a passkey, a decision between two real options — stops the job at that point. The bot sets it up one action away, asks, and continues when the answer comes.
|
|
34
|
+
|
|
35
|
+
Three bots come with the app, split by temperament: **Jarvis** plans a job, hands parts to whoever fits and does the rest; **Navigator** goes and looks; **Scribe** writes. Make more in settings: a name, a sentence about what it is for, optionally a model and a few pinned tools. The sentence is what she reads when deciding who gets the job.
|
|
36
|
+
|
|
37
|
+
## Jobs outlive the call
|
|
38
|
+
|
|
39
|
+
A job runs on the server. It is not in the tab, and it does not stop when you hang up. Every step — the request, each tool call, what came back, the report — is written as a row, and the screen is a projection of those rows. When you come back, the jobs that finished are waiting, and she tells you about them on the next call, or rings you if you turned that on.
|
|
40
|
+
|
|
41
|
+
A job that stopped to ask, or ran out of its step budget, is resumed from where it stopped: the same thread, with your answer appended. A long thread is compacted — the model summarizes what happened so far and continues from the summary.
|
|
42
|
+
|
|
43
|
+
## Memory is a folder
|
|
44
|
+
|
|
45
|
+
Memory is plain notes on your disk, in the data folder (`~/.thursday` when installed with `npx`, the checkout otherwise). Each note is a topic — `profile`, `preferences`, `people/…`, and whatever she creates. A few are `alwaysLoad` and travel into every call: your name, your language, how you like to be addressed, the rules you gave her.
|
|
46
|
+
|
|
47
|
+
You can open any note from settings and read exactly what she knows. When the listing gets long she says so in a lull, puts a note on your screen and forgets only what you name.
|
|
48
|
+
|
|
49
|
+
Writing mid-conversation catches what she noticed; it misses what only reads back as important afterwards. So when a call ends, a text model re-reads the most recent turns and reconciles the notes with them — correcting what changed, adding what was said in passing. Calls are stamped as they are read, so a backlog of short calls collapses into one pass rather than one per call.
|
|
50
|
+
|
|
51
|
+
Bots read memory but do not write it. What a bot learns goes into its report; whether it is worth keeping is her call, with you.
|
|
52
|
+
|
|
53
|
+
## What is not here
|
|
54
|
+
|
|
55
|
+
- **No cloud.** The server is the one on your machine; keys are stored locally and sent only to the provider you chose.
|
|
56
|
+
- **No account.** There is nothing to sign up for. There is also no login, which is why the server binds to `127.0.0.1` — see [SECURITY.md](../SECURITY.md).
|
|
57
|
+
- **No prompt engineering on your side.** The prompts are assembled from your data on every session; the one thing you can add is an instruction to her, and one to each bot, in settings.
|
|
58
|
+
|
|
59
|
+
For the code — which file owns what, and why — read [CLAUDE.md](../CLAUDE.md).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { type ToolSet, tool } from "ai";
|
|
1
|
+
import { asSchema, type ToolSet, tool } from "ai";
|
|
2
2
|
import { formatDistanceToNowStrict } from "date-fns";
|
|
3
|
+
import { IS_DEV } from "@/config";
|
|
3
4
|
import type { TextModel } from "@/features/ai/model";
|
|
4
|
-
import { tidying } from "@/features/ai/prompts/prompt-helper";
|
|
5
|
+
import { clockNow, tidying } from "@/features/ai/prompts/prompt-helper";
|
|
5
6
|
import {
|
|
6
7
|
askThursdayTool,
|
|
8
|
+
createReportTool,
|
|
7
9
|
delegateSpec,
|
|
8
|
-
reportTool,
|
|
9
10
|
taskSpec,
|
|
10
11
|
} from "@/features/ai/tools/bot.tool";
|
|
11
12
|
import { CALL_TOOLS } from "@/features/ai/tools/call.tool";
|
|
@@ -19,12 +20,15 @@ import { createSkillTools } from "@/features/ai/tools/skills.tool";
|
|
|
19
20
|
import { tidyDoneTool } from "@/features/ai/tools/tidy.tool";
|
|
20
21
|
import { TOOL_NAMES } from "@/features/ai/tools/tool-name";
|
|
21
22
|
import { createWorkspaceTools } from "@/features/ai/tools/workspace.tool";
|
|
23
|
+
import { readBotNotesOn } from "@/features/bot/bot.query";
|
|
22
24
|
import { taskActivity } from "@/features/bot/bot.schema";
|
|
23
25
|
import { listNoteIndex } from "@/features/memory/memory.query";
|
|
24
26
|
import { loadSkills } from "@/features/skills/skills.discover";
|
|
25
27
|
import { readCallSkillsOn } from "@/features/thursday/thursday.query";
|
|
26
28
|
import { jobShellEnv, openWorkspace } from "@/features/workspace/workspace";
|
|
27
29
|
import { toDate } from "@/lib/date-like";
|
|
30
|
+
import { logger } from "@/lib/logger";
|
|
31
|
+
import { estimateTokens } from "@/lib/tokens";
|
|
28
32
|
import { clip } from "@/lib/utils";
|
|
29
33
|
import { resolveSearchModel } from "./model";
|
|
30
34
|
|
|
@@ -109,12 +113,16 @@ function createTaskTools(callId: string | null | undefined): ToolSet {
|
|
|
109
113
|
note: "The text in `answer` was not passed on — `status` only reads. Send it again as `answer` if the bot is meant to hear it.",
|
|
110
114
|
}
|
|
111
115
|
: {};
|
|
116
|
+
// The clock, because the one in the instructions is from when the call
|
|
117
|
+
// opened and a call can run for hours; `since` is measured against this.
|
|
118
|
+
const now = { now: clockNow() };
|
|
112
119
|
// A named job comes back whole; the list clips outcomes, and the model pads a clipped report
|
|
113
120
|
if (task) {
|
|
114
121
|
const found = await resolveTask(task);
|
|
115
122
|
const one = found;
|
|
116
123
|
if (!one) return await noSuchJob(task);
|
|
117
124
|
return {
|
|
125
|
+
...now,
|
|
118
126
|
...dropped,
|
|
119
127
|
label: one.label,
|
|
120
128
|
bot: one.bot,
|
|
@@ -132,6 +140,7 @@ function createTaskTools(callId: string | null | undefined): ToolSet {
|
|
|
132
140
|
// No threads — only as much as is worth reading out: what it is
|
|
133
141
|
// asking, the one line of what it is doing, or how it ended
|
|
134
142
|
return {
|
|
143
|
+
...now,
|
|
135
144
|
...dropped,
|
|
136
145
|
tasks: tasks.map((task) => ({
|
|
137
146
|
label: task.label,
|
|
@@ -190,8 +199,44 @@ async function noSuchJob(ref: string): Promise<string> {
|
|
|
190
199
|
return `There is no job called "${ref}". The latest are: ${names}. Call again with one of those names.`;
|
|
191
200
|
}
|
|
192
201
|
|
|
202
|
+
/**
|
|
203
|
+
* What the set costs the model, beside the prompt's own line (prompts/prompt-helper
|
|
204
|
+
* logPromptSize). Descriptions and schemas are both counted because both are sent
|
|
205
|
+
* on every step. No budget here: the set is decided by the code, except for the MCP
|
|
206
|
+
* tools a bot is pinned to — those show up by name.
|
|
207
|
+
*/
|
|
208
|
+
async function logToolSize(target: ToolTarget, tools: ToolSet): Promise<void> {
|
|
209
|
+
const rows: { name: string; tokens: number }[] = [];
|
|
210
|
+
for (const [name, held] of Object.entries(tools)) {
|
|
211
|
+
const schema = await asSchema(held.inputSchema).jsonSchema;
|
|
212
|
+
// A description may be a function of the call's context; only a fixed one is counted
|
|
213
|
+
const said = typeof held.description === "string" ? held.description : "";
|
|
214
|
+
rows.push({
|
|
215
|
+
name,
|
|
216
|
+
tokens: estimateTokens(said) + estimateTokens(JSON.stringify(schema)),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
rows.sort((a, b) => b.tokens - a.tokens);
|
|
220
|
+
const total = rows.reduce((sum, row) => sum + row.tokens, 0);
|
|
221
|
+
logger.debug(
|
|
222
|
+
`${target} tools ${total} tokens over ${rows.length} — ${rows
|
|
223
|
+
.map((row) => `${row.name} ${row.tokens}`)
|
|
224
|
+
.join(", ")}`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
193
228
|
export async function loadTools(run: ToolRun): Promise<ToolSet> {
|
|
194
|
-
const
|
|
229
|
+
const tools = await buildTools(run);
|
|
230
|
+
// Measured only where the line would be printed: the schemas have to be built
|
|
231
|
+
// to be counted, and unlike a prompt this set does not grow with use.
|
|
232
|
+
if (IS_DEV) await logToolSize(run.target, tools);
|
|
233
|
+
return tools;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function buildTools(run: ToolRun): Promise<ToolSet> {
|
|
237
|
+
// The runtime is the hand: the call as the user talks, the pass that reads
|
|
238
|
+
// calls back, a bot mid-job (memory.tool botRememberTool records its own).
|
|
239
|
+
const memory = createMemoryTools(run.target === "tidy" ? "tidy" : "call");
|
|
195
240
|
|
|
196
241
|
if (run.target === "tidy") {
|
|
197
242
|
// Reading calls back (memory/memory.tidy): the one runtime besides the call
|
|
@@ -242,11 +287,16 @@ export async function loadTools(run: ToolRun): Promise<ToolSet> {
|
|
|
242
287
|
...createWorkspaceTools(sandbox, {
|
|
243
288
|
write: true,
|
|
244
289
|
env: jobShellEnv(run.taskId),
|
|
290
|
+
// What the shell is like and what this machine has, on the first command
|
|
291
|
+
// of the run only (workspace.tool shellGuide). The call gets no guide:
|
|
292
|
+
// one command is a glance, not a job to plan around
|
|
293
|
+
guide: true,
|
|
245
294
|
}),
|
|
246
295
|
// Pinned tools come with schemas; the rest sit behind `tool_search`, absent when nothing is left to find (mcp.tool)
|
|
247
296
|
...(await createMcpTools(run.bot, sandbox)),
|
|
248
297
|
...createSkillTools({ sandbox, skills }),
|
|
249
298
|
[TOOL_NAMES.ask_thursday]: askThursdayTool,
|
|
250
|
-
|
|
299
|
+
// No field for its own instructions when nobody keeps them (bot.schema BOT_NOTES_KEY)
|
|
300
|
+
[TOOL_NAMES.report]: createReportTool(await readBotNotesOn()),
|
|
251
301
|
};
|
|
252
302
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { BOT_RUN, PATHS } from "@/config";
|
|
1
|
+
import { BOT_NOTES, BOT_RUN, PATHS } from "@/config";
|
|
2
2
|
import { TOOL_NAMES } from "@/features/ai/tools/tool-name";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
listJobBots,
|
|
5
|
+
readBotNote,
|
|
6
|
+
readBotNotesOn,
|
|
7
|
+
} from "@/features/bot/bot.query";
|
|
4
8
|
import type { JobBot } from "@/features/bot/bot.schema";
|
|
5
9
|
import { findPinnedTools } from "@/features/connectors/mcp.query";
|
|
6
10
|
import type { McpToolRef } from "@/features/connectors/mcp.schema";
|
|
@@ -16,13 +20,18 @@ import {
|
|
|
16
20
|
loadSkills,
|
|
17
21
|
type SkillMetadata,
|
|
18
22
|
} from "@/features/skills/skills.discover";
|
|
19
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
type MachineTools,
|
|
25
|
+
openWorkspace,
|
|
26
|
+
readMachineTools,
|
|
27
|
+
} from "@/features/workspace/workspace";
|
|
20
28
|
import { logger } from "@/lib/logger";
|
|
21
29
|
import { clip } from "@/lib/utils";
|
|
22
30
|
import { listConnectedToolNames } from "../tools/connected";
|
|
23
31
|
import {
|
|
24
32
|
carriedLines,
|
|
25
33
|
type LoadedPrompt,
|
|
34
|
+
logPromptSize,
|
|
26
35
|
mcpToolLines,
|
|
27
36
|
noteLines,
|
|
28
37
|
nowLine,
|
|
@@ -44,31 +53,47 @@ export async function loadBotPrompt(
|
|
|
44
53
|
self: string,
|
|
45
54
|
persona?: string | null,
|
|
46
55
|
askedBy?: string | null,
|
|
56
|
+
/** The two folders that are this run's rather than the user's (bot.run). */
|
|
57
|
+
folders?: { scratch: string | null; own: string },
|
|
47
58
|
): Promise<LoadedPrompt> {
|
|
48
59
|
const sandbox = await openWorkspace();
|
|
49
60
|
const name = self.trim();
|
|
50
|
-
const [
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
const [
|
|
62
|
+
skills,
|
|
63
|
+
index,
|
|
64
|
+
carried,
|
|
65
|
+
mcpTools,
|
|
66
|
+
pinned,
|
|
67
|
+
allBots,
|
|
68
|
+
ownNote,
|
|
69
|
+
notesOn,
|
|
70
|
+
machine,
|
|
71
|
+
] = await Promise.all([
|
|
72
|
+
loadSkills(sandbox),
|
|
73
|
+
listNoteIndex(),
|
|
74
|
+
listAlwaysLoaded(),
|
|
75
|
+
// User-connected servers and the app's studio in one list (tools/connected)
|
|
76
|
+
listConnectedToolNames(),
|
|
77
|
+
// MCP tools this bot already holds; dropped from the listing below
|
|
78
|
+
findPinnedTools(name),
|
|
79
|
+
listJobBots(),
|
|
80
|
+
// What this bot left itself; kept by the pass that runs after a job (bot.notes)
|
|
81
|
+
readBotNote(name),
|
|
82
|
+
readBotNotesOn(),
|
|
83
|
+
// One `command -v` sweep; what is here decides the first command (environment)
|
|
84
|
+
readMachineTools(sandbox),
|
|
85
|
+
]);
|
|
62
86
|
|
|
63
87
|
// Drop self so a bot cannot call itself; a borrowed bot has no roster at all (bot.run MAX_DEPTH)
|
|
64
88
|
const peers = askedBy ? [] : allBots.filter((bot) => bot.name !== name);
|
|
65
89
|
|
|
66
90
|
const text = [
|
|
67
|
-
askedBy ? borrowedIdentity(askedBy) : identity(),
|
|
91
|
+
askedBy ? borrowedIdentity(name, askedBy) : identity(name),
|
|
68
92
|
memory(index, carried),
|
|
93
|
+
notesOn ? notes(ownNote) : "",
|
|
69
94
|
connectedTools(mcpTools, pinned),
|
|
70
95
|
methods(skills),
|
|
71
|
-
environment(sandbox.cwd),
|
|
96
|
+
environment(sandbox.cwd, machine, folders),
|
|
72
97
|
roster(peers),
|
|
73
98
|
askedBy ? askingBack(askedBy) : ASKING,
|
|
74
99
|
askedBy ? handingUp(askedBy) : FINISHING,
|
|
@@ -78,6 +103,7 @@ export async function loadBotPrompt(
|
|
|
78
103
|
.filter(Boolean)
|
|
79
104
|
.join("\n\n");
|
|
80
105
|
|
|
106
|
+
logPromptSize("bot", text);
|
|
81
107
|
logger.debug(`bot prompt\n${text}`);
|
|
82
108
|
|
|
83
109
|
return {
|
|
@@ -88,18 +114,19 @@ export async function loadBotPrompt(
|
|
|
88
114
|
}
|
|
89
115
|
|
|
90
116
|
/** Who the bot is, what machine it is on, and that guesses are not results. */
|
|
91
|
-
function identity(): string {
|
|
117
|
+
function identity(name: string): string {
|
|
92
118
|
return [
|
|
93
|
-
|
|
119
|
+
// Named, because the owner's prompt may not name it and its own instructions are addressed to it
|
|
120
|
+
`You are ${name}, a worker. Thursday handed you a job while she keeps talking to the user — they speak to her and to nobody else, and what she reads them out of your report is the only part of this that reaches them. You are not in that conversation and never address the user. This thread is drawn on their screen while you work, though: not written to them, but not private either. ${nowLine()}`,
|
|
94
121
|
MACHINE,
|
|
95
122
|
NO_GUESSING,
|
|
96
123
|
].join("\n\n");
|
|
97
124
|
}
|
|
98
125
|
|
|
99
126
|
/** The borrowed seat: above it is the borrowing bot, not Thursday. */
|
|
100
|
-
function borrowedIdentity(askedBy: string): string {
|
|
127
|
+
function borrowedIdentity(name: string, askedBy: string): string {
|
|
101
128
|
return [
|
|
102
|
-
`You are a worker. ${askedBy} is holding a job Thursday handed them and has handed one part of it to you.
|
|
129
|
+
`You are ${name}, a worker. ${askedBy} is holding a job Thursday handed them and has handed one part of it to you. The user speaks to Thursday and to nobody else; you never address them, and what you hand back goes into ${askedBy}'s report as-is. This thread is drawn on their screen while you work: not written to them, but not private either. ${nowLine()}`,
|
|
103
130
|
MACHINE,
|
|
104
131
|
NO_GUESSING,
|
|
105
132
|
].join("\n\n");
|
|
@@ -107,7 +134,7 @@ function borrowedIdentity(askedBy: string): string {
|
|
|
107
134
|
|
|
108
135
|
const MACHINE = `**You are on a real computer — the user's own.** You have a shell and a filesystem, and the job is done, not described: where there is no tool for something, write one — in \`node\` unless they asked for another language, the runtime this app itself runs on. Reach for \`${TOOL_NAMES.bash}\` before concluding that something cannot be done, but after the roster, not instead of it: a script you write to do another bot's job is the long way round.
|
|
109
136
|
|
|
110
|
-
**What is missing gets installed
|
|
137
|
+
**What is missing gets installed**, onto the machine once they say yes. What is already there is nobody's question — Environment says what this machine has.
|
|
111
138
|
|
|
112
139
|
**Bring back the thing itself** — their own machine, their own accounts, their own copy of whatever they sent you for — not a smaller safer version of it, and not a note on why you did not.`;
|
|
113
140
|
|
|
@@ -134,7 +161,9 @@ function memory(
|
|
|
134
161
|
): string {
|
|
135
162
|
const head = `## Memory
|
|
136
163
|
|
|
137
|
-
What the user's assistant knows about them
|
|
164
|
+
What the user's assistant knows about them. Thursday keeps it as she talks with them, and everyone reads it.
|
|
165
|
+
|
|
166
|
+
Yours to read, and to add to when the work turns up something about *them* that outlives the job; what the job itself turned up goes in your report. It describes the user, not you: how they are named and spoken to is hers to use with them, not yours to borrow. What you learn about this machine is not memory — that is your own instructions, below.`;
|
|
138
167
|
|
|
139
168
|
const alreadyKnown = carried.length
|
|
140
169
|
? `Already known:
|
|
@@ -151,6 +180,24 @@ A topic not listed is one nobody knows anything about.`;
|
|
|
151
180
|
return [head, alreadyKnown, listing].filter(Boolean).join("\n\n");
|
|
152
181
|
}
|
|
153
182
|
|
|
183
|
+
/**
|
|
184
|
+
* The bot's own notes, carried between jobs (database bot_note). Always drawn, empty or not,
|
|
185
|
+
* so a first job knows it has them. What belongs in them is on `report`, the only place a
|
|
186
|
+
* change can be asked for (tools/bot.tool); the chapter is what they are and that they last.
|
|
187
|
+
* The line about what goes in first shows only while they are empty — a standing instruction
|
|
188
|
+
* to write something turns every job into a note, and there is nothing to nudge once a bot
|
|
189
|
+
* has one. The count is here for the same reason: the room left is a fact, not a target.
|
|
190
|
+
*/
|
|
191
|
+
function notes(own: string | null): string {
|
|
192
|
+
return `## Your own instructions
|
|
193
|
+
|
|
194
|
+
What you wrote to yourself on earlier jobs here, to get better at this machine. Only you read it, and it is the one thing that reaches your next job. What belongs in it is what working here is like — the command that turns out to be the right one, the flag it needs, the tool this platform does not have. Not what the user is like: that is Memory, above, and everyone reads that one.
|
|
195
|
+
|
|
196
|
+
${own ?? "Empty. What usually goes in first is whatever you had to find out before you could start."}
|
|
197
|
+
|
|
198
|
+
(${own?.length ?? 0}/${BOT_NOTES.chars} characters)`;
|
|
199
|
+
}
|
|
200
|
+
|
|
154
201
|
/**
|
|
155
202
|
* Names only; schemas stay behind `tool_search`. Without the list the model cannot tell
|
|
156
203
|
* "no such tool" from "not searched yet".
|
|
@@ -184,13 +231,35 @@ Written-down methods. When the job is one of these, read it with \`${TOOL_NAMES.
|
|
|
184
231
|
${skillLines(skills)}`;
|
|
185
232
|
}
|
|
186
233
|
|
|
187
|
-
/**
|
|
188
|
-
|
|
234
|
+
/**
|
|
235
|
+
* The machine, then the workspace. What is installed is read as the prompt is
|
|
236
|
+
* assembled (workspace.ts readMachineTools) rather than left to the job to find
|
|
237
|
+
* out: a bot that has to check first spends a step on it, and one that guesses
|
|
238
|
+
* writes for a runtime that is not here. Naming what is absent does as much
|
|
239
|
+
* work as naming what is present — it is the half a model otherwise assumes.
|
|
240
|
+
* The three folders are named once; `write_file` refuses anything else
|
|
241
|
+
* (workspace.ts writeRefusal).
|
|
242
|
+
*/
|
|
243
|
+
const environment = (
|
|
244
|
+
cwd: string,
|
|
245
|
+
machine: MachineTools,
|
|
246
|
+
folders?: { scratch: string | null; own: string },
|
|
247
|
+
) => `## Environment
|
|
189
248
|
|
|
190
249
|
Current Cwd: ${cwd}
|
|
191
250
|
Platform: ${process.platform}
|
|
251
|
+
${machineLines(machine)}
|
|
252
|
+
|
|
253
|
+
Read off this machine as the job opened, so it is current: reach for what is here instead of checking for it.
|
|
254
|
+
|
|
255
|
+
Your workspace — \`${TOOL_NAMES.bash}\` runs here. Everything you write goes in one of these, and they are kept apart because what is in them lives for different lengths of time:
|
|
192
256
|
|
|
193
|
-
|
|
257
|
+
- \`${PATHS.artifacts}/\` — finished work the user opens, one entry per result. Theirs, and it stays.
|
|
258
|
+
- \`${PATHS.projects}/\` — code you build, one folder each. It outlives this job, and a project's dependencies install inside it, never at the workspace root.
|
|
259
|
+
- \`${folders?.scratch ?? PATHS.scratch}/\` — this job's working material. It goes when the job does, so nothing here is worth keeping.
|
|
260
|
+
- \`${folders?.own ?? PATHS.bots}/\` — yours, across every job you run here. A script you wrote once and will want again, a table you built. Your own instructions can name what is in it.
|
|
261
|
+
|
|
262
|
+
Never the directory above — that is the app you run in; outside the workspace, only where the user pointed you.`;
|
|
194
263
|
|
|
195
264
|
/** Same roster the voice prompt shows, used the other way: which part of a held job is another bot's. */
|
|
196
265
|
function roster(peers: JobBot[]): string {
|
|
@@ -200,6 +269,8 @@ function roster(peers: JobBot[]): string {
|
|
|
200
269
|
|
|
201
270
|
${peers.map((bot) => `- **${bot.name}** — ${bot.description}`).join("\n")}
|
|
202
271
|
|
|
272
|
+
These lines were written for the user, who reads them on their own screen: where one says "you" it means them, not you.
|
|
273
|
+
|
|
203
274
|
A part of your job another bot is for goes to \`${TOOL_NAMES.ask_bot}\`, because their tools and their practice are the reason they exist. What you build is made of what they bring back, so send for it before you build. A part you could finish in a couple of commands is yours; one that would take a run of its own is theirs. The job stays yours and you report; they see only your brief — not the call, not this thread.`;
|
|
204
275
|
}
|
|
205
276
|
|
|
@@ -260,3 +331,27 @@ A detail that is not in the request may be here — a name, a number, which one
|
|
|
260
331
|
|
|
261
332
|
${lines.join("\n")}`;
|
|
262
333
|
}
|
|
334
|
+
|
|
335
|
+
/** One line per kind, absences included. Only a bot gets these: the call runs one command as a glance. */
|
|
336
|
+
function machineLines(machine: MachineTools): string {
|
|
337
|
+
const line = (label: string, of: MachineTools["runtimes"]) =>
|
|
338
|
+
`${label}: ${of.found.length ? of.found.join(", ") : "none"}.${
|
|
339
|
+
of.missing.length ? ` Not here: ${of.missing.join(", ")}.` : ""
|
|
340
|
+
}`;
|
|
341
|
+
// Not a preference: openWorkspace writes the pnpm fence, and an `npm install`
|
|
342
|
+
// in a project here resolves against that rather than against the project
|
|
343
|
+
const fenced = machine.managers.found.includes("pnpm")
|
|
344
|
+
? " Use pnpm, not npm — this workspace is fenced for it (`pnpm-workspace.yaml`, `.npmrc`)."
|
|
345
|
+
: "";
|
|
346
|
+
return [
|
|
347
|
+
line("Runtimes", machine.runtimes),
|
|
348
|
+
`${line("Package managers", machine.managers)}${fenced}`,
|
|
349
|
+
machine.browser === null
|
|
350
|
+
? ""
|
|
351
|
+
: machine.browser
|
|
352
|
+
? "Browser: installed."
|
|
353
|
+
: "Browser: not installed — `playwright-cli install-browser chromium` puts one there.",
|
|
354
|
+
]
|
|
355
|
+
.filter(Boolean)
|
|
356
|
+
.join("\n");
|
|
357
|
+
}
|
|
@@ -13,7 +13,12 @@ import {
|
|
|
13
13
|
} from "@/features/memory/memory.schema";
|
|
14
14
|
import type { OwedTurn } from "@/features/thursday/thursday.query";
|
|
15
15
|
import { logger } from "@/lib/logger";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
carriedLines,
|
|
18
|
+
logPromptSize,
|
|
19
|
+
noteLines,
|
|
20
|
+
nowLine,
|
|
21
|
+
} from "./prompt-helper";
|
|
17
22
|
|
|
18
23
|
/**
|
|
19
24
|
* Everything the read-back hears (features/memory/memory.tidy). One system text
|
|
@@ -39,6 +44,7 @@ export async function loadTidyPrompt(
|
|
|
39
44
|
]);
|
|
40
45
|
const system = [identity(), memory(index, carried), pass()].join("\n\n");
|
|
41
46
|
const user = [transcript(read), written(read.written)].join("\n\n");
|
|
47
|
+
logPromptSize("tidy", `${system}\n\n${user}`);
|
|
42
48
|
logger.debug(`tidy prompt\n${system}\n\n${user}`);
|
|
43
49
|
return { system, user };
|
|
44
50
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { format, formatDistanceToNowStrict } from "date-fns";
|
|
2
|
-
import { MEMORY_LISTING_TOKENS, PROMPT_LINE } from "@/config";
|
|
2
|
+
import { MEMORY_LISTING_TOKENS, PROMPT_BUDGET, PROMPT_LINE } from "@/config";
|
|
3
3
|
import { TOOL_NAMES } from "@/features/ai/tools/tool-name";
|
|
4
4
|
import type { McpToolRef } from "@/features/connectors/mcp.schema";
|
|
5
5
|
import type {
|
|
@@ -9,7 +9,8 @@ import type {
|
|
|
9
9
|
import { MANY_FACTS } from "@/features/memory/memory.schema";
|
|
10
10
|
import type { SkillMetadata } from "@/features/skills/skills.discover";
|
|
11
11
|
import { toDate } from "@/lib/date-like";
|
|
12
|
-
import {
|
|
12
|
+
import { logger } from "@/lib/logger";
|
|
13
|
+
import { estimateTokens, sectionTokens } from "@/lib/tokens";
|
|
13
14
|
import { clip } from "@/lib/utils";
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -26,12 +27,37 @@ export type LoadedPrompt = {
|
|
|
26
27
|
opening: string | null;
|
|
27
28
|
};
|
|
28
29
|
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* What one assembled prompt costs, said where it is assembled. The debug line is
|
|
32
|
+
* the whole breakdown; past PROMPT_BUDGET it is a warning naming the chapter
|
|
33
|
+
* carrying it, and that one is always a listing the user can act on — nothing
|
|
34
|
+
* else in the app would ever say a prompt had grown.
|
|
35
|
+
*/
|
|
36
|
+
export function logPromptSize(kind: string, text: string): void {
|
|
37
|
+
const parts = sectionTokens(text);
|
|
38
|
+
const total = parts.reduce((sum, part) => sum + part.tokens, 0);
|
|
39
|
+
logger.debug(
|
|
40
|
+
`${kind} prompt ${total} tokens — ${parts
|
|
41
|
+
.map((part) => `${part.name} ${part.tokens}`)
|
|
42
|
+
.join(", ")}`,
|
|
43
|
+
);
|
|
44
|
+
const biggest = parts[0];
|
|
45
|
+
if (total > PROMPT_BUDGET && biggest) {
|
|
46
|
+
logger.warn(
|
|
47
|
+
`${kind} prompt is ${total} tokens, over ${PROMPT_BUDGET}: ${biggest.name} carries ${biggest.tokens} of it. Every call and every job pays this.`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** `2026-09-02 (Wed) 15:41 Asia/Seoul` */
|
|
53
|
+
export const clockNow = (now = new Date()) =>
|
|
54
|
+
`${format(now, "yyyy-MM-dd (EEE) HH:mm")} ${
|
|
32
55
|
Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
33
56
|
}`;
|
|
34
57
|
|
|
58
|
+
/** `**Now**: 2026-09-02 (Wed) 15:41 Asia/Seoul` */
|
|
59
|
+
export const nowLine = (now = new Date()) => `**Now**: ${clockNow(now)}`;
|
|
60
|
+
|
|
35
61
|
/** `4mo`, `12d`: short enough to read aloud. */
|
|
36
62
|
function sinceLast(at: MemoryIndexEntry["lastSeenAt"]): string {
|
|
37
63
|
const days = Math.floor((Date.now() - toDate(at).getTime()) / 86_400_000);
|
|
@@ -29,6 +29,7 @@ import { listConnectedToolNames } from "../tools/connected";
|
|
|
29
29
|
import {
|
|
30
30
|
carriedLines,
|
|
31
31
|
type LoadedPrompt,
|
|
32
|
+
logPromptSize,
|
|
32
33
|
mcpServerLines,
|
|
33
34
|
noteLines,
|
|
34
35
|
nowLine,
|
|
@@ -85,6 +86,7 @@ export async function loadThursdayPrompt(
|
|
|
85
86
|
.filter(Boolean)
|
|
86
87
|
.join("\n\n");
|
|
87
88
|
|
|
89
|
+
logPromptSize("thursday", text);
|
|
88
90
|
logger.debug(`thursday prompt\n${text}`);
|
|
89
91
|
|
|
90
92
|
return {
|
|
@@ -94,7 +94,7 @@ export const askThursdaySpec = {
|
|
|
94
94
|
// joined string, and a schema rejection only loops. The reader takes arrays only (task.query optionsOf)
|
|
95
95
|
options: z
|
|
96
96
|
.union([z.string().array(), z.string()])
|
|
97
|
-
.
|
|
97
|
+
.nullish()
|
|
98
98
|
.describe(
|
|
99
99
|
'Only when there really are choices — two or three, each short, as an array: ["Signed in", "Not yet"]. Null for an open question.',
|
|
100
100
|
),
|
|
@@ -104,8 +104,11 @@ export const askThursdaySpec = {
|
|
|
104
104
|
/**
|
|
105
105
|
* The one way a run ends on its own terms; the loop is stopped by it (`hasToolCall`, bot.run).
|
|
106
106
|
* `complete` cannot be inferred: a run out of steps and a finished run look the same from outside.
|
|
107
|
+
* `notes` is what the bot wants changed about its own instructions, in its own words — never
|
|
108
|
+
* the text itself. A bot that rewrites the whole thing here edits it around the job it was on
|
|
109
|
+
* and drops what that job was not about; a pass of its own writes it (features/bot/bot.notes).
|
|
107
110
|
*/
|
|
108
|
-
export const reportSpec = {
|
|
111
|
+
export const reportSpec = (notes: boolean) => ({
|
|
109
112
|
name: TOOL_NAMES.report,
|
|
110
113
|
description:
|
|
111
114
|
"Hand back what the job produced. Calling this ends the job — nothing after it runs.",
|
|
@@ -120,8 +123,21 @@ export const reportSpec = {
|
|
|
120
123
|
.describe(
|
|
121
124
|
"True when the whole request is done. False when work is left over — the user is then asked whether to keep going, and `result` is what they decide on.",
|
|
122
125
|
),
|
|
126
|
+
...(notes
|
|
127
|
+
? {
|
|
128
|
+
notes: reportNotes,
|
|
129
|
+
}
|
|
130
|
+
: {}),
|
|
123
131
|
}),
|
|
124
|
-
};
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
/** Only attached while Settings > Bots keeps them (bot.schema BOT_NOTES_KEY). */
|
|
135
|
+
const reportNotes = z
|
|
136
|
+
.string()
|
|
137
|
+
.nullish()
|
|
138
|
+
.describe(
|
|
139
|
+
"What should change in your own instructions before your next job here — something to add, something wrong to correct, something to drop. Your own words, any length; someone else writes it in. Nothing to say is the usual answer, and leaving it out costs nothing: they already say what they say.",
|
|
140
|
+
);
|
|
125
141
|
|
|
126
142
|
/** The voice session's handle on a job already handed over. Run by the server, like `delegate` (load-tools). */
|
|
127
143
|
export const taskSpec = {
|
|
@@ -132,13 +148,13 @@ export const taskSpec = {
|
|
|
132
148
|
action: z.enum(["status", "answer", "cancel"]),
|
|
133
149
|
task: z
|
|
134
150
|
.string()
|
|
135
|
-
.
|
|
151
|
+
.nullish()
|
|
136
152
|
.describe(
|
|
137
153
|
"The job, by its label or by the handle a past call's transcript carries. With `status`: null for every job in one line each — that is the list — or name one to get its report in full.",
|
|
138
154
|
),
|
|
139
155
|
answer: z
|
|
140
156
|
.string()
|
|
141
|
-
.
|
|
157
|
+
.nullish()
|
|
142
158
|
.describe(
|
|
143
159
|
"With `answer`: what to tell the bot — the answer to its question, a course correction for a job still running, or what to do next on one that finished.",
|
|
144
160
|
),
|
|
@@ -157,19 +173,22 @@ export const askThursdayTool = tool({
|
|
|
157
173
|
* the call and its result are both written to the thread, and a resumed run
|
|
158
174
|
* reads a finished exchange rather than a call left hanging.
|
|
159
175
|
*/
|
|
160
|
-
export const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
176
|
+
export const createReportTool = (notes: boolean) => {
|
|
177
|
+
const spec = reportSpec(notes);
|
|
178
|
+
return tool({
|
|
179
|
+
description: spec.description,
|
|
180
|
+
inputSchema: spec.parameters,
|
|
181
|
+
execute: async ({ result, complete }) => {
|
|
182
|
+
const missing = await missingFiles(result);
|
|
183
|
+
if (missing.length) {
|
|
184
|
+
return `${NOT_HANDED_BACK} these files do not exist — ${missing.join(", ")}. Write them first, or take the paths out of the result.`;
|
|
185
|
+
}
|
|
186
|
+
return complete
|
|
187
|
+
? "Handed back. The job is closed; nothing further runs."
|
|
188
|
+
: "Handed back as unfinished. The user decides whether to continue.";
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
};
|
|
173
192
|
|
|
174
193
|
/**
|
|
175
194
|
* A report naming a workspace file that does not exist is refused: the path becomes a link
|