thursday-agent 0.1.1 → 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/{_1r2_qk-._.js → _09ja_w1._.js} +2 -2
- package/.next/server/chunks/ssr/{_1dnxp2k._.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/_0w5bgak._.js +3 -0
- package/.next/server/chunks/ssr/{_1hkvsg0._.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/{_1b3yex3._.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_0i37h34._.js → node_modules_004qlr4._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0nh2m35._.js → node_modules_01qnv-a._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1kj6swe._.js → node_modules_04n7mjs._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0eqdzva._.js → node_modules_08d8bke._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0nq_p-o._.js → node_modules_09xhur7._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1854ebs._.js → node_modules_0_n241r._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_00riwzq._.js → node_modules_0iz1rzd._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1wsx1pg._.js → node_modules_0kl7-tn._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_17ls31t._.js → node_modules_0kxz4gt._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1fsvm8f._.js → node_modules_0lrjk5t._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1euv75v._.js → node_modules_0m93zeg._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1_w_b81._.js → node_modules_0n1kj4p._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1bjm3ch._.js → node_modules_0ndvxub._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_03f-6up._.js → node_modules_13r44jq._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_07_oxjr._.js → node_modules_17lrt3y._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1yxkq1s._.js → node_modules_18_dk7_._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_012m-mo._.js → node_modules_19dp7ko._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0egn4iq._.js → node_modules_1dwguw_._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1y1m-q4._.js → node_modules_1hd-5tv._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0xe_k12._.js → node_modules_1hn8z1f._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0sbobpz._.js → node_modules_1n5ygqe._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_14uxpfb._.js → node_modules_1q0p85z._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_01jj5oy._.js → node_modules_1toafc-._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ewynyi._.js → node_modules_1xyd7ov._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_17c71fz._.js → node_modules_1zu988w._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1mnmhse._.js → node_modules_20yebtj._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1f5re6n._.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_216aa7f._.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/{1rja48de93vvx.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/{3yan6-5cvpk2o.js → 18vhba44l1fc9.js} +1 -1
- package/.next/static/chunks/19i8h2t-5evjx.js +1 -0
- package/.next/static/chunks/{0h7pjyewk7ky-.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/{2kl3n2d-4-7i5.js → 28gotuisldmom.js} +1 -1
- package/.next/static/chunks/{34eu8t79ko6lm.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/{2ka9uwo_evj7a.js → 2t8wqycwg3y04.js} +2 -2
- package/.next/static/chunks/32z_ypgtchw48.js +1 -0
- package/.next/static/chunks/{261871a8ot1pi.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/{2qci2z603es_m.js → 3ard_noqapnw1.js} +1 -1
- package/.next/static/chunks/{0yn2yol46cj77.js → 3omud5xunrk1f.js} +1 -1
- package/.next/static/chunks/42dnrkbph9e-1.js +1 -0
- package/README.md +24 -1
- 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/bin/thursday.mjs +50 -2
- 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]__0b-0i32._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1e-f3gy._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1minz8_._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1ncajna._.js +0 -3
- package/.next/server/chunks/ssr/_124q1la._.js +0 -3
- package/.next/server/chunks/ssr/_19eoufz._.js +0 -3
- package/.next/server/chunks/ssr/_1aca4j8._.js +0 -3
- package/.next/server/chunks/ssr/_1cb_5sv._.js +0 -3
- package/.next/server/chunks/ssr/_1m5popy._.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/0_6qjj4f3t3j-.js +0 -1
- package/.next/static/chunks/0g2n0xskvyk04.js +0 -2
- package/.next/static/chunks/14s7e14xltx47.js +0 -1
- package/.next/static/chunks/1lpdcqos7mkek.js +0 -1
- package/.next/static/chunks/1u5d-pvhwir9g.js +0 -1
- package/.next/static/chunks/2cgzsjzheo2m2.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/2v_vqhmdl8de_.js +0 -1
- package/.next/static/chunks/35uo6rtl_otbd.js +0 -1
- package/.next/static/chunks/388-t2v72jh39.css +0 -1
- package/.next/static/chunks/3w7-y53xzl2ir.js +0 -1
- /package/.next/static/{G-9_HnysmzY_Bx9GpDYAO → YR4NWcDDuzwLbTTU7U7Vr}/_buildManifest.js +0 -0
- /package/.next/static/{G-9_HnysmzY_Bx9GpDYAO → YR4NWcDDuzwLbTTU7U7Vr}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{G-9_HnysmzY_Bx9GpDYAO → YR4NWcDDuzwLbTTU7U7Vr}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -133,7 +133,30 @@ And after every call a text model **re-reads what was actually said** and reconc
|
|
|
133
133
|
npx thursday-agent
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
Opens at `localhost:3000
|
|
136
|
+
Opens at `localhost:3000`, or the next free port if something already has it. One OpenAI or xAI key opens the call — a voice key is also a text key, so one is enough to run everything.
|
|
137
|
+
|
|
138
|
+
### Running it
|
|
139
|
+
|
|
140
|
+
It runs in the terminal you started it in, like `n8n`. There is no daemon and nothing is installed into your login items.
|
|
141
|
+
|
|
142
|
+
| | |
|
|
143
|
+
|---|---|
|
|
144
|
+
| **Stop it** | `Ctrl+C` |
|
|
145
|
+
| **Start it again** | the same `npx thursday-agent` |
|
|
146
|
+
| **Close the terminal** | it stops with the terminal — nothing is left running |
|
|
147
|
+
| **Keep it up while you work** | run it in its own tab, or `npx thursday-agent &` |
|
|
148
|
+
| **Another port** | `npx thursday-agent --port 4000` |
|
|
149
|
+
| **Somewhere else on disk** | `npx thursday-agent --home ~/work/thursday` |
|
|
150
|
+
|
|
151
|
+
**Your data outlives all of it.** Everything you say, everything the bots write, your keys and your memory live in `~/.thursday`, never inside the package — so restarting, rebooting, or upgrading to the next version keeps every call and every file. Deleting that folder is the uninstall.
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
~/.thursday
|
|
155
|
+
├── local.db calls, memory, bots, jobs, keys
|
|
156
|
+
└── .ai-workspace what the bots wrote: artifacts, scratch, skills
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The first run also fetches the browser bots drive (~280 MB) in the background, once. That one lands in Playwright's own cache — `~/Library/Caches/ms-playwright`, or `~/.cache/ms-playwright` on Linux — so every run after it, and every upgrade, skips the download.
|
|
137
160
|
|
|
138
161
|
<details>
|
|
139
162
|
<summary><b>From source</b></summary>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import { queryKey } from "@/app/api/query-key";
|
|
5
|
+
import { createEventBus, type EventHandlers } from "@/lib/protocol/events";
|
|
6
|
+
import { fromEventSource, useEventFeed } from "@/lib/protocol/events.client";
|
|
7
|
+
import type { AppEvent } from "./app-event";
|
|
8
|
+
|
|
9
|
+
/** Browser-side bus. Only server events flow here; `emit` is not exported. */
|
|
10
|
+
const bus = createEventBus<AppEvent>();
|
|
11
|
+
|
|
12
|
+
/** Subscribes to server events; pass handlers for the types you care about. */
|
|
13
|
+
export function useAppEvent(handlers: EventHandlers<AppEvent>) {
|
|
14
|
+
useEventFeed(bus, handlers);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Opens the one connection per tab, from the root layout. EventSource
|
|
19
|
+
* reconnects on its own and replays the on-connect events.
|
|
20
|
+
*/
|
|
21
|
+
export function AppEventSource() {
|
|
22
|
+
useEffect(() => fromEventSource(queryKey.events, bus), []);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { BROWSER_GONE_MS } from "@/config";
|
|
2
|
+
import { createEventBus, type EventBus } from "@/lib/protocol/events";
|
|
3
|
+
import {
|
|
4
|
+
createEventStream,
|
|
5
|
+
type EventStream,
|
|
6
|
+
} from "@/lib/protocol/events.server";
|
|
7
|
+
import { createPresence, type Presence } from "@/lib/protocol/presence";
|
|
8
|
+
import { type AppEvent, isSignal } from "./app-event";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Server-side bus and the stream that carries it to the browser. Pinned on
|
|
12
|
+
* globalThis because next dev reloads this module while open streams still
|
|
13
|
+
* subscribe to the first bus.
|
|
14
|
+
*/
|
|
15
|
+
type Pinned = {
|
|
16
|
+
__appEvents?: EventBus<AppEvent>;
|
|
17
|
+
__appEventStream?: EventStream<AppEvent>;
|
|
18
|
+
__presence?: Presence;
|
|
19
|
+
__bootId?: string;
|
|
20
|
+
};
|
|
21
|
+
const pinned = globalThis as Pinned;
|
|
22
|
+
|
|
23
|
+
export const appEvents: EventBus<AppEvent> = (pinned.__appEvents ??=
|
|
24
|
+
createEventBus<AppEvent>());
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Whether a browser is on the stream. The app stops what it is doing when the
|
|
28
|
+
* last one has been gone for BROWSER_GONE_MS; who stops what is wired at boot
|
|
29
|
+
* (instrumentation), not here.
|
|
30
|
+
*/
|
|
31
|
+
export const presence: Presence = (pinned.__presence ??=
|
|
32
|
+
createPresence(BROWSER_GONE_MS));
|
|
33
|
+
|
|
34
|
+
/** Signals coalesce over 150ms; data events pass through. */
|
|
35
|
+
export const appEventStream: EventStream<AppEvent> =
|
|
36
|
+
(pinned.__appEventStream ??= createEventStream(appEvents, {
|
|
37
|
+
coalesce: {
|
|
38
|
+
ms: 150,
|
|
39
|
+
keyOf: (event) => (isSignal(event) ? event.type : null),
|
|
40
|
+
},
|
|
41
|
+
onWatchers: presence.track,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
/** Identifies this server process; changes on restart. */
|
|
45
|
+
export const BOOT_ID: string = (pinned.__bootId ??= crypto.randomUUID());
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every event the server sends the browser. Two kinds: a signal carries no data
|
|
3
|
+
* and the receiver revalidates the matching GET; a data event is the value
|
|
4
|
+
* itself. A new signal must also be added to `SIGNALS` below.
|
|
5
|
+
*/
|
|
6
|
+
export type AppEvent =
|
|
7
|
+
/** Sent on connect. A changed `boot` means the server restarted; re-read everything. */
|
|
8
|
+
| { type: "hello"; boot: string }
|
|
9
|
+
/** Signal: a task changed (every write in bot.query). */
|
|
10
|
+
| { type: "tasks" }
|
|
11
|
+
/** Signal: a note or fact changed. */
|
|
12
|
+
| { type: "memory" }
|
|
13
|
+
/** Signal: a server was registered, removed, synced, or finished OAuth. */
|
|
14
|
+
| { type: "mcp" }
|
|
15
|
+
/** Signal: a read-back started or ended (memory/tidy.query). */
|
|
16
|
+
| { type: "memory-tidy" }
|
|
17
|
+
/** Data: a read-back ended on its own. `tally` is one line of what changed, or why it failed. */
|
|
18
|
+
| { type: "memory-tidied"; failed: boolean; tally: string }
|
|
19
|
+
/** Data: show this note on screen, or hide it when null (memory.tool). */
|
|
20
|
+
| { type: "memory-view"; path: string | null }
|
|
21
|
+
/** Data: a finished job produced a document; `path` is workspace-relative. */
|
|
22
|
+
| { type: "artifact"; taskId: string; label: string; path: string };
|
|
23
|
+
|
|
24
|
+
/** Union members carrying nothing but `type`. */
|
|
25
|
+
type Signal<E = AppEvent> = E extends AppEvent
|
|
26
|
+
? keyof E extends "type"
|
|
27
|
+
? E["type"]
|
|
28
|
+
: never
|
|
29
|
+
: never;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Signals are coalesced before sending (app-event.server). The `Record` type
|
|
33
|
+
* makes a signal missing here a compile error.
|
|
34
|
+
*/
|
|
35
|
+
export const SIGNALS: Record<Signal, true> = {
|
|
36
|
+
tasks: true,
|
|
37
|
+
memory: true,
|
|
38
|
+
mcp: true,
|
|
39
|
+
"memory-tidy": true,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const isSignal = (
|
|
43
|
+
event: AppEvent,
|
|
44
|
+
): event is Extract<AppEvent, { type: Signal }> => event.type in SIGNALS;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readGatewayCatalog } from "@/features/ai/model";
|
|
2
|
+
import type { GatewayModel } from "@/features/ai/model.schema";
|
|
3
|
+
import { serverRoute } from "@/lib/protocol/server-route";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Everything the gateway carries, every modality in one listing. It answers
|
|
7
|
+
* unauthenticated, so the shelf reads before a key is set; only running a model
|
|
8
|
+
* needs one. The answer is built in ai/model, where `callableRows` reads the same list.
|
|
9
|
+
*/
|
|
10
|
+
export const GET = serverRoute(
|
|
11
|
+
async (): Promise<GatewayModel[]> => readGatewayCatalog(),
|
|
12
|
+
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AiProvider,
|
|
3
|
+
TEXT_MODEL_PROVIDER_LIST,
|
|
4
|
+
} from "@/features/ai/model.schema";
|
|
5
|
+
import { readConfig } from "@/features/config/config.query";
|
|
6
|
+
import { serverRoute } from "@/lib/protocol/server-route";
|
|
7
|
+
|
|
8
|
+
/** Callable providers and whether each one's key is set. */
|
|
9
|
+
export const GET = serverRoute(async () =>
|
|
10
|
+
Promise.all(
|
|
11
|
+
TEXT_MODEL_PROVIDER_LIST.map(
|
|
12
|
+
async (item): Promise<AiProvider> => ({
|
|
13
|
+
id: item.id,
|
|
14
|
+
label: item.label,
|
|
15
|
+
apiKeyName: item.apiKeyName,
|
|
16
|
+
suggestModels: item.suggestModels,
|
|
17
|
+
hasKey: Boolean(await readConfig(item.apiKeyName)),
|
|
18
|
+
}),
|
|
19
|
+
),
|
|
20
|
+
),
|
|
21
|
+
);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every endpoint the browser reads. Keys double as SWR cache keys; `revalidate`
|
|
3
|
+
* matches by url prefix, so invalidating `queryKey.memory` also refreshes
|
|
4
|
+
* `queryKey.notePath(path)`. Writes go through server actions, not here.
|
|
5
|
+
*/
|
|
6
|
+
/** Encodes per segment, keeping the path's `/`. */
|
|
7
|
+
const encodePath = (path: string) =>
|
|
8
|
+
path.split("/").map(encodeURIComponent).join("/");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Inverse of encodePath for catch-all segments. No decoding: Next already
|
|
12
|
+
* decodes segments, and decoding again throws on names containing `%`.
|
|
13
|
+
*/
|
|
14
|
+
export const decodePath = (segments: string[]) => segments.join("/");
|
|
15
|
+
|
|
16
|
+
export const queryKey = {
|
|
17
|
+
/** MemoryNote[] first page: pinned first, then by warmth */
|
|
18
|
+
memory: "/api/memory",
|
|
19
|
+
/** Next page; `offset` is the count received so far */
|
|
20
|
+
memoryPage: (offset: number) => ({
|
|
21
|
+
url: "/api/memory",
|
|
22
|
+
query: { offset: offset || null },
|
|
23
|
+
}),
|
|
24
|
+
/** One note with all facts, by path. Paths contain `/`, so it is a query, not a
|
|
25
|
+
* segment: pass the hook null instead of building this key when closed. */
|
|
26
|
+
notePath: (path: string) => ({ url: "/api/memory", query: { path } }),
|
|
27
|
+
/** MemoryTidyStatusView: settings, what is pending, the pass in progress and the last one. Under the memory prefix on purpose: a fact changing moves it too. */
|
|
28
|
+
memoryTidy: "/api/memory/tidy",
|
|
29
|
+
|
|
30
|
+
/** MCPServerSummary[] with tool counts, no tools */
|
|
31
|
+
mcp: "/api/mcp",
|
|
32
|
+
/** MCPServer, one server with its tools */
|
|
33
|
+
mcpServer: (name: string | null) => ({
|
|
34
|
+
url: "/api/mcp",
|
|
35
|
+
pathVariable: [name],
|
|
36
|
+
}),
|
|
37
|
+
|
|
38
|
+
/** Every MCP tool across servers, flat */
|
|
39
|
+
mcpTools: "/api/mcp/tools",
|
|
40
|
+
|
|
41
|
+
/** SkillSummary[], custom first */
|
|
42
|
+
skills: "/api/skills",
|
|
43
|
+
/**
|
|
44
|
+
* SkillNode: a folder listing or a file's text inside a skill.
|
|
45
|
+
* `path` is relative to the skill folder; "" is the skill itself.
|
|
46
|
+
*/
|
|
47
|
+
skillNode: (source: string | null, dir: string | null, path: string) => ({
|
|
48
|
+
url: "/api/skills",
|
|
49
|
+
pathVariable: [source, dir],
|
|
50
|
+
query: { path },
|
|
51
|
+
}),
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Bot[]. `tasks` and `taskHistory` sit under this prefix, so editing a bot
|
|
55
|
+
* also revalidates the inbox and loaded history pages.
|
|
56
|
+
*/
|
|
57
|
+
bot: "/api/bot",
|
|
58
|
+
|
|
59
|
+
/** boolean: whether bots keep their own instructions (Settings > Bots) */
|
|
60
|
+
botNotes: "/api/bot/notes",
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Inbox: Task[] newest first with threads, everything running or asking plus
|
|
64
|
+
* the most recent finished few. Read by use-thursday only; the `tasks` signal
|
|
65
|
+
* triggers revalidation, with a 30s poll as fallback.
|
|
66
|
+
*/
|
|
67
|
+
tasks: "/api/bot/task",
|
|
68
|
+
/**
|
|
69
|
+
* History page: Task[] newest first. `before` is the last page's final
|
|
70
|
+
* updatedAt (ISO), null for the first page.
|
|
71
|
+
*/
|
|
72
|
+
taskHistory: (before: string | null) => ({
|
|
73
|
+
url: "/api/bot/task",
|
|
74
|
+
query: { history: 1, before },
|
|
75
|
+
}),
|
|
76
|
+
/** ResultPart[]: the full result of one tool call; lists carry only a few lines. */
|
|
77
|
+
toolResult: (taskId: string | null, callId: string | null) => ({
|
|
78
|
+
url: "/api/bot/task",
|
|
79
|
+
pathVariable: [taskId],
|
|
80
|
+
query: { call: callId },
|
|
81
|
+
}),
|
|
82
|
+
/**
|
|
83
|
+
* WorkspaceFolder: one folder's rows, and nothing below them. The bare key
|
|
84
|
+
* is the root, so invalidating it refreshes every folder.
|
|
85
|
+
*/
|
|
86
|
+
workspace: "/api/workspace",
|
|
87
|
+
workspaceFolder: (path: string, rows: number) => ({
|
|
88
|
+
url: "/api/workspace",
|
|
89
|
+
query: { path: path || null, rows },
|
|
90
|
+
}),
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* ArtifactShelf: the top of `artifacts/`, one row per artifact.
|
|
94
|
+
* `artifactSet` opens one of those rows. The bare key is the menu, so
|
|
95
|
+
* invalidating it refreshes an open set too.
|
|
96
|
+
*/
|
|
97
|
+
artifacts: "/api/artifact",
|
|
98
|
+
artifactShelf: (rows: number) => ({
|
|
99
|
+
url: "/api/artifact",
|
|
100
|
+
query: { rows },
|
|
101
|
+
}),
|
|
102
|
+
artifactSet: (name: string, rows: number) => ({
|
|
103
|
+
url: "/api/artifact",
|
|
104
|
+
query: { set: name, rows },
|
|
105
|
+
}),
|
|
106
|
+
|
|
107
|
+
/** Raw workspace file, no Result envelope; for iframe, img and fetch, not SWR. */
|
|
108
|
+
file: (path: string) => `/api/file/${encodePath(path)}`,
|
|
109
|
+
/** File viewer page (new tab): md rendered, html and pdf framed */
|
|
110
|
+
fileView: (path: string) => `/artifact/${encodePath(path)}`,
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Server-sent events, not a read; only app-event.client's `AppEventSource`
|
|
114
|
+
* opens it. The payloads are the AppEvent union in app/api/events/app-event.ts.
|
|
115
|
+
*/
|
|
116
|
+
events: "/api/events",
|
|
117
|
+
|
|
118
|
+
/** ConfigStatus[]: which declared config keys are set; values stay on the server */
|
|
119
|
+
config: "/api/config",
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* CallRecord[] page, newest call first, turns in speaking order; pages count
|
|
123
|
+
* calls (CALL_HISTORY_PAGE). `before` is the oldest call's `startedAt` (ISO)
|
|
124
|
+
* on the previous page, null for the first. Calls with no turns are omitted.
|
|
125
|
+
*/
|
|
126
|
+
callHistory: (before: string | null) => ({
|
|
127
|
+
url: "/api/thursday/call",
|
|
128
|
+
query: { before },
|
|
129
|
+
}),
|
|
130
|
+
|
|
131
|
+
/** boolean: whether the call may read a skill itself (Settings › Thursday) */
|
|
132
|
+
callSkills: "/api/thursday/skills",
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* POST, not a read; the one endpoint here SWR never touches. Body is
|
|
136
|
+
* {toolCallId, callId, name, input}; one JSON Result per call, abortable.
|
|
137
|
+
*/
|
|
138
|
+
toolCall: "/api/thursday/tool-call",
|
|
139
|
+
|
|
140
|
+
/** Providers this app can call and whether each key is set */
|
|
141
|
+
llmModel: "/api/llm-model",
|
|
142
|
+
/** Models reachable with the AI Gateway key; gateway only */
|
|
143
|
+
modelCatalog: "/api/llm-model/catalog",
|
|
144
|
+
} as const;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { asSchema } from "ai";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { loadTools } from "@/features/ai/load-tools";
|
|
4
|
+
import { modelErrorToString } from "@/features/ai/model";
|
|
5
|
+
import { serverRoute } from "@/lib/protocol/server-route";
|
|
6
|
+
import { publicError } from "@/lib/public-error";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Executes a tool call from the voice session. The page forwards what the model
|
|
10
|
+
* asked for; bots never come through here (they call `execute` directly).
|
|
11
|
+
* A route rather than a server action because actions run serially per client
|
|
12
|
+
* and one turn may call two tools at once. One POST, one JSON, no streaming;
|
|
13
|
+
* hanging up aborts the fetch and the signal reaches the tool.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const ToolCallSchema = z.object({
|
|
17
|
+
toolCallId: z.string().min(1),
|
|
18
|
+
/** The call this came from; `delegate` attaches the job to it. */
|
|
19
|
+
callId: z.string().nullish(),
|
|
20
|
+
name: z.string().min(1),
|
|
21
|
+
input: z.unknown().optional(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const isAsyncIterable = (value: unknown): value is AsyncIterable<unknown> =>
|
|
25
|
+
typeof value === "object" && value !== null && Symbol.asyncIterator in value;
|
|
26
|
+
|
|
27
|
+
/** For streaming tools the last chunk is the answer. */
|
|
28
|
+
async function drain(output: unknown): Promise<unknown> {
|
|
29
|
+
if (!isAsyncIterable(output)) return output;
|
|
30
|
+
let latest: unknown;
|
|
31
|
+
for await (const part of output) latest = part;
|
|
32
|
+
return latest;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const POST = serverRoute(async (request) => {
|
|
36
|
+
const { toolCallId, callId, name, input } = ToolCallSchema.parse(
|
|
37
|
+
await request.json(),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// The set the voice runtime may run, not whatever the body names.
|
|
41
|
+
const tools = await loadTools({ target: "thursday", callId });
|
|
42
|
+
const tool = tools[name];
|
|
43
|
+
if (!tool) publicError(`There is no tool called "${name}".`);
|
|
44
|
+
|
|
45
|
+
// end_call has no execute by design: the page owns the call and intercepts it.
|
|
46
|
+
if (!tool.execute) publicError(`"${name}" is handled by the page, not here.`);
|
|
47
|
+
|
|
48
|
+
const parsed = await asSchema(tool.inputSchema).validate?.(input ?? {});
|
|
49
|
+
if (parsed && !parsed.success) {
|
|
50
|
+
publicError(
|
|
51
|
+
`"${name}" got arguments that do not fit: ${parsed.error.message}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const output = await tool.execute(parsed?.value ?? input ?? {}, {
|
|
57
|
+
toolCallId,
|
|
58
|
+
messages: [],
|
|
59
|
+
context: undefined,
|
|
60
|
+
abortSignal: request.signal,
|
|
61
|
+
});
|
|
62
|
+
return { output: await drain(output) };
|
|
63
|
+
} catch (cause) {
|
|
64
|
+
// A tool failure is a line the model reads and recovers from, not a masked server error.
|
|
65
|
+
publicError(`"${name}" failed: ${modelErrorToString(cause)}`);
|
|
66
|
+
}
|
|
67
|
+
});
|
package/bin/thursday.mjs
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import { existsSync, symlinkSync } from "node:fs";
|
|
8
8
|
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { createServer } from "node:net";
|
|
9
10
|
import { homedir } from "node:os";
|
|
10
11
|
import { join, resolve } from "node:path";
|
|
11
12
|
import { ROOT, toolPath } from "./tools.mjs";
|
|
@@ -49,7 +50,39 @@ if (has("-v", "--version")) {
|
|
|
49
50
|
process.exit(0);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
/** Whether something already holds the port we are about to bind. */
|
|
54
|
+
const taken = (port) =>
|
|
55
|
+
new Promise((resolve) => {
|
|
56
|
+
const probe = createServer()
|
|
57
|
+
.once("error", () => resolve(true))
|
|
58
|
+
.once("listening", () => probe.close(() => resolve(false)))
|
|
59
|
+
.listen(port, "127.0.0.1");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 3000 is the most occupied port on a developer's machine, and the first thing
|
|
64
|
+
* `npx thursday-agent` does is bind it — so an untouched install used to end in
|
|
65
|
+
* an EADDRINUSE stack trace. A port nobody asked for is ours to move; a port
|
|
66
|
+
* that was asked for is not, and saying so is more use than moving it quietly.
|
|
67
|
+
*/
|
|
68
|
+
async function freePort(asked) {
|
|
69
|
+
const from = Number(asked ?? 3000);
|
|
70
|
+
if (!(await taken(from))) return from;
|
|
71
|
+
if (asked !== undefined) {
|
|
72
|
+
console.error(
|
|
73
|
+
`\n Port ${from} is already in use.\n Try another: thursday --port ${from + 1}\n`,
|
|
74
|
+
);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
for (let port = from + 1; port < from + 20; port++) {
|
|
78
|
+
if (!(await taken(port))) return port;
|
|
79
|
+
}
|
|
80
|
+
console.error(`\n Nothing free between ${from} and ${from + 20}.\n`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const asked = flag("port") ?? process.env.PORT;
|
|
85
|
+
const port = String(await freePort(asked));
|
|
53
86
|
const home = resolve(flag("home") || process.env.THURSDAY_HOME || DEFAULT_HOME);
|
|
54
87
|
const url = `http://localhost:${port}`;
|
|
55
88
|
|
|
@@ -122,7 +155,22 @@ if (!has("--no-open")) {
|
|
|
122
155
|
).unref();
|
|
123
156
|
}
|
|
124
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Ctrl+C reaches the whole process group, so the server normally hears it first
|
|
160
|
+
* and closes on its own. When it does not, it is holding a connection that does
|
|
161
|
+
* not end — the browser's event stream is one — and a second Ctrl+C is not a
|
|
162
|
+
* request to try again.
|
|
163
|
+
*/
|
|
164
|
+
let stopping = false;
|
|
125
165
|
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
126
|
-
process.on(signal, () =>
|
|
166
|
+
process.on(signal, () => {
|
|
167
|
+
if (stopping) {
|
|
168
|
+
child.kill("SIGKILL");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
stopping = true;
|
|
172
|
+
child.kill(signal);
|
|
173
|
+
setTimeout(() => child.kill("SIGKILL"), 4000).unref();
|
|
174
|
+
});
|
|
127
175
|
}
|
|
128
176
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import type { ComponentProps } from "react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Joins plain `<Button>`s into one control (outer corners only, shared 1px
|
|
9
|
+
* seams). Nested groups get a gap between them. For a selected value use
|
|
10
|
+
* `Segmented`.
|
|
11
|
+
*/
|
|
12
|
+
const buttonGroupVariants = cva(
|
|
13
|
+
"flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-1.5 [&>*]:relative [&>*]:focus-visible:z-10",
|
|
14
|
+
{
|
|
15
|
+
variants: {
|
|
16
|
+
orientation: {
|
|
17
|
+
horizontal:
|
|
18
|
+
"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
|
|
19
|
+
vertical:
|
|
20
|
+
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultVariants: {
|
|
24
|
+
orientation: "horizontal",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
function ButtonGroup({
|
|
30
|
+
className,
|
|
31
|
+
orientation = "horizontal",
|
|
32
|
+
...props
|
|
33
|
+
}: ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
data-slot="button-group"
|
|
37
|
+
data-orientation={orientation}
|
|
38
|
+
role="group"
|
|
39
|
+
className={cn(buttonGroupVariants({ orientation }), className)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { ButtonGroup, buttonGroupVariants };
|