thursday-agent 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) 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/{_0lc_7c8._.js → _09ja_w1._.js} +2 -2
  168. package/.next/server/chunks/ssr/{_1dcxzqe._.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/{_0nso8tp._.js → _0w5bgak._.js} +2 -2
  174. package/.next/server/chunks/ssr/{_19_o9d-._.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/{_0q_2f2x._.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_108wd2o._.js → node_modules_004qlr4._.js} +1 -1
  194. package/.next/server/chunks/ssr/{node_modules_1ub40gk._.js → node_modules_01qnv-a._.js} +1 -1
  195. package/.next/server/chunks/ssr/{node_modules_1y8_3vg._.js → node_modules_04n7mjs._.js} +1 -1
  196. package/.next/server/chunks/ssr/{node_modules_0_j7-g4._.js → node_modules_08d8bke._.js} +1 -1
  197. package/.next/server/chunks/ssr/{node_modules_0y2-av4._.js → node_modules_09xhur7._.js} +1 -1
  198. package/.next/server/chunks/ssr/{node_modules_043-236._.js → node_modules_0_n241r._.js} +1 -1
  199. package/.next/server/chunks/ssr/{node_modules_0ls96je._.js → node_modules_0iz1rzd._.js} +1 -1
  200. package/.next/server/chunks/ssr/{node_modules_0xwx4u-._.js → node_modules_0kl7-tn._.js} +1 -1
  201. package/.next/server/chunks/ssr/{node_modules_1jatz7w._.js → node_modules_0kxz4gt._.js} +1 -1
  202. package/.next/server/chunks/ssr/{node_modules_1t0y5jt._.js → node_modules_0lrjk5t._.js} +1 -1
  203. package/.next/server/chunks/ssr/{node_modules_13f2md6._.js → node_modules_0m93zeg._.js} +1 -1
  204. package/.next/server/chunks/ssr/{node_modules_1nhw9qg._.js → node_modules_0n1kj4p._.js} +1 -1
  205. package/.next/server/chunks/ssr/{node_modules_1k29j1j._.js → node_modules_0ndvxub._.js} +1 -1
  206. package/.next/server/chunks/ssr/{node_modules_16i4o_3._.js → node_modules_13r44jq._.js} +1 -1
  207. package/.next/server/chunks/ssr/{node_modules_0fbw5-u._.js → node_modules_17lrt3y._.js} +1 -1
  208. package/.next/server/chunks/ssr/{node_modules_1d-is1o._.js → node_modules_18_dk7_._.js} +1 -1
  209. package/.next/server/chunks/ssr/{node_modules_1hn_sd5._.js → node_modules_19dp7ko._.js} +1 -1
  210. package/.next/server/chunks/ssr/{node_modules_20ynonm._.js → node_modules_1dwguw_._.js} +1 -1
  211. package/.next/server/chunks/ssr/{node_modules_00d6675._.js → node_modules_1hd-5tv._.js} +1 -1
  212. package/.next/server/chunks/ssr/{node_modules_1ee8gav._.js → node_modules_1hn8z1f._.js} +1 -1
  213. package/.next/server/chunks/ssr/{node_modules_0bue6gy._.js → node_modules_1n5ygqe._.js} +1 -1
  214. package/.next/server/chunks/ssr/{node_modules_1_588_4._.js → node_modules_1q0p85z._.js} +1 -1
  215. package/.next/server/chunks/ssr/{node_modules_0ipn1ho._.js → node_modules_1toafc-._.js} +1 -1
  216. package/.next/server/chunks/ssr/{node_modules_0_fvxnt._.js → node_modules_1xyd7ov._.js} +1 -1
  217. package/.next/server/chunks/ssr/{node_modules_1v0077l._.js → node_modules_1zu988w._.js} +1 -1
  218. package/.next/server/chunks/ssr/{node_modules_06kvvuo._.js → node_modules_20yebtj._.js} +1 -1
  219. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_092s8nx._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0m9a4y0._.js} +1 -1
  220. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1nvexgu._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_13w_wy9._.js} +1 -1
  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/{3aquirz5h71da.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/{2ihkf6l2jowo9.js → 18vhba44l1fc9.js} +3 -3
  233. package/.next/static/chunks/19i8h2t-5evjx.js +1 -0
  234. package/.next/static/chunks/{1gy3bbv7usnjo.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/{21rk8w7bda-su.js → 28gotuisldmom.js} +1 -1
  238. package/.next/static/chunks/{3xxa2j56-f3nm.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/{2fq_86jvq9ymq.js → 2t8wqycwg3y04.js} +2 -2
  243. package/.next/static/chunks/32z_ypgtchw48.js +1 -0
  244. package/.next/static/chunks/{247d-r95j8xs0.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/{1khakdu_iuedc.js → 3ard_noqapnw1.js} +1 -1
  248. package/.next/static/chunks/{1fr5m75pwtyic.js → 3omud5xunrk1f.js} +1 -1
  249. package/.next/static/chunks/42dnrkbph9e-1.js +1 -0
  250. package/app/api/events/app-event.client.ts +24 -0
  251. package/app/api/events/app-event.server.ts +45 -0
  252. package/app/api/events/app-event.ts +44 -0
  253. package/app/api/llm-model/catalog/route.ts +12 -0
  254. package/app/api/llm-model/route.ts +21 -0
  255. package/app/api/query-key.ts +144 -0
  256. package/app/api/thursday/tool-call/route.ts +67 -0
  257. package/components/ui/button-group.tsx +45 -0
  258. package/components/ui/custom-icon.tsx +259 -0
  259. package/components/ui/dropdown-menu.tsx +271 -0
  260. package/components/ui/folded-text.tsx +68 -0
  261. package/components/ui/input-group.tsx +157 -0
  262. package/components/ui/json-view.tsx +240 -0
  263. package/components/ui/shiny-text.tsx +141 -0
  264. package/components/ui/text-type.tsx +213 -0
  265. package/components/ui/theme-sync.tsx +9 -0
  266. package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260909060813_optimal_sabretooth}/migration.sql +7 -0
  267. package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260909060813_optimal_sabretooth}/snapshot.json +54 -1
  268. package/database/migrations/20260909063821_bitter_tenebrous/migration.sql +1 -0
  269. package/database/migrations/20260909063821_bitter_tenebrous/snapshot.json +1285 -0
  270. package/docs/how-it-works.md +59 -0
  271. package/docs/images/keeps-going.png +0 -0
  272. package/docs/images/task-thread.png +0 -0
  273. package/docs/images/two-minds.png +0 -0
  274. package/features/ai/load-tools.ts +55 -5
  275. package/features/ai/prompts/bot.prompt.ts +121 -26
  276. package/features/ai/prompts/memory-tidy.prompt.ts +7 -1
  277. package/features/ai/prompts/prompt-helper.ts +31 -5
  278. package/features/ai/prompts/thursday.prompt.ts +2 -0
  279. package/features/ai/tools/bot.tool.ts +37 -18
  280. package/features/ai/tools/mcp.tool.ts +5 -5
  281. package/features/ai/tools/memory.tool.ts +17 -10
  282. package/features/ai/tools/studio.tool.ts +5 -5
  283. package/features/ai/tools/tool-name.ts +2 -0
  284. package/features/ai/tools/workspace.tool.ts +33 -5
  285. package/features/bot/bot.action.ts +31 -4
  286. package/features/bot/bot.notes.ts +120 -0
  287. package/features/bot/bot.query.ts +56 -1
  288. package/features/bot/bot.run.ts +43 -3
  289. package/features/bot/bot.runner.ts +94 -20
  290. package/features/bot/bot.schema.ts +17 -0
  291. package/features/bot/components/bot-room.tsx +405 -306
  292. package/features/bot/components/bot-setting.tsx +123 -20
  293. package/features/bot/task.query.ts +18 -5
  294. package/features/memory/components/memory-setting.tsx +11 -2
  295. package/features/memory/memory.query.ts +9 -2
  296. package/features/memory/memory.schema.ts +30 -0
  297. package/features/skills/components/skills-setting.tsx +9 -1
  298. package/features/workspace/workspace.ts +136 -3
  299. package/hooks/use-draft.ts +37 -0
  300. package/hooks/use-hotkey.ts +105 -0
  301. package/hooks/use-object-state.ts +51 -0
  302. package/hooks/use-on-visible.ts +72 -0
  303. package/hooks/use-theme.ts +106 -0
  304. package/hooks/use-wake-word.ts +262 -0
  305. package/lib/realtime/realtime.driver.ts +34 -1
  306. package/lib/realtime/realtime.protocol.ts +6 -0
  307. package/lib/sandbox.ts +9 -1
  308. package/lib/tokens.ts +19 -0
  309. package/package.json +1 -1
  310. package/.next/server/chunks/[root-of-the-server]__02kt8yn._.js +0 -10
  311. package/.next/server/chunks/[root-of-the-server]__0h59di-._.js +0 -5
  312. package/.next/server/chunks/[root-of-the-server]__0qjqtwo._.js +0 -5
  313. package/.next/server/chunks/[root-of-the-server]__0yk6s3w._.js +0 -5
  314. package/.next/server/chunks/[root-of-the-server]__0z3gelv._.js +0 -3
  315. package/.next/server/chunks/[root-of-the-server]__11w_7_3._.js +0 -3
  316. package/.next/server/chunks/[root-of-the-server]__1_0q_9w._.js +0 -3
  317. package/.next/server/chunks/[root-of-the-server]__1gmn1cs._.js +0 -10
  318. package/.next/server/chunks/_0fzqt2y._.js +0 -100
  319. package/.next/server/chunks/_0jgm3b8._.js +0 -55
  320. package/.next/server/chunks/_0ks6_sg._.js +0 -55
  321. package/.next/server/chunks/_0tzfesa._.js +0 -3
  322. package/.next/server/chunks/_0zmestz._.js +0 -55
  323. package/.next/server/chunks/_12nr4xc._.js +0 -3
  324. package/.next/server/chunks/_1hzrsvg._.js +0 -55
  325. package/.next/server/chunks/_1uj4_io._.js +0 -10
  326. package/.next/server/chunks/_1yldgo4._.js +0 -55
  327. package/.next/server/chunks/features_0scmr4i._.js +0 -3
  328. package/.next/server/chunks/features_bot_bot_query_ts_12mgkl2._.js +0 -3
  329. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0ykklj0._.js +0 -3
  330. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1ryg3wh._.js +0 -3
  331. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1vwbnq_._.js +0 -3
  332. package/.next/server/chunks/ssr/[root-of-the-server]__01id-v3._.js +0 -3
  333. package/.next/server/chunks/ssr/[root-of-the-server]__0b-0i32._.js +0 -3
  334. package/.next/server/chunks/ssr/[root-of-the-server]__185grso._.js +0 -3
  335. package/.next/server/chunks/ssr/[root-of-the-server]__1minz8_._.js +0 -3
  336. package/.next/server/chunks/ssr/_0j4v8of._.js +0 -3
  337. package/.next/server/chunks/ssr/_0l81bzy._.js +0 -3
  338. package/.next/server/chunks/ssr/_10uvw3l._.js +0 -3
  339. package/.next/server/chunks/ssr/_1jbv7m1._.js +0 -3
  340. package/.next/server/chunks/ssr/lib_realtime_0aetuyp._.js +0 -24
  341. package/.next/server/chunks/ssr/lib_realtime_18k_aj-._.js +0 -24
  342. package/.next/server/chunks/ssr/node_modules_zod_v4_classic_errors_05xv7vp.js +0 -3
  343. package/.next/static/chunks/0yysv1gzin17t.js +0 -1
  344. package/.next/static/chunks/1keib_gt066gc.js +0 -1
  345. package/.next/static/chunks/28g42tw8tsvhd.js +0 -1
  346. package/.next/static/chunks/2cwjgkt_hoyhj.js +0 -22
  347. package/.next/static/chunks/2rbf1fpd8n80-.js +0 -22
  348. package/.next/static/chunks/2ukj8k69y89um.js +0 -1
  349. package/.next/static/chunks/2wngeetfnzxkp.js +0 -2
  350. package/.next/static/chunks/388-t2v72jh39.css +0 -1
  351. package/.next/static/chunks/3fqvc0cg-atq_.js +0 -1
  352. package/.next/static/chunks/3p4egvfjxpatb.js +0 -1
  353. package/.next/static/chunks/3qe1w1ei-huhl.js +0 -1
  354. package/.next/static/chunks/3y40riycdip-i.js +0 -1
  355. package/.next/static/chunks/45a6zbtkelph9.js +0 -1
  356. /package/.next/static/{X34BUd9bfdH27ripuwwBm → YR4NWcDDuzwLbTTU7U7Vr}/_buildManifest.js +0 -0
  357. /package/.next/static/{X34BUd9bfdH27ripuwwBm → YR4NWcDDuzwLbTTU7U7Vr}/_clientMiddlewareManifest.js +0 -0
  358. /package/.next/static/{X34BUd9bfdH27ripuwwBm → YR4NWcDDuzwLbTTU7U7Vr}/_ssgManifest.js +0 -0
@@ -1,6 +1,14 @@
1
1
  "use client";
2
2
 
3
- import { Check, History, Plus, Trash2, Wrench, X } from "lucide-react";
3
+ import {
4
+ Check,
5
+ History,
6
+ PencilLine,
7
+ Plus,
8
+ Trash2,
9
+ Wrench,
10
+ X,
11
+ } from "lucide-react";
4
12
  import { type ReactNode, useState } from "react";
5
13
  import { queryKey } from "@/app/api/query-key";
6
14
  import { Button } from "@/components/ui/button";
@@ -16,13 +24,17 @@ import { Input } from "@/components/ui/input";
16
24
  import { notify } from "@/components/ui/notify";
17
25
  import ShinyText from "@/components/ui/shiny-text";
18
26
  import { Swatch } from "@/components/ui/swatch";
27
+ import { Switch } from "@/components/ui/switch";
19
28
  import { Textarea } from "@/components/ui/textarea";
29
+ import { BOT_NOTES, PROMPT_CROWDED } from "@/config";
20
30
  import { ModelPicker } from "@/features/ai/components/model-picker";
21
31
  import type { TextModelProviderId } from "@/features/ai/model.schema";
22
32
  import {
33
+ clearBotNoteAction,
23
34
  createBotAction,
24
35
  createSeedBotsAction,
25
36
  deleteBotAction,
37
+ setBotNotesOnAction,
26
38
  updateBotAction,
27
39
  } from "@/features/bot/bot.action";
28
40
  import {
@@ -38,6 +50,7 @@ import { BOT_SEEDS, type BotSeed } from "@/features/bot/bot.seed";
38
50
  import { BotMark } from "@/features/bot/components/bot-mark";
39
51
  import {
40
52
  SettingError,
53
+ SettingNote,
41
54
  SettingPanes,
42
55
  SettingPanesSkeleton,
43
56
  SettingRailNote,
@@ -111,6 +124,13 @@ export function BotSetting() {
111
124
  onPick={() => setPicked(SEEDS)}
112
125
  />
113
126
  )}
127
+
128
+ {bots.length > PROMPT_CROWDED.bots && (
129
+ <SettingNote className="mx-3 mt-2 leading-relaxed">
130
+ {bots.length} bots. Each is a line in every prompt, and one more
131
+ for Thursday to choose between.
132
+ </SettingNote>
133
+ )}
114
134
  </div>
115
135
  }
116
136
  right={
@@ -514,6 +534,12 @@ function BotPage({
514
534
  onDone(null);
515
535
  },
516
536
  });
537
+ // Shared with the rail's switch; SWR dedups the read
538
+ const { data: notesOn } = useServerRoute<boolean>(queryKey.botNotes);
539
+ const [clearNote, clearingNote] = useServerAction(clearBotNoteAction, {
540
+ okMessage: "Notes cleared",
541
+ onOk: () => revalidate(queryKey.bot),
542
+ });
517
543
 
518
544
  const ready =
519
545
  name.trim() && description.trim() && provider && model.trim() && !creating;
@@ -531,6 +557,19 @@ function BotPage({
531
557
  });
532
558
  };
533
559
 
560
+ /** The bot writes its own notes; the only thing to do to them from here is throw them away. */
561
+ const confirmClearNote = async () => {
562
+ if (!bot) return;
563
+ const confirmed = await notify.confirm({
564
+ title: `Clear ${bot.name}'s own prompt?`,
565
+ description:
566
+ "What it worked out about doing jobs here is gone, and it starts the next one without it.",
567
+ okText: "Clear",
568
+ destructive: true,
569
+ });
570
+ if (confirmed) clearNote(bot.name);
571
+ };
572
+
534
573
  const confirmRemove = async () => {
535
574
  if (!bot) return;
536
575
  const confirmed = await notify.confirm({
@@ -595,6 +634,50 @@ function BotPage({
595
634
  </div>
596
635
  </div>
597
636
 
637
+ {bot?.note && (
638
+ /* Not a Row like the fields above it: those are settings the owner
639
+ typed, this is what the bot has worked out, and a block under its
640
+ face reads as part of who it is rather than one more control. */
641
+ <div className="overflow-hidden rounded-xl border border-border/60 bg-muted/25">
642
+ <div className="flex items-center gap-2.5 border-b border-border/60 px-4 py-3">
643
+ <PencilLine className="size-3.5 shrink-0 text-muted-foreground" />
644
+ <span className="min-w-0 flex-1 truncate text-[13px] font-medium">
645
+ What {bot.name} has worked out
646
+ </span>
647
+ <span className="shrink-0 font-mono text-[10px] text-muted-foreground">
648
+ {bot.note.length}/{BOT_NOTES.chars}
649
+ </span>
650
+ </div>
651
+ <p className="px-4 py-3.5 text-[13px] leading-relaxed whitespace-pre-wrap">
652
+ {bot.note}
653
+ </p>
654
+ <div className="flex items-center gap-2 px-4 pb-3">
655
+ {/* No amber: switched off is the user's own choice, not something
656
+ waiting on them. The word carries it. */}
657
+ <span className="min-w-0 flex-1 truncate font-mono text-[10px] text-muted-foreground">
658
+ {notesOn === false
659
+ ? "Paused — bots are not keeping their own prompt"
660
+ : "It writes this itself when a job ends"}
661
+ {bot.noteAt && (
662
+ <>
663
+ <span className="px-1.5 opacity-50">·</span>
664
+ {shortAgo(bot.noteAt)}
665
+ </>
666
+ )}
667
+ </span>
668
+ <Button
669
+ size="sm"
670
+ variant="ghost"
671
+ loading={clearingNote}
672
+ onClick={confirmClearNote}
673
+ className="shrink-0 text-muted-foreground hover:text-destructive"
674
+ >
675
+ Clear
676
+ </Button>
677
+ </div>
678
+ </div>
679
+ )}
680
+
598
681
  <MarkPicker
599
682
  icon={icon}
600
683
  onChange={(next) => {
@@ -668,29 +751,49 @@ function BotPage({
668
751
  );
669
752
  }
670
753
 
671
- /** What the roster's pick is, in the section's rail. */
754
+ /**
755
+ * What the roster's pick is, and the one setting that is the whole set's rather
756
+ * than any bot's: whether bots keep their own instructions at all.
757
+ */
672
758
  function BotRail({ bot }: { bot: Bot | null }) {
673
- if (!bot)
674
- return (
759
+ const { data: notesOn, mutate } = useServerRoute<boolean>(queryKey.botNotes);
760
+ const [setNotesOn] = useServerAction(setBotNotesOnAction, {
761
+ onOk: () => revalidate(queryKey.botNotes),
762
+ });
763
+
764
+ const tokens = bot ? bot.tokens.input + bot.tokens.output : 0;
765
+ return (
766
+ <>
675
767
  <SettingRailNote>
676
- Becomes a bot once it has a name and a model
768
+ {bot ? (
769
+ <span className="font-mono text-[11px]">
770
+ {bot.name}
771
+ <span className="px-1.5 opacity-50">·</span>
772
+ <span
773
+ title={`in ${formatCount(bot.tokens.input)} · out ${formatCount(bot.tokens.output)}`}
774
+ >
775
+ {tokens > 0 ? `${formatCount(tokens)} tokens` : "no tokens yet"}
776
+ </span>
777
+ <span className="px-1.5 opacity-50">·</span>
778
+ since {whenOf(bot.createdAt)}
779
+ </span>
780
+ ) : (
781
+ "Becomes a bot once it has a name and a model"
782
+ )}
677
783
  </SettingRailNote>
678
- );
679
- const tokens = bot.tokens.input + bot.tokens.output;
680
- return (
681
- <SettingRailNote>
682
- <span className="font-mono text-[11px]">
683
- {bot.name}
684
- <span className="px-1.5 opacity-50">·</span>
685
- <span
686
- title={`in ${formatCount(bot.tokens.input)} · out ${formatCount(bot.tokens.output)}`}
687
- >
688
- {tokens > 0 ? `${formatCount(tokens)} tokens` : "no tokens yet"}
689
- </span>
690
- <span className="px-1.5 opacity-50">·</span>
691
- since {whenOf(bot.createdAt)}
784
+ <span className="shrink-0 text-xs text-muted-foreground">
785
+ Bots keep their own prompt
692
786
  </span>
693
- </SettingRailNote>
787
+ <Switch
788
+ checked={notesOn ?? true}
789
+ disabled={notesOn === undefined}
790
+ onCheckedChange={(on) => {
791
+ void mutate(on, false);
792
+ setNotesOn(on);
793
+ }}
794
+ aria-label="Bots keep their own prompt"
795
+ />
796
+ </>
694
797
  );
695
798
  }
696
799
 
@@ -226,9 +226,11 @@ export async function deleteFinishedTasks() {
226
226
  const removed = await database
227
227
  .delete(taskTable)
228
228
  .where(inArray(taskTable.status, ["done", "failed"]))
229
- .returning({ id: taskTable.id });
229
+ // The label comes back too: a job's working folder is named for it and goes
230
+ // with the row (bot.runner removeFinishedTasks).
231
+ .returning({ id: taskTable.id, label: taskTable.label });
230
232
  if (removed.length) changed();
231
- return removed.length;
233
+ return removed;
232
234
  }
233
235
 
234
236
  /** Every job, newest first. The caller stops each one before deleting it (bot.runner removeTask). */
@@ -275,6 +277,8 @@ export type TaskMessageInput = {
275
277
  content: ModelMessage["content"];
276
278
  /** Compaction summary row; `listThread` re-reads from here. */
277
279
  compact?: boolean;
280
+ /** Drawn as the app's own line, not as something a person or a bot said. */
281
+ note?: boolean;
278
282
  };
279
283
 
280
284
  /**
@@ -288,13 +292,20 @@ export async function upsertMessage(
288
292
  ) {
289
293
  await database
290
294
  .insert(taskMessageTable)
291
- .values({ taskId, seq, ...message, compact: message.compact ?? false })
295
+ .values({
296
+ taskId,
297
+ seq,
298
+ ...message,
299
+ compact: message.compact ?? false,
300
+ note: message.note ?? false,
301
+ })
292
302
  .onConflictDoUpdate({
293
303
  target: [taskMessageTable.taskId, taskMessageTable.seq],
294
304
  set: {
295
305
  role: message.role,
296
306
  content: message.content,
297
307
  compact: message.compact ?? false,
308
+ note: message.note ?? false,
298
309
  },
299
310
  });
300
311
  changed();
@@ -512,8 +523,10 @@ function linesOf(message: StoredMessage, owner: string): TaskLine[] {
512
523
  if (message.seq === 0) return [];
513
524
  const text = typeof content === "string" ? content : textOf(content);
514
525
  if (!text) return [];
515
- // Compaction summary row: a marker, not the user's words (bot.run compact)
516
- if (message.compact) return [{ ...base, id: id(0), kind: "note", text }];
526
+ // The app's own line — a compaction marker, or why a run stopped — never
527
+ // the user's words. `compact` is read too: rows written before `note` was.
528
+ if (message.note || message.compact)
529
+ return [{ ...base, id: id(0), kind: "note", text }];
517
530
  return [{ ...base, id: id(0), kind: "user", text }];
518
531
  }
519
532
 
@@ -25,6 +25,7 @@ import {
25
25
  isFading,
26
26
  type MemoryNote,
27
27
  type MemorySection,
28
+ memorySourceLabel,
28
29
  noteTitle,
29
30
  sectionOf,
30
31
  } from "@/features/memory/memory.schema";
@@ -348,8 +349,16 @@ function NotePage({ note }: { note: MemoryNote }) {
348
349
  <span className="flex-1 text-sm leading-relaxed text-foreground/90">
349
350
  {fact.text}
350
351
  </span>
351
- <span className="shrink-0 pt-0.5 font-mono text-[11px] text-muted-foreground/50">
352
- {format(fact.createdAt, "yyyy.MM.dd")}
352
+ {/* When, and whose hand — memory is kept by four of them */}
353
+ <span className="shrink-0 pt-0.5 text-right text-[11px] text-muted-foreground/50">
354
+ <span className="block font-mono">
355
+ {format(fact.createdAt, "yyyy.MM.dd")}
356
+ </span>
357
+ {memorySourceLabel(fact.source) && (
358
+ <span className="block">
359
+ {memorySourceLabel(fact.source)}
360
+ </span>
361
+ )}
353
362
  </span>
354
363
  </li>
355
364
  ))}
@@ -12,6 +12,7 @@ import {
12
12
  type MemoryAlwaysLoaded,
13
13
  type MemoryNoteView,
14
14
  type MemoryNoteWrite,
15
+ type MemorySource,
15
16
  type MemoryWrite,
16
17
  recallScore,
17
18
  } from "./memory.schema";
@@ -68,6 +69,7 @@ function latestFacts(noteIds: number[]) {
68
69
  noteId: memoryFactTable.noteId,
69
70
  text: memoryFactTable.text,
70
71
  alwaysLoad: memoryFactTable.alwaysLoad,
72
+ source: memoryFactTable.source,
71
73
  createdAt: memoryFactTable.createdAt,
72
74
  })
73
75
  .from(memoryFactTable)
@@ -129,7 +131,8 @@ export async function deleteNote(id: number) {
129
131
  export async function addFact(noteId: number, text: string) {
130
132
  const [fact] = await database
131
133
  .insert(memoryFactTable)
132
- .values({ noteId, text })
134
+ // Only the screen calls this, so the hand is known here (memory.action)
135
+ .values({ noteId, text, source: "user" })
133
136
  .returning();
134
137
  await touchNote(noteId);
135
138
  changed();
@@ -155,7 +158,7 @@ export async function reviseFact(noteId: number, factId: number, text: string) {
155
158
 
156
159
  const [fact] = await tx
157
160
  .insert(memoryFactTable)
158
- .values({ noteId, text })
161
+ .values({ noteId, text, source: "user" })
159
162
  .returning();
160
163
  await tx
161
164
  .update(memoryNoteTable)
@@ -313,6 +316,8 @@ export const sameFact = (text: string) =>
313
316
  */
314
317
  export async function writeNotes(
315
318
  input: MemoryNoteWrite[],
319
+ /** Which hand is writing; recorded on every fact (memory.schema MemorySource). */
320
+ source: MemorySource,
316
321
  ): Promise<MemoryWrite> {
317
322
  const unnamed: string[] = [];
318
323
  const paths: string[] = [];
@@ -466,6 +471,7 @@ export async function writeNotes(
466
471
  .values({
467
472
  noteId,
468
473
  text: fact.text,
474
+ source,
469
475
  alwaysLoad: claim(
470
476
  fact.alwaysLoad ?? target.alwaysLoad,
471
477
  fact.text,
@@ -494,6 +500,7 @@ export async function writeNotes(
494
500
  .values({
495
501
  noteId,
496
502
  text: fact.text,
503
+ source,
497
504
  alwaysLoad: claim(fact.alwaysLoad === true, fact.text),
498
505
  })
499
506
  .returning({
@@ -1,12 +1,42 @@
1
1
  import { z } from "zod";
2
2
  import { type DateLike, DateLikeSchema, toDate } from "@/lib/date-like";
3
3
 
4
+ /**
5
+ * Who put a fact here. Memory is one note kept by four hands — the user typing
6
+ * on the screen, the call as they talk, a bot that turned something up mid-job,
7
+ * and the pass that reads calls back afterwards — and they are not equally
8
+ * close to the user. A reader that cannot tell them apart reads what a bot
9
+ * inferred as something the user said.
10
+ */
11
+ export const MemorySourceSchema = z.enum(["user", "call", "bot", "tidy"]);
12
+
13
+ export type MemorySource = z.infer<typeof MemorySourceSchema>;
14
+
15
+ /**
16
+ * How a source reads on screen. Recorded for the person looking at their own
17
+ * memory, not for a model: a bare `bot` in a prompt says nothing a reader can
18
+ * act on — it cannot tell whether that was itself — and costs a sentence to
19
+ * explain. Empty where it was never recorded.
20
+ */
21
+ export const memorySourceLabel = (source: MemorySource | null | undefined) =>
22
+ source === "user"
23
+ ? "you"
24
+ : source === "call"
25
+ ? "on a call"
26
+ : source === "bot"
27
+ ? "a bot"
28
+ : source === "tidy"
29
+ ? "read back"
30
+ : "";
31
+
4
32
  // Storage is fact-based: one row per fact, history via isLatest.
5
33
  export const MemoryFactSchema = z.object({
6
34
  id: z.number(),
7
35
  text: z.string(),
8
36
  /** Loaded into every prompt without opening the note (ALWAYS_LOADED_MAX). */
9
37
  alwaysLoad: z.boolean(),
38
+ /** Null on rows written before the hand was recorded. */
39
+ source: MemorySourceSchema.nullish(),
10
40
  createdAt: DateLikeSchema,
11
41
  });
12
42
 
@@ -28,13 +28,14 @@ import { Skeleton } from "@/components/ui/skeleton";
28
28
  import { Switch } from "@/components/ui/switch";
29
29
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
30
30
  import { Textarea } from "@/components/ui/textarea";
31
- import { PATHS } from "@/config";
31
+ import { PATHS, PROMPT_CROWDED } from "@/config";
32
32
  import {
33
33
  SettingDialogContent,
34
34
  SettingError,
35
35
  SettingFilter,
36
36
  SettingGroup,
37
37
  SettingItems,
38
+ SettingNote,
38
39
  SettingRailNote,
39
40
  SettingScreen,
40
41
  SettingSkeleton,
@@ -128,6 +129,13 @@ export function SkillsSetting() {
128
129
  ))}
129
130
  </SettingItems>
130
131
  </SettingGroup>
132
+
133
+ {on > PROMPT_CROWDED.skills && (
134
+ <SettingNote>
135
+ {on} skills are on. Each is a line in every prompt a bot reads, and
136
+ one more to look past when it picks.
137
+ </SettingNote>
138
+ )}
131
139
  </SettingScreen>
132
140
  );
133
141
  }
@@ -2,6 +2,7 @@ import {
2
2
  mkdir,
3
3
  readdir,
4
4
  realpath,
5
+ rm,
5
6
  stat,
6
7
  unlink,
7
8
  writeFile,
@@ -42,7 +43,12 @@ export const ARTIFACTS = join(WORKSPACE, PATHS.artifacts);
42
43
  * Created on open, and the only places inside the workspace `write_file`
43
44
  * accepts. `.agents` is the skills CLI's install folder.
44
45
  */
45
- const BOT_FOLDERS = [PATHS.artifacts, PATHS.projects, PATHS.scratch];
46
+ const BOT_FOLDERS = [
47
+ PATHS.artifacts,
48
+ PATHS.projects,
49
+ PATHS.scratch,
50
+ PATHS.bots,
51
+ ];
46
52
  const WRITABLE = new Set([...BOT_FOLDERS, ".agents"]);
47
53
 
48
54
  /** Where playwright-cli drops a snapshot after every command. Its name, not ours. */
@@ -61,7 +67,7 @@ export function writeRefusal(full: string): string | null {
61
67
  const rel = relative(WORKSPACE, full);
62
68
  const [top] = rel.split(sep);
63
69
  if (top && WRITABLE.has(top)) return null;
64
- return `Not written: ${rel || "the workspace root"} is outside the workspace's folders. Finished work goes under ${PATHS.artifacts}/, code under ${PATHS.projects}/, everything else under ${PATHS.scratch}/.`;
70
+ return `Not written: ${rel || "the workspace root"} is outside the workspace's folders. Finished work goes under ${PATHS.artifacts}/, code under ${PATHS.projects}/, your own kit under ${PATHS.bots}/, and this job's working material under ${PATHS.scratch}/.`;
65
71
  }
66
72
  if (under(APP_ROOT, full)) {
67
73
  return `Not written: ${full} is inside the app that is running you, not the user's files. Work in ${WORKSPACE}; outside it, write only where the user pointed you.`;
@@ -89,6 +95,50 @@ export async function insideWorkspace(rel: string): Promise<string | null> {
89
95
  return under(root, target) ? target : null;
90
96
  }
91
97
 
98
+ /**
99
+ * Where one job keeps what it is still working on. `scratch/` used to be shared
100
+ * by every job at once, so nobody could tell whose a file was or when it stopped
101
+ * mattering, and it only grew. A job has a beginning and an end, which is what
102
+ * makes its working material safe to clear later — and it is the right unit
103
+ * rather than the bot, because several bots work inside one job (`ask_bot`) and
104
+ * one bot runs many jobs.
105
+ *
106
+ * Named for the label so the folder is readable on the Workspace screen, with
107
+ * the head of the id after it so two jobs called the same thing stay apart.
108
+ * Finished work never lands here: that is `artifacts/`, which stays flat and
109
+ * unattributed, and code is `projects/`, which outlives the job that started it.
110
+ */
111
+ export function jobScratch(taskId: string, label: string): string {
112
+ const slug =
113
+ label
114
+ .trim()
115
+ .toLowerCase()
116
+ .replace(/[^\p{L}\p{N}]+/gu, "-")
117
+ .replace(/^-+|-+$/g, "")
118
+ .slice(0, 40) || "job";
119
+ return `${PATHS.scratch}/${slug}-${taskId.slice(0, 6)}`;
120
+ }
121
+
122
+ /** Where one bot keeps what it wants on its next job (config PATHS.bots). */
123
+ export const botFolder = (bot: string): string =>
124
+ `${PATHS.bots}/${bot.replace(/[^\p{L}\p{N}_-]+/gu, "-")}`;
125
+
126
+ export async function openBotFolder(bot: string): Promise<string> {
127
+ const path = botFolder(bot);
128
+ await mkdir(join(WORKSPACE, path), { recursive: true });
129
+ return path;
130
+ }
131
+
132
+ /** Creates it, so a job never has to and never writes to the shared root by mistake. */
133
+ export async function openJobScratch(
134
+ taskId: string,
135
+ label: string,
136
+ ): Promise<string> {
137
+ const path = jobScratch(taskId, label);
138
+ await mkdir(join(WORKSPACE, path), { recursive: true });
139
+ return path;
140
+ }
141
+
92
142
  /**
93
143
  * A job's own browser session. playwright-cli reads the session name from
94
144
  * this variable when `-s=` is not given, so the model never picks one and the
@@ -105,6 +155,52 @@ export const jobShellEnv = (
105
155
  }
106
156
  : {};
107
157
 
158
+ /** Set by `ensureBrowser`, read by `readMachineTools`; null until it has finished. */
159
+ let browserReady: boolean | null = null;
160
+
161
+ /**
162
+ * What the shell can actually reach. A bot writes a job around what is here —
163
+ * node or python3, which package manager — and the platform does not say: two
164
+ * macs differ. Probed in one `command -v` sweep at the moment it is asked, per
165
+ * job rather than cached, so a job that installs something is not told
166
+ * otherwise on its next run.
167
+ */
168
+ export const PROBED = {
169
+ runtimes: ["node", "python3", "uv", "bun", "deno"],
170
+ managers: ["pnpm", "npm", "yarn"],
171
+ } as const;
172
+
173
+ export type MachineTools = {
174
+ [K in keyof typeof PROBED]: { found: string[]; missing: string[] };
175
+ } & {
176
+ /** Null while `ensureBrowser` is still running; it starts at boot and is not awaited. */
177
+ browser: boolean | null;
178
+ };
179
+
180
+ export async function readMachineTools(
181
+ sandbox: Sandbox,
182
+ ): Promise<MachineTools> {
183
+ const names = Object.values(PROBED).flat();
184
+ const { stdout } = await sandbox.exec(
185
+ names
186
+ .map(
187
+ (name) => `command -v ${name} >/dev/null 2>&1 && printf '%s ' ${name}`,
188
+ )
189
+ .join("; "),
190
+ { timeoutMs: 10_000 },
191
+ );
192
+ const found = new Set(stdout.trim().split(/\s+/).filter(Boolean));
193
+ const split = (list: readonly string[]) => ({
194
+ found: list.filter((name) => found.has(name)),
195
+ missing: list.filter((name) => !found.has(name)),
196
+ });
197
+ return {
198
+ runtimes: split(PROBED.runtimes),
199
+ managers: split(PROBED.managers),
200
+ browser: browserReady,
201
+ };
202
+ }
203
+
108
204
  /**
109
205
  * The browser bots drive. It is not in the package — ~280 MB, and its build
110
206
  * moves with playwright-core — so it is fetched here instead of from an install
@@ -122,7 +218,8 @@ export async function ensureBrowser(): Promise<void> {
122
218
  .exec("playwright-cli install-browser chromium")
123
219
  .catch((cause: unknown) => ({ exitCode: 1, stderr: String(cause) }));
124
220
 
125
- if (done.exitCode === 0) return;
221
+ browserReady = done.exitCode === 0;
222
+ if (browserReady) return;
126
223
  logger.warn(
127
224
  `no browser — bots cannot open a page until \`playwright-cli install-browser chromium\` succeeds: ${done.stderr.trim().split("\n").at(-1) ?? ""}`,
128
225
  );
@@ -137,6 +234,7 @@ export async function closeJobShell(taskId: string): Promise<void> {
137
234
  })
138
235
  .catch(() => {});
139
236
  await pruneBrowserFiles();
237
+ await pruneOutputFiles();
140
238
  }
141
239
 
142
240
  /**
@@ -145,6 +243,26 @@ export async function closeJobShell(taskId: string): Promise<void> {
145
243
  */
146
244
  const BROWSER_FILE_TTL_MS = 60 * 60 * 1000;
147
245
 
246
+ async function pruneOutputFiles(): Promise<void> {
247
+ const dir = join(WORKSPACE, PATHS.output);
248
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
249
+ const cutoff = Date.now() - OUTPUT_FILE_TTL_MS;
250
+ for (const entry of entries) {
251
+ if (!entry.isFile()) continue;
252
+ const file = join(dir, entry.name);
253
+ const info = await stat(file).catch(() => null);
254
+ if (info && info.mtimeMs < cutoff) await unlink(file).catch(() => {});
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Tool output over `TOOL_OUTPUT.max` is written here in full and the model is
260
+ * told where. Nothing read it after the run that made it, and nothing deleted
261
+ * it either — a day is long enough to still be looking, short enough that the
262
+ * folder does not become the biggest thing in the workspace.
263
+ */
264
+ const OUTPUT_FILE_TTL_MS = 24 * 60 * 60 * 1000;
265
+
148
266
  async function pruneBrowserFiles(): Promise<void> {
149
267
  const dir = join(WORKSPACE, BROWSER_DIR);
150
268
  const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
@@ -185,3 +303,18 @@ export async function openWorkspace(): Promise<Sandbox> {
185
303
  toolPath: TOOL_PATH,
186
304
  });
187
305
  }
306
+
307
+ /**
308
+ * Removes a job's working folder. Its lifetime is the task's: when the row goes,
309
+ * so does what it was working with (features/bot/bot.runner). Never touches
310
+ * `artifacts` or `projects` — those are the user's and outlive every job.
311
+ */
312
+ export async function removeJobScratch(
313
+ taskId: string,
314
+ label: string,
315
+ ): Promise<void> {
316
+ await rm(join(WORKSPACE, jobScratch(taskId, label)), {
317
+ recursive: true,
318
+ force: true,
319
+ }).catch(() => {});
320
+ }
@@ -0,0 +1,37 @@
1
+ import type { KeyboardEvent } from "react";
2
+ import { useState } from "react";
3
+
4
+ /**
5
+ * Draft for a one-line input, committed on blur. Enter blurs (one commit path);
6
+ * a draft shorter than `min` reverts to `value`; an outside change to `value`
7
+ * resets the draft.
8
+ */
9
+ export function useDraft(
10
+ value: string,
11
+ onCommit: (next: string) => void | Promise<void>,
12
+ { min = 1 }: { min?: number } = {},
13
+ ) {
14
+ const [draft, setDraft] = useState(value);
15
+ // Sync during render; an effect would show the stale draft for a frame.
16
+ const [seen, setSeen] = useState(value);
17
+ if (value !== seen) {
18
+ setSeen(value);
19
+ setDraft(value);
20
+ }
21
+
22
+ const commit = () => {
23
+ const next = draft.trim();
24
+ if (next.length < min) return setDraft(value);
25
+ setDraft(next);
26
+ if (next !== value) void onCommit(next);
27
+ };
28
+
29
+ const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
30
+ // Enter during IME composition confirms the character, not the draft.
31
+ if (event.key === "Enter" && !event.nativeEvent.isComposing) {
32
+ event.currentTarget.blur();
33
+ }
34
+ };
35
+
36
+ return { value: draft, set: setDraft, commit, onKeyDown };
37
+ }