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.
Files changed (361) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/routes-manifest.json +6 -0
  6. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  14. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next/server/app/_not-found.html +1 -1
  16. package/.next/server/app/_not-found.rsc +13 -13
  17. package/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
  18. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
  19. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  20. package/.next/server/app/api/artifact/route.js +1 -1
  21. package/.next/server/app/api/artifact/route.js.nft.json +1 -1
  22. package/.next/server/app/api/bot/notes/route/app-paths-manifest.json +3 -0
  23. package/.next/server/app/api/bot/notes/route/build-manifest.json +12 -0
  24. package/.next/server/app/api/bot/notes/route/server-reference-manifest.json +4 -0
  25. package/.next/server/app/api/bot/notes/route.js +14 -0
  26. package/.next/server/app/api/bot/notes/route.js.map +5 -0
  27. package/.next/server/app/api/bot/notes/route.js.nft.json +1 -0
  28. package/.next/server/app/api/bot/notes/route_client-reference-manifest.js +3 -0
  29. package/.next/server/app/api/bot/route.js +4 -4
  30. package/.next/server/app/api/bot/route.js.nft.json +1 -1
  31. package/.next/server/app/api/bot/task/[id]/route.js +6 -6
  32. package/.next/server/app/api/bot/task/[id]/route.js.nft.json +1 -1
  33. package/.next/server/app/api/bot/task/route.js +6 -6
  34. package/.next/server/app/api/bot/task/route.js.nft.json +1 -1
  35. package/.next/server/app/api/config/route.js +3 -3
  36. package/.next/server/app/api/config/route.js.nft.json +1 -1
  37. package/.next/server/app/api/events/route.js +1 -1
  38. package/.next/server/app/api/events/route.js.nft.json +1 -1
  39. package/.next/server/app/api/file/[...path]/route.js +1 -1
  40. package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
  41. package/.next/server/app/api/llm-model/catalog/route.js +6 -6
  42. package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
  43. package/.next/server/app/api/llm-model/route.js +3 -3
  44. package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
  45. package/.next/server/app/api/mcp/[name]/route.js +3 -3
  46. package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
  47. package/.next/server/app/api/mcp/oauth/callback/route.js +5 -5
  48. package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
  49. package/.next/server/app/api/mcp/route.js +3 -3
  50. package/.next/server/app/api/mcp/route.js.nft.json +1 -1
  51. package/.next/server/app/api/mcp/tools/route.js +3 -3
  52. package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
  53. package/.next/server/app/api/memory/route.js +7 -7
  54. package/.next/server/app/api/memory/route.js.nft.json +1 -1
  55. package/.next/server/app/api/memory/tidy/route.js +10 -11
  56. package/.next/server/app/api/memory/tidy/route.js.nft.json +1 -1
  57. package/.next/server/app/api/skills/[source]/[name]/route.js +1 -1
  58. package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
  59. package/.next/server/app/api/skills/route.js +1 -1
  60. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  61. package/.next/server/app/api/thursday/call/route.js +8 -8
  62. package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
  63. package/.next/server/app/api/thursday/skills/route.js +8 -8
  64. package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
  65. package/.next/server/app/api/thursday/tool-call/route.js +11 -12
  66. package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
  67. package/.next/server/app/api/workspace/route.js +1 -1
  68. package/.next/server/app/api/workspace/route.js.nft.json +1 -1
  69. package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
  70. package/.next/server/app/artifact/[...path]/page.js +3 -3
  71. package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
  72. package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
  73. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  74. package/.next/server/app/page/server-reference-manifest.json +63 -41
  75. package/.next/server/app/page.js +7 -7
  76. package/.next/server/app/page.js.nft.json +1 -1
  77. package/.next/server/app/page_client-reference-manifest.js +1 -1
  78. package/.next/server/app-paths-manifest.json +1 -0
  79. package/.next/server/chunks/0d-b_next_dist_server_app-render_after-task-async-storage_external_0-exn7p.js +3 -0
  80. package/.next/server/chunks/{[root-of-the-server]__0t_dqaj._.js → [root-of-the-server]__0-npkh_._.js} +1 -1
  81. package/.next/server/chunks/[root-of-the-server]__005var5._.js +3 -0
  82. package/.next/server/chunks/[root-of-the-server]__019dx9f._.js +2 -2
  83. package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +3 -0
  84. package/.next/server/chunks/{[root-of-the-server]__1mzp_el._.js → [root-of-the-server]__04_8spk._.js} +2 -2
  85. package/.next/server/chunks/[root-of-the-server]__06t77t4._.js +1 -1
  86. package/.next/server/chunks/[root-of-the-server]__0bed6hu._.js +4 -4
  87. package/.next/server/chunks/[root-of-the-server]__0cro2dz._.js +5 -0
  88. package/.next/server/chunks/[root-of-the-server]__0emr6go._.js +3 -0
  89. package/.next/server/chunks/[root-of-the-server]__0le4d-p._.js +1 -1
  90. package/.next/server/chunks/[root-of-the-server]__0m8ywyo._.js +5 -5
  91. package/.next/server/chunks/{[root-of-the-server]__113fqg5._.js → [root-of-the-server]__0p32jfz._.js} +3 -3
  92. package/.next/server/chunks/[root-of-the-server]__0phdgco._.js +4 -4
  93. package/.next/server/chunks/[root-of-the-server]__0wvgrsl._.js +1 -1
  94. package/.next/server/chunks/[root-of-the-server]__11j52t0._.js +5 -0
  95. package/.next/server/chunks/{[root-of-the-server]__0j_n5l3._.js → [root-of-the-server]__1692zrv._.js} +2 -2
  96. package/.next/server/chunks/[root-of-the-server]__16thabu._.js +1 -1
  97. package/.next/server/chunks/[root-of-the-server]__173nhwf._.js +3 -3
  98. package/.next/server/chunks/[root-of-the-server]__1bn73ih._.js +10 -0
  99. package/.next/server/chunks/[root-of-the-server]__1bqhuh9._.js +4 -4
  100. package/.next/server/chunks/[root-of-the-server]__1ccon1w._.js +10 -0
  101. package/.next/server/chunks/[root-of-the-server]__1cqizfq._.js +1 -1
  102. package/.next/server/chunks/{[root-of-the-server]__1u_qvc7._.js → [root-of-the-server]__1fboj2j._.js} +2 -2
  103. package/.next/server/chunks/{[root-of-the-server]__1dow0bz._.js → [root-of-the-server]__1oli73c._.js} +2 -2
  104. package/.next/server/chunks/[root-of-the-server]__1txedx6._.js +4 -4
  105. package/.next/server/chunks/[root-of-the-server]__1untqv8._.js +5 -0
  106. package/.next/server/chunks/_0-73of8._.js +55 -0
  107. package/.next/server/chunks/{_2050gwn._.js → _00_584r._.js} +66 -28
  108. package/.next/server/chunks/{_0y8is-s._.js → _03a0897._.js} +1 -1
  109. package/.next/server/chunks/_05h2cii._.js +1 -1
  110. package/.next/server/chunks/_06mxlgn._.js +1 -1
  111. package/.next/server/chunks/_0a351g7._.js +1 -1
  112. package/.next/server/chunks/{_0hin38_._.js → _0khihjk._.js} +5 -5
  113. package/.next/server/chunks/_0kjjmag._.js +1 -1
  114. package/.next/server/chunks/{_1t8fv7b._.js → _0n74zsf._.js} +5 -5
  115. package/.next/server/chunks/_0nn99o2._.js +3 -0
  116. package/.next/server/chunks/_0pppbb5._.js +10 -6
  117. package/.next/server/chunks/{_1icgbi8._.js → _0rnf-6n._.js} +5 -5
  118. package/.next/server/chunks/_0u10wmw._.js +55 -0
  119. package/.next/server/chunks/{_0kl37zl._.js → _0vnutv9._.js} +1 -1
  120. package/.next/server/chunks/_0wp4le3._.js +1 -1
  121. package/.next/server/chunks/_0y5fg-x._.js +1 -1
  122. package/.next/server/chunks/_106co2o._.js +3 -0
  123. package/.next/server/chunks/_12bbv5i._.js +10 -6
  124. package/.next/server/chunks/_15hq89o._.js +1 -1
  125. package/.next/server/chunks/_15q1pig._.js +1 -1
  126. package/.next/server/chunks/_18-1wcc._.js +55 -0
  127. package/.next/server/chunks/_19wg5yn._.js +138 -0
  128. package/.next/server/chunks/_1_5mnq4._.js +18 -0
  129. package/.next/server/chunks/_1bt95p9._.js +55 -0
  130. package/.next/server/chunks/_1cbk7ms._.js +9 -9
  131. package/.next/server/chunks/_1drgfx4._.js +18 -0
  132. package/.next/server/chunks/_1drv2et._.js +10 -6
  133. package/.next/server/chunks/{_10qam6o._.js → _1e48czp._.js} +5 -5
  134. package/.next/server/chunks/{_1skgy7b._.js → _1kg70wc._.js} +5 -5
  135. package/.next/server/chunks/_1php4eq._.js +1 -1
  136. package/.next/server/chunks/_1rysr-r._.js +10 -0
  137. package/.next/server/chunks/_1u3rwbr._.js +3 -0
  138. package/.next/server/chunks/_1x7p319._.js +1 -1
  139. package/.next/server/chunks/_1yuv4vp._.js +1 -1
  140. package/.next/server/chunks/_next-internal_server_app_api_bot_notes_route_actions_0qcsyu7.js +3 -0
  141. package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
  142. package/.next/server/chunks/features_bot_task_query_ts_11xuzl3._.js +1 -1
  143. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0228f43._.js → node_modules_consola_dist_chunks_prompt_mjs_02s3hjt._.js} +1 -1
  144. 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
  145. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0otw8ef._.js → node_modules_consola_dist_chunks_prompt_mjs_096ais_._.js} +1 -1
  146. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0tf290y._.js → node_modules_consola_dist_chunks_prompt_mjs_0a1rd09._.js} +1 -1
  147. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0gzvgfv._.js +3 -0
  148. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0j61-ma._.js +3 -0
  149. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0tvkfgh._.js +3 -0
  150. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_11zjo-1._.js +3 -0
  151. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1pycqsq._.js +3 -0
  152. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1tpvamf._.js +3 -0
  153. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1y9-wcr._.js +3 -0
  154. package/.next/server/chunks/ssr/[externals]__0dizgek._.js +3 -0
  155. package/.next/server/chunks/ssr/[root-of-the-server]__0f6s48x._.js +1 -1
  156. package/.next/server/chunks/ssr/[root-of-the-server]__0m1b5ky._.js +3 -0
  157. package/.next/server/chunks/ssr/{[root-of-the-server]__0dfwvr4._.js → [root-of-the-server]__0un-erv._.js} +2 -2
  158. package/.next/server/chunks/ssr/[root-of-the-server]__1-mnhyd._.js +5 -3
  159. package/.next/server/chunks/ssr/[root-of-the-server]__1bww500._.js +3 -0
  160. package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +1 -1
  161. package/.next/server/chunks/ssr/[root-of-the-server]__1qfqvmg._.js +3 -0
  162. package/.next/server/chunks/ssr/[root-of-the-server]__1vcdast._.js +3 -0
  163. package/.next/server/chunks/ssr/[root-of-the-server]__1xwnu-h._.js +3 -0
  164. package/.next/server/chunks/ssr/_03webdr._.js +3 -0
  165. package/.next/server/chunks/ssr/{_0gd4vaa._.js → _040sk32._.js} +71 -29
  166. package/.next/server/chunks/ssr/_077-g6s._.js +1 -1
  167. package/.next/server/chunks/ssr/{_1r2_qk-._.js → _09ja_w1._.js} +2 -2
  168. package/.next/server/chunks/ssr/{_1dnxp2k._.js → _0f_b_9s._.js} +2 -2
  169. package/.next/server/chunks/ssr/_0h7zdni._.js +1 -1
  170. package/.next/server/chunks/ssr/_0lu1n_x._.js +3 -0
  171. package/.next/server/chunks/ssr/_0sf1isi._.js +1 -1
  172. package/.next/server/chunks/ssr/_0su6_08._.js +3 -0
  173. package/.next/server/chunks/ssr/_0w5bgak._.js +3 -0
  174. package/.next/server/chunks/ssr/{_1hkvsg0._.js → _0x_nu5e._.js} +2 -2
  175. package/.next/server/chunks/ssr/_1-teee5._.js +3 -3
  176. package/.next/server/chunks/ssr/_13punpm._.js +1 -1
  177. package/.next/server/chunks/ssr/{_0ay6b01._.js → _1ciyoo3._.js} +2 -2
  178. package/.next/server/chunks/ssr/_1e-dgz0._.js +1 -1
  179. package/.next/server/chunks/ssr/_1fq7zgb._.js +1 -1
  180. package/.next/server/chunks/ssr/{_0cvjbuz._.js → _1g6l4uj._.js} +2 -2
  181. package/.next/server/chunks/ssr/{_1b3yex3._.js → _1lip4th._.js} +2 -2
  182. package/.next/server/chunks/ssr/_1m3bsfr._.js +3 -0
  183. package/.next/server/chunks/ssr/_1mznrxk._.js +1 -1
  184. package/.next/server/chunks/ssr/_1ruzehw._.js +3 -0
  185. package/.next/server/chunks/ssr/_1vyd1ji._.js +3 -0
  186. package/.next/server/chunks/ssr/features_bot_components_bot-setting_tsx_01ajft6._.js +2 -2
  187. package/.next/server/chunks/ssr/features_config_components_14jqgy4._.js +1 -1
  188. package/.next/server/chunks/ssr/features_config_components_config-setting_tsx_0xfwqdg._.js +1 -1
  189. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
  190. package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +1 -1
  191. package/.next/server/chunks/ssr/lib_realtime_12wg_dd._.js +27 -0
  192. package/.next/server/chunks/ssr/lib_realtime_15h_v1v._.js +27 -0
  193. package/.next/server/chunks/ssr/{node_modules_0i37h34._.js → node_modules_004qlr4._.js} +1 -1
  194. package/.next/server/chunks/ssr/{node_modules_0nh2m35._.js → node_modules_01qnv-a._.js} +1 -1
  195. package/.next/server/chunks/ssr/{node_modules_1kj6swe._.js → node_modules_04n7mjs._.js} +1 -1
  196. package/.next/server/chunks/ssr/{node_modules_0eqdzva._.js → node_modules_08d8bke._.js} +1 -1
  197. package/.next/server/chunks/ssr/{node_modules_0nq_p-o._.js → node_modules_09xhur7._.js} +1 -1
  198. package/.next/server/chunks/ssr/{node_modules_1854ebs._.js → node_modules_0_n241r._.js} +1 -1
  199. package/.next/server/chunks/ssr/{node_modules_00riwzq._.js → node_modules_0iz1rzd._.js} +1 -1
  200. package/.next/server/chunks/ssr/{node_modules_1wsx1pg._.js → node_modules_0kl7-tn._.js} +1 -1
  201. package/.next/server/chunks/ssr/{node_modules_17ls31t._.js → node_modules_0kxz4gt._.js} +1 -1
  202. package/.next/server/chunks/ssr/{node_modules_1fsvm8f._.js → node_modules_0lrjk5t._.js} +1 -1
  203. package/.next/server/chunks/ssr/{node_modules_1euv75v._.js → node_modules_0m93zeg._.js} +1 -1
  204. package/.next/server/chunks/ssr/{node_modules_1_w_b81._.js → node_modules_0n1kj4p._.js} +1 -1
  205. package/.next/server/chunks/ssr/{node_modules_1bjm3ch._.js → node_modules_0ndvxub._.js} +1 -1
  206. package/.next/server/chunks/ssr/{node_modules_03f-6up._.js → node_modules_13r44jq._.js} +1 -1
  207. package/.next/server/chunks/ssr/{node_modules_07_oxjr._.js → node_modules_17lrt3y._.js} +1 -1
  208. package/.next/server/chunks/ssr/{node_modules_1yxkq1s._.js → node_modules_18_dk7_._.js} +1 -1
  209. package/.next/server/chunks/ssr/{node_modules_012m-mo._.js → node_modules_19dp7ko._.js} +1 -1
  210. package/.next/server/chunks/ssr/{node_modules_0egn4iq._.js → node_modules_1dwguw_._.js} +1 -1
  211. package/.next/server/chunks/ssr/{node_modules_1y1m-q4._.js → node_modules_1hd-5tv._.js} +1 -1
  212. package/.next/server/chunks/ssr/{node_modules_0xe_k12._.js → node_modules_1hn8z1f._.js} +1 -1
  213. package/.next/server/chunks/ssr/{node_modules_0sbobpz._.js → node_modules_1n5ygqe._.js} +1 -1
  214. package/.next/server/chunks/ssr/{node_modules_14uxpfb._.js → node_modules_1q0p85z._.js} +1 -1
  215. package/.next/server/chunks/ssr/{node_modules_01jj5oy._.js → node_modules_1toafc-._.js} +1 -1
  216. package/.next/server/chunks/ssr/{node_modules_0ewynyi._.js → node_modules_1xyd7ov._.js} +1 -1
  217. package/.next/server/chunks/ssr/{node_modules_17c71fz._.js → node_modules_1zu988w._.js} +1 -1
  218. package/.next/server/chunks/ssr/{node_modules_1mnmhse._.js → node_modules_20yebtj._.js} +1 -1
  219. 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
  220. 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
  221. package/.next/server/chunks/ssr/node_modules_consola_dist_chunks_prompt_mjs_05cwqv2._.js +47 -0
  222. package/.next/server/middleware-build-manifest.js +3 -3
  223. package/.next/server/pages/404.html +1 -1
  224. package/.next/server/pages/500.html +1 -1
  225. package/.next/server/server-reference-manifest.js +1 -1
  226. package/.next/server/server-reference-manifest.json +64 -42
  227. package/.next/static/chunks/05b6nx_o3_f_n.js +1 -0
  228. package/.next/static/chunks/{1rja48de93vvx.js → 07kl352c-lvcz.js} +1 -1
  229. package/.next/static/chunks/0dmns-gi0raa4.js +1 -0
  230. package/.next/static/chunks/0oc4_xoia77dg.css +1 -0
  231. package/.next/static/chunks/0pheo8m3a-rzi.js +1 -0
  232. package/.next/static/chunks/{3yan6-5cvpk2o.js → 18vhba44l1fc9.js} +1 -1
  233. package/.next/static/chunks/19i8h2t-5evjx.js +1 -0
  234. package/.next/static/chunks/{0h7pjyewk7ky-.js → 1c5fcj473025q.js} +1 -1
  235. package/.next/static/chunks/1pi_hbmjw-f-z.js +1 -0
  236. package/.next/static/chunks/{0tydnn2kybibk.js → 1twt6k09vg4np.js} +1 -1
  237. package/.next/static/chunks/{2kl3n2d-4-7i5.js → 28gotuisldmom.js} +1 -1
  238. package/.next/static/chunks/{34eu8t79ko6lm.js → 2998p0y7k1c8z.js} +1 -1
  239. package/.next/static/chunks/2l233xfycjpd8.js +1 -0
  240. package/.next/static/chunks/2p5n7pa3c40t-.js +2 -0
  241. package/.next/static/chunks/2ru1q132af7h7.js +1 -0
  242. package/.next/static/chunks/{2ka9uwo_evj7a.js → 2t8wqycwg3y04.js} +2 -2
  243. package/.next/static/chunks/32z_ypgtchw48.js +1 -0
  244. package/.next/static/chunks/{261871a8ot1pi.js → 33pyjb3xd1iez.js} +1 -1
  245. package/.next/static/chunks/3786h5vayt80-.js +28 -0
  246. package/.next/static/chunks/3_h9txt3tq8t8.js +28 -0
  247. package/.next/static/chunks/{2qci2z603es_m.js → 3ard_noqapnw1.js} +1 -1
  248. package/.next/static/chunks/{0yn2yol46cj77.js → 3omud5xunrk1f.js} +1 -1
  249. package/.next/static/chunks/42dnrkbph9e-1.js +1 -0
  250. package/README.md +24 -1
  251. package/app/api/events/app-event.client.ts +24 -0
  252. package/app/api/events/app-event.server.ts +45 -0
  253. package/app/api/events/app-event.ts +44 -0
  254. package/app/api/llm-model/catalog/route.ts +12 -0
  255. package/app/api/llm-model/route.ts +21 -0
  256. package/app/api/query-key.ts +144 -0
  257. package/app/api/thursday/tool-call/route.ts +67 -0
  258. package/bin/thursday.mjs +50 -2
  259. package/components/ui/button-group.tsx +45 -0
  260. package/components/ui/custom-icon.tsx +259 -0
  261. package/components/ui/dropdown-menu.tsx +271 -0
  262. package/components/ui/folded-text.tsx +68 -0
  263. package/components/ui/input-group.tsx +157 -0
  264. package/components/ui/json-view.tsx +240 -0
  265. package/components/ui/shiny-text.tsx +141 -0
  266. package/components/ui/text-type.tsx +213 -0
  267. package/components/ui/theme-sync.tsx +9 -0
  268. package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260909060813_optimal_sabretooth}/migration.sql +7 -0
  269. package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260909060813_optimal_sabretooth}/snapshot.json +54 -1
  270. package/database/migrations/20260909063821_bitter_tenebrous/migration.sql +1 -0
  271. package/database/migrations/20260909063821_bitter_tenebrous/snapshot.json +1285 -0
  272. package/docs/how-it-works.md +59 -0
  273. package/docs/images/keeps-going.png +0 -0
  274. package/docs/images/task-thread.png +0 -0
  275. package/docs/images/two-minds.png +0 -0
  276. package/features/ai/load-tools.ts +55 -5
  277. package/features/ai/prompts/bot.prompt.ts +121 -26
  278. package/features/ai/prompts/memory-tidy.prompt.ts +7 -1
  279. package/features/ai/prompts/prompt-helper.ts +31 -5
  280. package/features/ai/prompts/thursday.prompt.ts +2 -0
  281. package/features/ai/tools/bot.tool.ts +37 -18
  282. package/features/ai/tools/mcp.tool.ts +5 -5
  283. package/features/ai/tools/memory.tool.ts +17 -10
  284. package/features/ai/tools/studio.tool.ts +5 -5
  285. package/features/ai/tools/tool-name.ts +2 -0
  286. package/features/ai/tools/workspace.tool.ts +33 -5
  287. package/features/bot/bot.action.ts +31 -4
  288. package/features/bot/bot.notes.ts +120 -0
  289. package/features/bot/bot.query.ts +56 -1
  290. package/features/bot/bot.run.ts +43 -3
  291. package/features/bot/bot.runner.ts +94 -20
  292. package/features/bot/bot.schema.ts +17 -0
  293. package/features/bot/components/bot-room.tsx +405 -306
  294. package/features/bot/components/bot-setting.tsx +123 -20
  295. package/features/bot/task.query.ts +18 -5
  296. package/features/memory/components/memory-setting.tsx +11 -2
  297. package/features/memory/memory.query.ts +9 -2
  298. package/features/memory/memory.schema.ts +30 -0
  299. package/features/skills/components/skills-setting.tsx +9 -1
  300. package/features/workspace/workspace.ts +136 -3
  301. package/hooks/use-draft.ts +37 -0
  302. package/hooks/use-hotkey.ts +105 -0
  303. package/hooks/use-object-state.ts +51 -0
  304. package/hooks/use-on-visible.ts +72 -0
  305. package/hooks/use-theme.ts +106 -0
  306. package/hooks/use-wake-word.ts +262 -0
  307. package/lib/realtime/realtime.driver.ts +34 -1
  308. package/lib/realtime/realtime.protocol.ts +6 -0
  309. package/lib/sandbox.ts +9 -1
  310. package/lib/tokens.ts +19 -0
  311. package/package.json +1 -1
  312. package/.next/server/chunks/[root-of-the-server]__02kt8yn._.js +0 -10
  313. package/.next/server/chunks/[root-of-the-server]__0h59di-._.js +0 -5
  314. package/.next/server/chunks/[root-of-the-server]__0qjqtwo._.js +0 -5
  315. package/.next/server/chunks/[root-of-the-server]__0yk6s3w._.js +0 -5
  316. package/.next/server/chunks/[root-of-the-server]__0z3gelv._.js +0 -3
  317. package/.next/server/chunks/[root-of-the-server]__11w_7_3._.js +0 -3
  318. package/.next/server/chunks/[root-of-the-server]__1_0q_9w._.js +0 -3
  319. package/.next/server/chunks/[root-of-the-server]__1gmn1cs._.js +0 -10
  320. package/.next/server/chunks/_0fzqt2y._.js +0 -100
  321. package/.next/server/chunks/_0jgm3b8._.js +0 -55
  322. package/.next/server/chunks/_0ks6_sg._.js +0 -55
  323. package/.next/server/chunks/_0tzfesa._.js +0 -3
  324. package/.next/server/chunks/_0zmestz._.js +0 -55
  325. package/.next/server/chunks/_12nr4xc._.js +0 -3
  326. package/.next/server/chunks/_1hzrsvg._.js +0 -55
  327. package/.next/server/chunks/_1uj4_io._.js +0 -10
  328. package/.next/server/chunks/_1yldgo4._.js +0 -55
  329. package/.next/server/chunks/features_0scmr4i._.js +0 -3
  330. package/.next/server/chunks/features_bot_bot_query_ts_12mgkl2._.js +0 -3
  331. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0ykklj0._.js +0 -3
  332. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1ryg3wh._.js +0 -3
  333. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1vwbnq_._.js +0 -3
  334. package/.next/server/chunks/ssr/[root-of-the-server]__0b-0i32._.js +0 -3
  335. package/.next/server/chunks/ssr/[root-of-the-server]__1e-f3gy._.js +0 -3
  336. package/.next/server/chunks/ssr/[root-of-the-server]__1minz8_._.js +0 -3
  337. package/.next/server/chunks/ssr/[root-of-the-server]__1ncajna._.js +0 -3
  338. package/.next/server/chunks/ssr/_124q1la._.js +0 -3
  339. package/.next/server/chunks/ssr/_19eoufz._.js +0 -3
  340. package/.next/server/chunks/ssr/_1aca4j8._.js +0 -3
  341. package/.next/server/chunks/ssr/_1cb_5sv._.js +0 -3
  342. package/.next/server/chunks/ssr/_1m5popy._.js +0 -3
  343. package/.next/server/chunks/ssr/lib_realtime_0aetuyp._.js +0 -24
  344. package/.next/server/chunks/ssr/lib_realtime_18k_aj-._.js +0 -24
  345. package/.next/server/chunks/ssr/node_modules_zod_v4_classic_errors_05xv7vp.js +0 -3
  346. package/.next/static/chunks/0_6qjj4f3t3j-.js +0 -1
  347. package/.next/static/chunks/0g2n0xskvyk04.js +0 -2
  348. package/.next/static/chunks/14s7e14xltx47.js +0 -1
  349. package/.next/static/chunks/1lpdcqos7mkek.js +0 -1
  350. package/.next/static/chunks/1u5d-pvhwir9g.js +0 -1
  351. package/.next/static/chunks/2cgzsjzheo2m2.js +0 -1
  352. package/.next/static/chunks/2cwjgkt_hoyhj.js +0 -22
  353. package/.next/static/chunks/2rbf1fpd8n80-.js +0 -22
  354. package/.next/static/chunks/2ukj8k69y89um.js +0 -1
  355. package/.next/static/chunks/2v_vqhmdl8de_.js +0 -1
  356. package/.next/static/chunks/35uo6rtl_otbd.js +0 -1
  357. package/.next/static/chunks/388-t2v72jh39.css +0 -1
  358. package/.next/static/chunks/3w7-y53xzl2ir.js +0 -1
  359. /package/.next/static/{G-9_HnysmzY_Bx9GpDYAO → YR4NWcDDuzwLbTTU7U7Vr}/_buildManifest.js +0 -0
  360. /package/.next/static/{G-9_HnysmzY_Bx9GpDYAO → YR4NWcDDuzwLbTTU7U7Vr}/_clientMiddlewareManifest.js +0 -0
  361. /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`. One OpenAI or xAI key opens the call — a voice key is also a text key, so one is enough to run everything. Data lives in `~/.thursday`.
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
- const port = flag("port") || process.env.PORT || "3000";
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, () => child.kill(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 };