thursday-agent 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/prerender-manifest.json +3 -3
  4. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  5. package/.next/server/app/_global-error.html +1 -1
  6. package/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  10. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  11. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/.next/server/app/_not-found.html +1 -1
  13. package/.next/server/app/_not-found.rsc +2 -2
  14. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  15. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  16. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  17. package/.next/server/app/api/artifact/route.js +4 -4
  18. package/.next/server/app/api/artifact/route.js.nft.json +1 -1
  19. package/.next/server/app/api/bot/memory/files/route.js +3 -3
  20. package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
  21. package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
  22. package/.next/server/app/api/bot/route.js.nft.json +1 -1
  23. package/.next/server/app/api/bot/task/[id]/route.js +8 -8
  24. package/.next/server/app/api/bot/task/[id]/route.js.nft.json +1 -1
  25. package/.next/server/app/api/bot/task/route.js +6 -6
  26. package/.next/server/app/api/bot/task/route.js.nft.json +1 -1
  27. package/.next/server/app/api/config/route.js.nft.json +1 -1
  28. package/.next/server/app/api/events/route.js.nft.json +1 -1
  29. package/.next/server/app/api/file/[...path]/route.js +4 -4
  30. package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
  31. package/.next/server/app/api/llm-model/catalog/route.js +2 -2
  32. package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
  33. package/.next/server/app/api/llm-model/credits/route.js +2 -2
  34. package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
  35. package/.next/server/app/api/llm-model/route.js +2 -2
  36. package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
  37. package/.next/server/app/api/llm-model/subscription/route.js +2 -2
  38. package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
  39. package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
  40. package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
  41. package/.next/server/app/api/mcp/route.js.nft.json +1 -1
  42. package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
  43. package/.next/server/app/api/memory/edit/route.js +11 -11
  44. package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
  45. package/.next/server/app/api/memory/route.js.nft.json +1 -1
  46. package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
  47. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  48. package/.next/server/app/api/thursday/call/route.js +6 -6
  49. package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
  50. package/.next/server/app/api/thursday/skills/route.js +6 -6
  51. package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
  52. package/.next/server/app/api/thursday/tool-call/route.js +8 -8
  53. package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
  54. package/.next/server/app/api/workspace/route.js +3 -2
  55. package/.next/server/app/api/workspace/route.js.nft.json +1 -1
  56. package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
  57. package/.next/server/app/artifact/[...path]/page.js +2 -2
  58. package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
  59. package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
  60. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  61. package/.next/server/app/page/server-reference-manifest.json +40 -40
  62. package/.next/server/app/page.js +3 -3
  63. package/.next/server/app/page.js.nft.json +1 -1
  64. package/.next/server/app/page_client-reference-manifest.js +1 -1
  65. package/.next/server/chunks/[root-of-the-server]__026u77e._.js +5 -0
  66. package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +1 -1
  67. package/.next/server/chunks/{[root-of-the-server]__02xjoto._.js → [root-of-the-server]__050zri4._.js} +1 -1
  68. package/.next/server/chunks/[root-of-the-server]__0bg9710._.js +3 -0
  69. package/.next/server/chunks/[root-of-the-server]__0in-d5s._.js +3 -0
  70. package/.next/server/chunks/[root-of-the-server]__0ncs9uu._.js +1 -1
  71. package/.next/server/chunks/[root-of-the-server]__0pa9k-f._.js +8 -0
  72. package/.next/server/chunks/[root-of-the-server]__0qi_m0e._.js +8 -0
  73. package/.next/server/chunks/[root-of-the-server]__0tq0m_k._.js +3 -0
  74. package/.next/server/chunks/[root-of-the-server]__0xep6j_._.js +3 -0
  75. package/.next/server/chunks/[root-of-the-server]__1-ga0kh._.js +2 -2
  76. package/.next/server/chunks/[root-of-the-server]__144l7qt._.js +8 -0
  77. package/.next/server/chunks/[root-of-the-server]__1_04mcf._.js +2 -2
  78. package/.next/server/chunks/[root-of-the-server]__1anc1p9._.js +1 -1
  79. package/.next/server/chunks/[root-of-the-server]__1jg8t86._.js +8 -0
  80. package/.next/server/chunks/{[root-of-the-server]__12cabg9._.js → [root-of-the-server]__1lldzy9._.js} +1 -1
  81. package/.next/server/chunks/[root-of-the-server]__1orn-5b._.js +4 -3
  82. package/.next/server/chunks/{[root-of-the-server]__01dkp4a._.js → [root-of-the-server]__1peqvsi._.js} +1 -1
  83. package/.next/server/chunks/[root-of-the-server]__1w8gm5h._.js +10 -0
  84. package/.next/server/chunks/[root-of-the-server]__1xt39nn._.js +8 -0
  85. package/.next/server/chunks/[root-of-the-server]__1zb8mfo._.js +8 -0
  86. package/.next/server/chunks/_0-fjql4._.js +11 -6
  87. package/.next/server/chunks/_00xh_q0._.js +4 -6
  88. package/.next/server/chunks/_05h2cii._.js +1 -1
  89. package/.next/server/chunks/{_1_9mjx-._.js → _091bi8d._.js} +1 -1
  90. package/.next/server/chunks/_0fzqt2y._.js +57 -45
  91. package/.next/server/chunks/{_0u4aiy2._.js → _0lr92vd._.js} +1 -1
  92. package/.next/server/chunks/_0mecv0f._.js +11 -6
  93. package/.next/server/chunks/_0mh8j4m._.js +1 -1
  94. package/.next/server/chunks/{_0sbynis._.js → _0si7xzn._.js} +1 -1
  95. package/.next/server/chunks/_0wp4le3._.js +1 -1
  96. package/.next/server/chunks/_0y5fg-x._.js +1 -1
  97. package/.next/server/chunks/_1230wbx._.js +5 -5
  98. package/.next/server/chunks/_15hq89o._.js +1 -1
  99. package/.next/server/chunks/{_0obojvv._.js → _18d7-5p._.js} +1 -1
  100. package/.next/server/chunks/{_03kp0jp._.js → _1c78vw5._.js} +71 -61
  101. package/.next/server/chunks/_1d0j961._.js +2 -2
  102. package/.next/server/chunks/_1j8k_j5._.js +1 -1
  103. package/.next/server/chunks/_1krd9rf._.js +1 -1
  104. package/.next/server/chunks/_1php4eq._.js +1 -1
  105. package/.next/server/chunks/{_03a0897._.js → _1zkfbaa._.js} +1 -1
  106. package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
  107. package/.next/server/chunks/features_1qf3cei._.js +6 -1
  108. package/.next/server/chunks/features_memory_1uqvksn._.js +2 -2
  109. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0r28-sm._.js → node_modules_consola_dist_chunks_prompt_mjs_17k8irj._.js} +1 -1
  110. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_1xoq2rl._.js → node_modules_consola_dist_chunks_prompt_mjs_1jidylq._.js} +1 -1
  111. package/.next/server/chunks/ssr/[root-of-the-server]__0-nob52._.js +4 -0
  112. package/.next/server/chunks/ssr/[root-of-the-server]__092knlj._.js +3 -0
  113. package/.next/server/chunks/ssr/{[root-of-the-server]__0_69wqr._.js → [root-of-the-server]__0s4wb56._.js} +1 -1
  114. package/.next/server/chunks/ssr/{[root-of-the-server]__1eh7hid._.js → [root-of-the-server]__0s9dq5u._.js} +1 -1
  115. package/.next/server/chunks/ssr/{[root-of-the-server]__0zkv7z_._.js → [root-of-the-server]__0sd3izg._.js} +2 -2
  116. package/.next/server/chunks/ssr/{[root-of-the-server]__1gtlw6v._.js → [root-of-the-server]__1-13tcn._.js} +3 -3
  117. package/.next/server/chunks/ssr/[root-of-the-server]__10-dz5o._.js +13 -9
  118. package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +1 -1
  119. package/.next/server/chunks/ssr/{[root-of-the-server]__1cyjywu._.js → [root-of-the-server]__1zidpfl._.js} +1 -1
  120. package/.next/server/chunks/ssr/_001_ms1._.js +6 -1
  121. package/.next/server/chunks/ssr/{_07uchnc._.js → _04z2c7b._.js} +2 -2
  122. package/.next/server/chunks/ssr/{_0kbkmar._.js → _06tssji._.js} +2 -2
  123. package/.next/server/chunks/ssr/_0fk4c7d._.js +3 -0
  124. package/.next/server/chunks/ssr/_0i_6q1w._.js +2 -2
  125. package/.next/server/chunks/ssr/{_1xvgn77._.js → _0m2lsd1._.js} +2 -2
  126. package/.next/server/chunks/ssr/_0mggveb._.js +3 -0
  127. package/.next/server/chunks/ssr/_0nbpc_8._.js +3 -0
  128. package/.next/server/chunks/ssr/{_0y0tiht._.js → _0rzucul._.js} +3 -3
  129. package/.next/server/chunks/ssr/{_0mb8zc8._.js → _0s99bgb._.js} +2 -2
  130. package/.next/server/chunks/ssr/_0zcjrbg._.js +3 -0
  131. package/.next/server/chunks/ssr/_1-teee5._.js +4 -4
  132. package/.next/server/chunks/ssr/{_0c-ql2m._.js → _11j92tu._.js} +2 -2
  133. package/.next/server/chunks/ssr/_13fke0x._.js +1 -1
  134. package/.next/server/chunks/ssr/{_01_6m32._.js → _1ebxla1._.js} +2 -2
  135. package/.next/server/chunks/ssr/_1jdgjv9._.js +3 -0
  136. package/.next/server/chunks/ssr/{_0u2jv_x._.js → _1lz_cq2._.js} +60 -50
  137. package/.next/server/chunks/ssr/{_20kd_n0._.js → _1nj4niv._.js} +3 -3
  138. package/.next/server/chunks/ssr/_1ovmc9s._.js +3 -0
  139. package/.next/server/chunks/ssr/_1sji22n._.js +1 -1
  140. package/.next/server/chunks/ssr/_1vyd1ji._.js +1 -1
  141. package/.next/server/chunks/ssr/features_config_components_config-setting_tsx_0xfwqdg._.js +1 -1
  142. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
  143. package/.next/server/chunks/ssr/{node_modules_1-hc6uv._.js → node_modules_0-dr_o7._.js} +1 -1
  144. package/.next/server/chunks/ssr/{node_modules_124bue5._.js → node_modules_04sksn9._.js} +1 -1
  145. package/.next/server/chunks/ssr/{node_modules_1uuj9x1._.js → node_modules_05-n6dz._.js} +1 -1
  146. package/.next/server/chunks/ssr/{node_modules_1i7u1ia._.js → node_modules_056qgf-._.js} +1 -1
  147. package/.next/server/chunks/ssr/{node_modules_0lm7_vm._.js → node_modules_07dsgkx._.js} +1 -1
  148. package/.next/server/chunks/ssr/{node_modules_1h4a4pp._.js → node_modules_0bvopy2._.js} +1 -1
  149. package/.next/server/chunks/ssr/{node_modules_1bc7gu8._.js → node_modules_0fkeku3._.js} +1 -1
  150. package/.next/server/chunks/ssr/{node_modules_0h50b8n._.js → node_modules_0ifp389._.js} +1 -1
  151. package/.next/server/chunks/ssr/{node_modules_0zp9r92._.js → node_modules_0iur59s._.js} +1 -1
  152. package/.next/server/chunks/ssr/{node_modules_063xwpp._.js → node_modules_0m383r2._.js} +1 -1
  153. package/.next/server/chunks/ssr/{node_modules_1bgb6-f._.js → node_modules_0u9da1h._.js} +1 -1
  154. package/.next/server/chunks/ssr/{node_modules_0bvraul._.js → node_modules_0zeg3x_._.js} +1 -1
  155. package/.next/server/chunks/ssr/{node_modules_1t9cyee._.js → node_modules_1-y7nni._.js} +1 -1
  156. package/.next/server/chunks/ssr/{node_modules_1_n3ztw._.js → node_modules_16mkxuo._.js} +1 -1
  157. package/.next/server/chunks/ssr/{node_modules_0eu1va6._.js → node_modules_1a0kmjl._.js} +1 -1
  158. package/.next/server/chunks/ssr/{node_modules_0z5tsu8._.js → node_modules_1chvq5f._.js} +1 -1
  159. package/.next/server/chunks/ssr/{node_modules_0h7u60n._.js → node_modules_1j8j4pt._.js} +1 -1
  160. package/.next/server/chunks/ssr/{node_modules_0rjg6t1._.js → node_modules_1k6n600._.js} +1 -1
  161. package/.next/server/chunks/ssr/{node_modules_1xx28h1._.js → node_modules_1k7z2j6._.js} +1 -1
  162. package/.next/server/chunks/ssr/{node_modules_0h5hnr1._.js → node_modules_1k8j8n0._.js} +1 -1
  163. package/.next/server/chunks/ssr/{node_modules_0mwym0g._.js → node_modules_1lz0oxc._.js} +1 -1
  164. package/.next/server/chunks/ssr/{node_modules_1vqb6po._.js → node_modules_1t77e4n._.js} +1 -1
  165. package/.next/server/chunks/ssr/{node_modules_1l0qtdu._.js → node_modules_1uyk91w._.js} +1 -1
  166. package/.next/server/chunks/ssr/{node_modules_0y_cded._.js → node_modules_1x0ztf4._.js} +1 -1
  167. package/.next/server/chunks/ssr/{node_modules_113tz0l._.js → node_modules_1y79i83._.js} +1 -1
  168. package/.next/server/chunks/ssr/{node_modules_1juq3hs._.js → node_modules_20c28js._.js} +1 -1
  169. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0d3skpl._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0bkwd0u._.js} +1 -1
  170. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0i44kk5._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0p9a9su._.js} +1 -1
  171. package/.next/server/chunks/ssr/{node_modules_@vercel_oidc_dist_0qe30a_._.js → node_modules_@vercel_oidc_dist_1o553h-._.js} +1 -1
  172. package/.next/server/middleware-build-manifest.js +3 -3
  173. package/.next/server/pages/404.html +1 -1
  174. package/.next/server/pages/500.html +1 -1
  175. package/.next/server/server-reference-manifest.js +1 -1
  176. package/.next/server/server-reference-manifest.json +41 -41
  177. package/.next/static/chunks/{17donp69y4do7.js → 0n3jle9kbqfr2.js} +8 -8
  178. package/.next/static/chunks/{3ok9_x74rip3v.js → 11r2yp_yh9_qn.js} +1 -1
  179. package/.next/static/chunks/{26-pr2c9ro222.js → 126fj4pga_72k.js} +1 -1
  180. package/.next/static/chunks/{2a0k6jlq0_v1u.js → 159udiexf6dom.js} +2 -2
  181. package/.next/static/chunks/18gi7o2ibrgh8.js +3 -0
  182. package/.next/static/chunks/{0x79tx04dxbpp.js → 1q9z1tohil7xn.js} +1 -1
  183. package/.next/static/chunks/{3cfi_q6d_wtci.js → 1sjgfets224re.js} +1 -1
  184. package/.next/static/chunks/{3agpgkwaa321p.js → 1wszsr83t01gh.js} +1 -1
  185. package/.next/static/chunks/22_zkas21rpna.js +1 -0
  186. package/.next/static/chunks/{1ekc7ul556kbl.js → 2szintyibu0s5.js} +1 -1
  187. package/.next/static/chunks/{1okqiww3pnwka.js → 3-b6b7entqazs.js} +1 -1
  188. package/.next/static/chunks/3cq5g494yuke8.css +1 -0
  189. package/.next/static/chunks/{2fzs9fm8yarvq.js → 3dmbhsv2u_8kj.js} +1 -1
  190. package/.next/static/chunks/{1grhjo7a5a0_-.js → 3i0a5px-oufgc.js} +1 -1
  191. package/.next/static/chunks/{280t0_e99i276.js → 3wexk9qo8zuj3.js} +3 -3
  192. package/.next/static/chunks/3xre8bu_pk1p_.js +1 -0
  193. package/.next/static/chunks/3zv6t3i77xhr_.js +1 -0
  194. package/.next/static/chunks/{28cizcdaabd4h.js → 408ueu4hfvcg7.js} +1 -1
  195. package/.next/static/chunks/{3xpbkl66ssib5.js → 435atsnhs7ksu.js} +8 -8
  196. package/README.md +46 -23
  197. package/app/api/events/app-event.ts +0 -2
  198. package/bin/port.mjs +65 -0
  199. package/bin/thursday.mjs +2 -33
  200. package/docs/how-it-works.md +11 -7
  201. package/docs/images/face-dark.png +0 -0
  202. package/docs/images/face-light.png +0 -0
  203. package/docs/images/keeps-going.png +0 -0
  204. package/features/ai/chatgpt.ts +42 -20
  205. package/features/ai/load-tools.ts +20 -14
  206. package/features/ai/prompts/bot.prompt.ts +91 -89
  207. package/features/ai/prompts/thursday.prompt.ts +48 -41
  208. package/features/ai/tools/bot.tool.ts +3 -3
  209. package/features/ai/tools/memory.tool.ts +65 -62
  210. package/features/ai/tools/skills.tool.ts +25 -8
  211. package/features/ai/tools/tool-name.ts +0 -1
  212. package/features/ai/tools/workspace.tool.ts +3 -0
  213. package/features/bot/bot.action.ts +6 -1
  214. package/features/bot/bot.run.ts +195 -80
  215. package/features/bot/bot.runner.ts +82 -39
  216. package/features/bot/bot.schema.ts +25 -0
  217. package/features/bot/components/bot-room.tsx +57 -4
  218. package/features/bot/components/bot-tool.tsx +0 -1
  219. package/features/bot/task.query.ts +132 -10
  220. package/features/bot/task.store.ts +26 -9
  221. package/features/memory/components/memory-edit.tsx +7 -5
  222. package/features/thursday/components/thursday.tsx +0 -3
  223. package/features/thursday/tool-line.ts +0 -1
  224. package/features/thursday/use-thursday.ts +1 -1
  225. package/features/workspace/workspace.ts +27 -20
  226. package/package.json +1 -1
  227. package/skills/browser/SKILL.md +6 -5
  228. package/.next/server/chunks/[root-of-the-server]__04q--0f._.js +0 -8
  229. package/.next/server/chunks/[root-of-the-server]__0bl-e63._.js +0 -3
  230. package/.next/server/chunks/[root-of-the-server]__0jq4ohy._.js +0 -8
  231. package/.next/server/chunks/[root-of-the-server]__0ls6_8y._.js +0 -8
  232. package/.next/server/chunks/[root-of-the-server]__1339jnv._.js +0 -8
  233. package/.next/server/chunks/[root-of-the-server]__18_teo8._.js +0 -8
  234. package/.next/server/chunks/[root-of-the-server]__1ccon1w._.js +0 -10
  235. package/.next/server/chunks/[root-of-the-server]__1wa1k7r._.js +0 -8
  236. package/.next/server/chunks/_1zjrdag._.js +0 -3
  237. package/.next/server/chunks/ssr/[root-of-the-server]__1ox7hbk._.js +0 -4
  238. package/.next/server/chunks/ssr/[root-of-the-server]__1pduf5n._.js +0 -3
  239. package/.next/server/chunks/ssr/_0mo2w06._.js +0 -3
  240. package/.next/server/chunks/ssr/_13z3f85._.js +0 -3
  241. package/.next/server/chunks/ssr/_14_lw10._.js +0 -3
  242. package/.next/server/chunks/ssr/_1eoqg_g._.js +0 -3
  243. package/.next/server/chunks/ssr/_1spzm8z._.js +0 -3
  244. package/.next/server/chunks/ssr/lib_date-like_ts_02is0pw._.js +0 -3
  245. package/.next/static/chunks/0tmgrnlje14-s.js +0 -3
  246. package/.next/static/chunks/0xzgvc0fkdb6t.js +0 -1
  247. package/.next/static/chunks/14hgtsjg-kael.css +0 -1
  248. package/.next/static/chunks/2hw2f6zmt7fmt.js +0 -1
  249. package/.next/static/chunks/3111pj5wnjgos.js +0 -1
  250. package/features/memory/components/memory-view.tsx +0 -84
  251. /package/.next/static/{x1cCqY0kWpR_VcYx_lWO5 → G6-gNoyGDEcgAsq-L4PE1}/_buildManifest.js +0 -0
  252. /package/.next/static/{x1cCqY0kWpR_VcYx_lWO5 → G6-gNoyGDEcgAsq-L4PE1}/_clientMiddlewareManifest.js +0 -0
  253. /package/.next/static/{x1cCqY0kWpR_VcYx_lWO5 → G6-gNoyGDEcgAsq-L4PE1}/_ssgManifest.js +0 -0
@@ -6,9 +6,8 @@ import { splitFrontmatter } from "@/features/skills/skills.query";
6
6
  import type { Sandbox } from "@/lib/sandbox";
7
7
 
8
8
  /**
9
- * A skill's full text is returned once per run; repeat loads get a pointer
10
- * back to the first result, since every step resends the whole context.
11
- * The tool set is built per run, so the `loaded` set lives in this closure.
9
+ * Reuse instructions only while their full text remains in this bot's context.
10
+ * A resume can carry them; compaction can remove them.
12
11
  */
13
12
  export const createSkillTools = ({
14
13
  skills,
@@ -17,7 +16,6 @@ export const createSkillTools = ({
17
16
  skills: SkillMetadata[];
18
17
  sandbox: Sandbox;
19
18
  }) => {
20
- const loaded = new Set<string>();
21
19
  return {
22
20
  [TOOL_NAMES.load_skill]: tool({
23
21
  description:
@@ -29,7 +27,7 @@ export const createSkillTools = ({
29
27
  "Exact name from the Skills list. Do not guess names that are not on it.",
30
28
  ),
31
29
  }),
32
- execute: async ({ name }) => {
30
+ execute: async ({ name }, { messages }) => {
33
31
  const skill = skills.find(
34
32
  (s) => s.name.toLowerCase() === name.toLowerCase(),
35
33
  );
@@ -41,10 +39,30 @@ export const createSkillTools = ({
41
39
  }
42
40
 
43
41
  const skillFile = `${skill.path}/SKILL.md`;
44
- if (loaded.has(skill.path)) {
42
+ const carried = messages.some(
43
+ (message) =>
44
+ Array.isArray(message.content) &&
45
+ message.content.some((part) => {
46
+ if (
47
+ part.type !== "tool-result" ||
48
+ part.toolName !== TOOL_NAMES.load_skill ||
49
+ part.output.type !== "json"
50
+ )
51
+ return false;
52
+ const value = part.output.value as {
53
+ skillDirectory?: string;
54
+ content?: string;
55
+ } | null;
56
+ return (
57
+ value?.skillDirectory === skill.path &&
58
+ typeof value.content === "string"
59
+ );
60
+ }),
61
+ );
62
+ if (carried) {
45
63
  return {
46
64
  skillDirectory: skill.path,
47
- note: `You already loaded '${skill.name}' in this run — its instructions are above, unchanged. Scroll back to that result rather than reading it again; if you need the file itself, read ${skillFile} with the shell.`,
65
+ note: `Use the full '${skill.name}' instructions already in your conversation. To check for changes on disk, read ${skillFile} with the shell.`,
48
66
  };
49
67
  }
50
68
 
@@ -55,7 +73,6 @@ export const createSkillTools = ({
55
73
  path: skill.path,
56
74
  limit: 50,
57
75
  });
58
- loaded.add(skill.path);
59
76
 
60
77
  return {
61
78
  skillDirectory: skill.path,
@@ -7,7 +7,6 @@ export const TOOL_NAMES = {
7
7
  memory_recall: "memory_recall",
8
8
  memory_remember: "memory_remember",
9
9
  memory_forget: "memory_forget",
10
- memory_show: "memory_show",
11
10
  memory_conversation: "memory_conversation",
12
11
 
13
12
  bash: "bash",
@@ -18,6 +18,8 @@ export const createWorkspaceTools = (
18
18
  env?: Record<string, string>;
19
19
  /** Attach the shell guide to this run's first `bash` result (bots only). */
20
20
  guide?: boolean;
21
+ /** How long one command may run; unset is the sandbox's own limit (config EXEC_TIMEOUT_MS). */
22
+ timeoutMs?: number;
21
23
  },
22
24
  ): ToolSet => {
23
25
  /** Fold absolute sandbox paths against cwd; they still resolve when handed back. */
@@ -45,6 +47,7 @@ export const createWorkspaceTools = (
45
47
  const result = await sandbox.exec(command, {
46
48
  signal: abortSignal,
47
49
  env: options.env,
50
+ timeoutMs: options.timeoutMs,
48
51
  });
49
52
  if (!owed) return result;
50
53
  owed = false;
@@ -110,7 +110,12 @@ export const startTaskAction = serverAction(
110
110
  const worker = await findBot(bot);
111
111
  if (!worker) publicError(`No bot called "${bot}".`);
112
112
  const label = labelFor(said);
113
- const id = await startTask({ bot: worker.name, request: said, label });
113
+ const id = await startTask({
114
+ bot: worker.name,
115
+ request: said,
116
+ label,
117
+ from: "user",
118
+ });
114
119
  return { id, label, bot: worker.name };
115
120
  },
116
121
  );
@@ -41,17 +41,21 @@ import {
41
41
  type TokenUsage,
42
42
  } from "@/features/bot/bot.schema";
43
43
  import {
44
- closeHiddenBrowser,
44
+ botBrowserSession,
45
45
  filesOnDisk,
46
46
  openBotFolder,
47
47
  openJobScratch,
48
48
  } from "@/features/workspace/workspace";
49
49
  import { logger } from "@/lib/logger";
50
50
  import { publicError } from "@/lib/public-error";
51
+ import { createKeyedLock } from "@/lib/queue";
51
52
  import { estimateTokens } from "@/lib/tokens";
53
+ import { PromiseChain } from "@/lib/utils";
52
54
  import { findJobBot } from "./bot.query";
53
55
  import {
54
56
  findTask,
57
+ listBorrowedTails,
58
+ listBotThread,
55
59
  listWrittenPaths,
56
60
  optionsOf,
57
61
  writtenPathsIn,
@@ -65,6 +69,11 @@ import {
65
69
 
66
70
  /** One thing that happened in a run. The runner adds which bot and inside which `ask_bot` call. */
67
71
  export type BotEvent =
72
+ /** A request enters this participant's own thread before its model runs. */
73
+ | {
74
+ type: "request";
75
+ content: Extract<ModelMessage, { role: "user" }>["content"];
76
+ }
68
77
  /** A finished chunk of prose. */
69
78
  | { type: "text"; text: string }
70
79
  /** A tool call. `id` is where the result lands. */
@@ -147,6 +156,8 @@ export type RunOptions = {
147
156
  * in one window.
148
157
  */
149
158
  session?: string | null;
159
+ /** Shared by every participant in this live segment; history itself lives in the DB. */
160
+ desks?: ReturnType<typeof createBotDesks>;
150
161
  /**
151
162
  * Drained once per step boundary; each string goes in front of the model as
152
163
  * a user turn before the next step. Only the job's own bot has one.
@@ -189,11 +200,11 @@ export async function runBot(
189
200
  ): Promise<void> {
190
201
  const depth = options.depth ?? 0;
191
202
  const parent = options.parent ?? null;
192
- const name = input.bot.trim();
203
+ const bot = await findJobBot(input.bot.trim());
204
+ const name = bot?.name ?? input.bot.trim();
193
205
  const emit = (event: BotEvent) =>
194
206
  options.emit({ ...event, bot: name, parent });
195
207
 
196
- const bot = await findJobBot(name);
197
208
  if (!bot) {
198
209
  await emit({
199
210
  type: "error",
@@ -205,7 +216,11 @@ export async function runBot(
205
216
  // Switched off is not a bot to pick — the backstop for every fresh start
206
217
  // (`delegate`, `ask_bot`, the screen). A resume carries `messages` instead and
207
218
  // is let through: its thread already exists and the user can still answer it.
208
- if (bot.disabled && "request" in input) {
219
+ const previous =
220
+ "askedBy" in input && options.taskId
221
+ ? await listBotThread(options.taskId, name)
222
+ : [];
223
+ if (bot.disabled && "request" in input && !previous.length) {
209
224
  await emit({
210
225
  type: "error",
211
226
  message: `${bot.name} is switched off. Use a name from the list.`,
@@ -214,6 +229,30 @@ export async function runBot(
214
229
  return;
215
230
  }
216
231
 
232
+ options = { ...options, desks: options.desks ?? createBotDesks() };
233
+ let history: ModelMessage[];
234
+ if ("messages" in input) {
235
+ history = input.messages;
236
+ } else {
237
+ const content = previous.length
238
+ ? `## ${input.askedBy} → ${name}: the next request\n\n${input.request.trim()}${input.context?.trim() ? `\n\n## What ${input.askedBy} passes on\n\n${input.context.trim()}` : ""}`
239
+ : buildHandoff({
240
+ chain: input.chain,
241
+ bots: [...input.above, name],
242
+ did: input.context ?? null,
243
+ part: input.request,
244
+ });
245
+ history = [
246
+ ...resumeThread(
247
+ previous,
248
+ options.taskId ? await listBorrowedTails(options.taskId) : undefined,
249
+ ),
250
+ { role: "user", content },
251
+ ];
252
+ options.signal?.throwIfAborted();
253
+ await emit({ type: "request", content });
254
+ }
255
+
217
256
  // Tools are built on the model: web search runs on this bot's model (load-tools).
218
257
  const model = await resolveModel(bot);
219
258
  // The job's row: the same one for every seat on the job, a borrowed bot's too
@@ -254,21 +293,6 @@ export async function runBot(
254
293
  ("askedBy" in input ? 0 : row?.contextBudget) || Number.POSITIVE_INFINITY,
255
294
  );
256
295
 
257
- const history: ModelMessage[] =
258
- "messages" in input
259
- ? input.messages
260
- : [
261
- {
262
- role: "user",
263
- content: buildHandoff({
264
- chain: input.chain,
265
- bots: [...input.above, name],
266
- did: input.context ?? null,
267
- part: input.request,
268
- }),
269
- },
270
- ];
271
-
272
296
  /** Remaining `ask_back` calls for a borrowed bot; the tool counts it down. */
273
297
  const asks = { left: BOT_RUN.askBack };
274
298
  // A borrowed bot asks back instead of asking Thursday. Any seat above the
@@ -344,6 +368,7 @@ export async function runBot(
344
368
  const summary = await compact(model.model, agentTools, messages, {
345
369
  signal: options.signal,
346
370
  budget,
371
+ instructions: prompt.text,
347
372
  }).finally(quiet.release);
348
373
  logger.debug(
349
374
  `compacted ${messages.length} messages at ${size} tokens (budget ${budget})`,
@@ -701,17 +726,19 @@ async function filesUnder(
701
726
  /**
702
727
  * The model summarizes its own context and continues from the summary. Tools
703
728
  * are passed with calling off: providers refuse a history of tool calls
704
- * without the tools that made them. A context too long to summarise whole —
705
- * the very thing a compaction is for — is tried once more without the tool
706
- * calls and results but the last few. A failure throws with its cause kept:
707
- * the runner parks what a retry or a smaller budget can fix (bot.runner
708
- * parkTask) and fails the rest; the thread stays.
729
+ * without the tools that made them. The run's own instructions go too: the
730
+ * summary is written by the bot the thread belongs to, and the provider's
731
+ * cached prefix, instructions first, still matches the run's. A context too
732
+ * long to summarise whole — the very thing a compaction is for — is tried once
733
+ * more without the tool calls and results but the last few. A failure throws
734
+ * with its cause kept: the runner parks what a retry or a smaller budget can
735
+ * fix (bot.runner parkTask) and fails the rest; the thread stays.
709
736
  */
710
737
  async function compact(
711
738
  model: LanguageModel,
712
739
  tools: ToolSet,
713
740
  messages: ModelMessage[],
714
- options: { signal?: AbortSignal; budget: number },
741
+ options: { signal?: AbortSignal; budget: number; instructions: string },
715
742
  ): Promise<{ text: string; usage: TokenUsage }> {
716
743
  let failure: unknown;
717
744
  try {
@@ -745,10 +772,11 @@ async function summarize(
745
772
  model: LanguageModel,
746
773
  tools: ToolSet,
747
774
  messages: ModelMessage[],
748
- options: { signal?: AbortSignal; budget: number },
775
+ options: { signal?: AbortSignal; budget: number; instructions: string },
749
776
  ): Promise<{ text: string; usage: TokenUsage }> {
750
777
  const { text, usage } = await generateText({
751
778
  model,
779
+ system: options.instructions,
752
780
  tools,
753
781
  toolChoice: "none",
754
782
  messages: [
@@ -875,15 +903,20 @@ export async function runBotToText(
875
903
  await runBot(input, {
876
904
  ...options,
877
905
  emit: async (event) => {
878
- if (event.type === "answer") {
906
+ if (
907
+ event.parent === (options.parent ?? null) &&
908
+ event.type === "answer"
909
+ ) {
879
910
  outcome = event.stopped
880
911
  ? `${event.text}\n\n(As far as they got — they stopped before finishing.)`
881
912
  : event.text;
882
913
  }
883
- if (event.type === "error") failure = event.message;
914
+ if (event.parent === (options.parent ?? null) && event.type === "error")
915
+ failure = event.message;
884
916
  await options.emit(event);
885
917
  },
886
918
  });
919
+ options.signal?.throwIfAborted();
887
920
  if (failure) publicError(failure);
888
921
  return outcome;
889
922
  }
@@ -914,6 +947,11 @@ function withAskBot(
914
947
  },
915
948
  ): ToolSet {
916
949
  if (asker.peers.length === 0) return tools;
950
+ // Calls made in the same step answer from one evolving conversation.
951
+ const conversations = new WeakMap<
952
+ ModelMessage[],
953
+ { messages: ModelMessage[]; answer: ReturnType<typeof PromiseChain> }
954
+ >();
917
955
 
918
956
  const agentTools: ToolSet = {
919
957
  ...tools,
@@ -921,66 +959,143 @@ function withAskBot(
921
959
  description: askBotSpec.description,
922
960
  inputSchema: askBotSpec.parameters,
923
961
  execute: async ({ bot, request, context }, call) => {
924
- // A bot above is blocked waiting on this very call; borrowing it back never returns
962
+ const found = await findJobBot(bot);
963
+ if (!found) return `No bot named "${bot}". Use a name from the list.`;
964
+ bot = found.name;
925
965
  if (bot === asker.name || asker.above.includes(bot)) {
926
- return `${bot} is already on this job, above you and waiting on your part. Pick another bot, or do it yourself.`;
927
- }
928
- // The borrowed bot works in the job's folder but drives a browser session
929
- // of its own, under this call's id. When it answers, the same rule as a
930
- // job's end: what it showed on their screen stays, what nobody can see
931
- // closes (workspace.ts closeHiddenBrowser).
932
- const session = asker.options.taskId
933
- ? `${asker.options.session ?? asker.options.taskId}-${call.toolCallId.slice(0, 8)}`
934
- : null;
935
- const exchanges: { question: string; answer: string }[] = [];
936
- const answer = async (question: string) => {
937
- const reply = await answerBack({
938
- model: asker.model,
939
- instructions: asker.instructions,
940
- // Same tools, calling off (see compact).
941
- tools: agentTools,
942
- messages: call.messages,
943
- asked: { bot, request },
944
- question,
945
- signal: asker.options.signal,
946
- });
947
- await asker.emit({ type: "answered", question, ...reply });
948
- exchanges.push({ question, answer: reply.text });
949
- return reply.text;
950
- };
951
- try {
952
- const outcome = await runBotToText(
953
- {
954
- bot,
955
- request,
956
- context,
957
- askedBy: asker.name,
958
- chain: asker.chain,
959
- above: [...asker.above, asker.name],
960
- },
961
- {
962
- ...asker.options,
963
- // This call's signal, not the job's: a stream that ends — the job
964
- // stopping, or the model going quiet — ends the part it waited on
965
- signal: call.abortSignal ?? asker.options.signal,
966
- depth: asker.depth + 1,
967
- parent: call.toolCallId,
968
- session,
969
- answer,
970
- },
971
- );
972
- return exchanges.length
973
- ? `${outcome}\n\n${exchangeLines(bot, exchanges)}`
974
- : outcome;
975
- } finally {
976
- if (session) void closeHiddenBrowser(session);
966
+ return `${bot} is already above you and waiting on this part. Use ask_back for a question, or pick another bot.`;
977
967
  }
968
+ const signal = call.abortSignal ?? asker.options.signal;
969
+ const desks = asker.options.desks!;
970
+ return desks.run(asker.name, bot, signal, async () => {
971
+ const session = asker.options.taskId
972
+ ? botBrowserSession(asker.options.taskId, bot)
973
+ : null;
974
+ const exchanges: { question: string; answer: string }[] = [];
975
+ let conversation = conversations.get(call.messages);
976
+ if (!conversation) {
977
+ conversation = {
978
+ messages: [...call.messages],
979
+ answer: PromiseChain(),
980
+ };
981
+ conversations.set(call.messages, conversation);
982
+ }
983
+ const current = conversation;
984
+ const answer = (question: string) =>
985
+ current.answer(async () => {
986
+ signal?.throwIfAborted();
987
+ const reply = await answerBack({
988
+ model: asker.model,
989
+ instructions: asker.instructions,
990
+ tools: agentTools,
991
+ messages: current.messages,
992
+ asked: { bot, request },
993
+ question,
994
+ signal,
995
+ });
996
+ current.messages.push(
997
+ {
998
+ role: "user",
999
+ content: `${bot} asks about the part you handed over:\n\n${question}`,
1000
+ },
1001
+ { role: "assistant", content: reply.text },
1002
+ );
1003
+ await asker.emit({ type: "answered", question, ...reply });
1004
+ exchanges.push({ question, answer: reply.text });
1005
+ return reply.text;
1006
+ });
1007
+ try {
1008
+ const outcome = await runBotToText(
1009
+ {
1010
+ bot,
1011
+ request,
1012
+ context,
1013
+ askedBy: asker.name,
1014
+ chain: asker.chain,
1015
+ above: [...asker.above, asker.name],
1016
+ },
1017
+ {
1018
+ ...asker.options,
1019
+ signal,
1020
+ depth: asker.depth + 1,
1021
+ parent: call.toolCallId,
1022
+ session,
1023
+ // Only the task's owner drains words sent by the user.
1024
+ notes: undefined,
1025
+ answer,
1026
+ },
1027
+ );
1028
+ return exchanges.length
1029
+ ? `${outcome}\n\n${exchangeLines(bot, exchanges)}`
1030
+ : outcome;
1031
+ } catch (cause) {
1032
+ signal?.throwIfAborted();
1033
+ publicError(
1034
+ [modelErrorToString(cause), exchangeLines(bot, exchanges)]
1035
+ .filter(Boolean)
1036
+ .join("\n\n"),
1037
+ );
1038
+ }
1039
+ });
978
1040
  },
979
1041
  }),
980
1042
  };
981
1043
  return agentTools;
982
1044
  }
983
1045
 
1046
+ /** One execution per participant, with wait-cycle detection across concurrent branches. */
1047
+ export function createBotDesks() {
1048
+ const lock = createKeyedLock();
1049
+ const waiting = new Map<string, Map<string, number>>();
1050
+ const pending = new Set<Promise<unknown>>();
1051
+ const reaches = (
1052
+ from: string,
1053
+ to: string,
1054
+ seen = new Set<string>(),
1055
+ ): boolean => {
1056
+ if (from === to) return true;
1057
+ if (seen.has(from)) return false;
1058
+ seen.add(from);
1059
+ return [...(waiting.get(from)?.keys() ?? [])].some((next) =>
1060
+ reaches(next, to, seen),
1061
+ );
1062
+ };
1063
+ return {
1064
+ async run<T>(
1065
+ from: string,
1066
+ to: string,
1067
+ signal: AbortSignal | undefined,
1068
+ work: () => Promise<T>,
1069
+ ): Promise<T> {
1070
+ signal?.throwIfAborted();
1071
+ if (reaches(to, from))
1072
+ publicError(
1073
+ `${to} is waiting on your work. Finish this part before asking them, or choose another bot.`,
1074
+ );
1075
+ const edges = waiting.get(from) ?? new Map<string, number>();
1076
+ waiting.set(from, edges);
1077
+ edges.set(to, (edges.get(to) ?? 0) + 1);
1078
+ const run = lock(to, async () => {
1079
+ signal?.throwIfAborted();
1080
+ return work();
1081
+ });
1082
+ pending.add(run);
1083
+ try {
1084
+ return await run;
1085
+ } finally {
1086
+ pending.delete(run);
1087
+ const left = (edges.get(to) ?? 1) - 1;
1088
+ if (left) edges.set(to, left);
1089
+ else edges.delete(to);
1090
+ if (!edges.size) waiting.delete(from);
1091
+ }
1092
+ },
1093
+ async settle() {
1094
+ while (pending.size) await Promise.allSettled([...pending]);
1095
+ },
1096
+ };
1097
+ }
1098
+
984
1099
  /** Appended under the answer: what the borrowed bot asked and what this bot answered. */
985
1100
  function exchangeLines(
986
1101
  bot: string,