thursday-agent 0.2.0 → 0.4.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 (541) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +5 -2
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +5 -5
  5. package/.next/routes-manifest.json +24 -6
  6. package/.next/server/app/_global-error/page.js +2 -2
  7. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/server/app/_global-error.html +1 -1
  10. package/.next/server/app/_global-error.rsc +2 -2
  11. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  13. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/server/app/_not-found/page.js +2 -2
  15. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  16. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next/server/app/_not-found.html +1 -1
  18. package/.next/server/app/_not-found.rsc +14 -14
  19. package/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  20. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
  21. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/.next/server/app/api/artifact/route.js +3 -3
  23. package/.next/server/app/api/artifact/route.js.nft.json +1 -1
  24. package/.next/server/app/api/bot/memory/files/route/app-paths-manifest.json +3 -0
  25. package/.next/server/app/api/bot/memory/files/route.js +10 -0
  26. package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -0
  27. package/.next/server/app/api/bot/memory/files/route_client-reference-manifest.js +3 -0
  28. package/.next/server/app/api/bot/memory/route/app-paths-manifest.json +3 -0
  29. package/.next/server/app/api/bot/memory/route.js +14 -0
  30. package/.next/server/app/api/bot/{notes → memory}/route.js.nft.json +1 -1
  31. package/.next/server/app/api/bot/memory/route_client-reference-manifest.js +3 -0
  32. package/.next/server/app/api/bot/route.js +6 -6
  33. package/.next/server/app/api/bot/route.js.nft.json +1 -1
  34. package/.next/server/app/api/bot/task/[id]/route.js +8 -8
  35. package/.next/server/app/api/bot/task/[id]/route.js.nft.json +1 -1
  36. package/.next/server/app/api/bot/task/route.js +8 -8
  37. package/.next/server/app/api/bot/task/route.js.nft.json +1 -1
  38. package/.next/server/app/api/config/route.js +5 -5
  39. package/.next/server/app/api/config/route.js.nft.json +1 -1
  40. package/.next/server/app/api/events/route.js +4 -5
  41. package/.next/server/app/api/events/route.js.nft.json +1 -1
  42. package/.next/server/app/api/file/[...path]/route.js +3 -3
  43. package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
  44. package/.next/server/app/api/llm-model/catalog/route.js +9 -8
  45. package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
  46. package/.next/server/app/api/llm-model/credits/route/app-paths-manifest.json +3 -0
  47. package/.next/server/app/api/llm-model/credits/route/build-manifest.json +12 -0
  48. package/.next/server/app/api/llm-model/credits/route/server-reference-manifest.json +4 -0
  49. package/.next/server/app/api/llm-model/credits/route.js +16 -0
  50. package/.next/server/app/api/llm-model/credits/route.js.map +5 -0
  51. package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -0
  52. package/.next/server/app/api/llm-model/credits/route_client-reference-manifest.js +3 -0
  53. package/.next/server/app/api/llm-model/route.js +10 -8
  54. package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
  55. package/.next/server/app/api/llm-model/subscription/route/app-paths-manifest.json +3 -0
  56. package/.next/server/app/api/llm-model/subscription/route/build-manifest.json +12 -0
  57. package/.next/server/app/api/llm-model/subscription/route/server-reference-manifest.json +4 -0
  58. package/.next/server/app/api/llm-model/subscription/route.js +15 -0
  59. package/.next/server/app/api/llm-model/subscription/route.js.map +5 -0
  60. package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -0
  61. package/.next/server/app/api/llm-model/subscription/route_client-reference-manifest.js +3 -0
  62. package/.next/server/app/api/mcp/[name]/route.js +4 -4
  63. package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
  64. package/.next/server/app/api/mcp/oauth/callback/route.js +5 -5
  65. package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
  66. package/.next/server/app/api/mcp/route.js +4 -4
  67. package/.next/server/app/api/mcp/route.js.nft.json +1 -1
  68. package/.next/server/app/api/mcp/tools/route.js +4 -4
  69. package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
  70. package/.next/server/app/api/memory/edit/route/app-paths-manifest.json +3 -0
  71. package/.next/server/app/api/memory/edit/route/build-manifest.json +12 -0
  72. package/.next/server/app/api/memory/edit/route/server-reference-manifest.json +4 -0
  73. package/.next/server/app/api/memory/edit/route.js +24 -0
  74. package/.next/server/app/api/memory/edit/route.js.map +5 -0
  75. package/.next/server/app/api/memory/edit/route.js.nft.json +1 -0
  76. package/.next/server/app/api/memory/{tidy → edit}/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/memory/route.js +6 -7
  78. package/.next/server/app/api/memory/route.js.nft.json +1 -1
  79. package/.next/server/app/api/skills/[source]/[name]/route.js +3 -3
  80. package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
  81. package/.next/server/app/api/skills/route.js +3 -3
  82. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  83. package/.next/server/app/api/thursday/call/route.js +9 -7
  84. package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
  85. package/.next/server/app/api/thursday/skills/route.js +9 -7
  86. package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
  87. package/.next/server/app/api/thursday/tool-call/route.js +17 -15
  88. package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
  89. package/.next/server/app/api/workspace/route.js +4 -5
  90. package/.next/server/app/api/workspace/route.js.nft.json +1 -1
  91. package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
  92. package/.next/server/app/artifact/[...path]/page.js +5 -6
  93. package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
  94. package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
  95. package/.next/server/app/favicon.ico/route.js +3 -4
  96. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  97. package/.next/server/app/favicon.ico.body +0 -0
  98. package/.next/server/app/page/server-reference-manifest.json +57 -90
  99. package/.next/server/app/page.js +10 -10
  100. package/.next/server/app/page.js.nft.json +1 -1
  101. package/.next/server/app/page_client-reference-manifest.js +1 -1
  102. package/.next/server/app-paths-manifest.json +5 -2
  103. package/.next/server/chunks/[externals]__0dzuk6_._.js +3 -0
  104. package/.next/server/chunks/[root-of-the-server]__01dkp4a._.js +3 -0
  105. package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +1 -1
  106. package/.next/server/chunks/[root-of-the-server]__02xjoto._.js +3 -0
  107. package/.next/server/chunks/[root-of-the-server]__04q--0f._.js +8 -0
  108. package/.next/server/chunks/[root-of-the-server]__0bl-e63._.js +3 -0
  109. package/.next/server/chunks/[root-of-the-server]__0ce8nf8._.js +5 -0
  110. package/.next/server/chunks/[root-of-the-server]__0ffbg85._.js +3 -0
  111. package/.next/server/chunks/[root-of-the-server]__0j7z0r3._.js +12 -0
  112. package/.next/server/chunks/[root-of-the-server]__0jq4ohy._.js +8 -0
  113. package/.next/server/chunks/[root-of-the-server]__0ls6_8y._.js +8 -0
  114. package/.next/server/chunks/[root-of-the-server]__0ncs9uu._.js +3 -0
  115. package/.next/server/chunks/[root-of-the-server]__0pa2n68._.js +3 -0
  116. package/.next/server/chunks/[root-of-the-server]__0q0vvt2._.js +5 -0
  117. package/.next/server/chunks/[root-of-the-server]__1-ga0kh._.js +5 -0
  118. package/.next/server/chunks/[root-of-the-server]__11n256_._.js +5 -0
  119. package/.next/server/chunks/[root-of-the-server]__12cabg9._.js +3 -0
  120. package/.next/server/chunks/[root-of-the-server]__1339jnv._.js +8 -0
  121. package/.next/server/chunks/[root-of-the-server]__15bjnu5._.js +5 -0
  122. package/.next/server/chunks/[root-of-the-server]__18_teo8._.js +8 -0
  123. package/.next/server/chunks/[root-of-the-server]__1_04mcf._.js +5 -0
  124. package/.next/server/chunks/[root-of-the-server]__1anc1p9._.js +3 -0
  125. package/.next/server/chunks/[root-of-the-server]__1ccon1w._.js +5 -5
  126. package/.next/server/chunks/[root-of-the-server]__1fizn31._.js +3 -0
  127. package/.next/server/chunks/[root-of-the-server]__1llj3-b._.js +3 -0
  128. package/.next/server/chunks/[root-of-the-server]__1orn-5b._.js +12 -0
  129. package/.next/server/chunks/[root-of-the-server]__1p8m3f4._.js +3 -0
  130. package/.next/server/chunks/[root-of-the-server]__1wa1k7r._.js +8 -0
  131. package/.next/server/chunks/_0-73of8._.js +5 -5
  132. package/.next/server/chunks/_0-fjql4._.js +26 -0
  133. package/.next/server/chunks/_00xh_q0._.js +25 -0
  134. package/.next/server/chunks/_051qh8u._.js +3 -0
  135. package/.next/server/chunks/{node_modules_next_04q-o58._.js → _05dla15._.js} +2 -2
  136. package/.next/server/chunks/_05h2cii._.js +1 -1
  137. package/.next/server/chunks/_0c9j6rb._.js +49 -0
  138. package/.next/server/chunks/_0fzqt2y._.js +134 -0
  139. package/.next/server/chunks/{_0vnutv9._.js → _0ioz3nd._.js} +1 -1
  140. package/.next/server/chunks/_0iwfby5._.js +3 -0
  141. package/.next/server/chunks/_0mecv0f._.js +26 -0
  142. package/.next/server/chunks/_0mh8j4m._.js +3 -0
  143. package/.next/server/chunks/_0n74zsf._.js +4 -4
  144. package/.next/server/chunks/_0obojvv._.js +3 -0
  145. package/.next/server/chunks/_0recufk._.js +49 -0
  146. package/.next/server/chunks/_0rnf-6n._.js +4 -4
  147. package/.next/server/chunks/_0sb7tlz._.js +3 -0
  148. package/.next/server/chunks/_0u10wmw._.js +5 -5
  149. package/.next/server/chunks/_0u4aiy2._.js +3 -0
  150. package/.next/server/chunks/_0wp4le3._.js +1 -1
  151. package/.next/server/chunks/_0y5fg-x._.js +1 -1
  152. package/.next/server/chunks/_1230wbx._.js +10 -0
  153. package/.next/server/chunks/_12nheja._.js +3 -0
  154. package/.next/server/chunks/_13510xh._.js +3 -0
  155. package/.next/server/chunks/_15hq89o._.js +1 -1
  156. package/.next/server/chunks/_17l5ygt._.js +49 -0
  157. package/.next/server/chunks/_18-1wcc._.js +5 -5
  158. package/.next/server/chunks/_1_5mnq4._.js +4 -4
  159. package/.next/server/chunks/_1_9mjx-._.js +3 -0
  160. package/.next/server/chunks/{_0pppbb5._.js → _1c78vw5._.js} +166 -37
  161. package/.next/server/chunks/_1d0j961._.js +6 -0
  162. package/.next/server/chunks/_1d5zmy8._.js +1 -1
  163. package/.next/server/chunks/_1drgfx4._.js +4 -4
  164. package/.next/server/chunks/_1f46eie._.js +3 -0
  165. package/.next/server/chunks/_1h5_8sk._.js +3 -0
  166. package/.next/server/chunks/_1hdnxb7._.js +3 -0
  167. package/.next/server/chunks/_1j8k_j5._.js +3 -0
  168. package/.next/server/chunks/_1krd9rf._.js +3 -0
  169. package/.next/server/chunks/_1nc0eui._.js +11 -0
  170. package/.next/server/chunks/_1nula3v._.js +3 -0
  171. package/.next/server/chunks/_1pf4qe-._.js +3 -0
  172. package/.next/server/chunks/_1php4eq._.js +1 -1
  173. package/.next/server/chunks/_1qxheoa._.js +3 -0
  174. package/.next/server/chunks/_1x6kd1r._.js +3 -0
  175. package/.next/server/chunks/_1yuv4vp._.js +1 -1
  176. package/.next/server/chunks/_1zjrdag._.js +3 -0
  177. package/.next/server/chunks/_next-internal_server_app_api_bot_memory_files_route_actions_1_xd_8f.js +3 -0
  178. package/.next/server/chunks/_next-internal_server_app_api_bot_memory_route_actions_1a8xfr7.js +3 -0
  179. package/.next/server/chunks/_next-internal_server_app_api_llm-model_credits_route_actions_0v424pb.js +3 -0
  180. package/.next/server/chunks/_next-internal_server_app_api_llm-model_subscription_route_actions_0-udcm5.js +3 -0
  181. package/.next/server/chunks/_next-internal_server_app_api_memory_edit_route_actions_02sfnmr.js +3 -0
  182. package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
  183. package/.next/server/chunks/features_0rpfxbk._.js +3 -0
  184. package/.next/server/chunks/features_1qf3cei._.js +3 -0
  185. package/.next/server/chunks/features_bot_0zt92oh._.js +3 -0
  186. package/.next/server/chunks/features_bot_bot_query_ts_12mgkl2._.js +3 -0
  187. package/.next/server/chunks/features_memory_1uqvksn._.js +6 -0
  188. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0a1rd09._.js → node_modules_consola_dist_chunks_prompt_mjs_0i7cvml._.js} +1 -1
  189. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_06067w6._.js → node_modules_consola_dist_chunks_prompt_mjs_0icqaqw._.js} +1 -1
  190. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_096ais_._.js → node_modules_consola_dist_chunks_prompt_mjs_0r28-sm._.js} +1 -1
  191. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_02s3hjt._.js → node_modules_consola_dist_chunks_prompt_mjs_0v_7drq._.js} +1 -1
  192. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1xoq2rl._.js +3 -0
  193. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0tvkfgh._.js → node_modules_consola_dist_chunks_prompt_mjs_1y49572._.js} +1 -1
  194. package/.next/server/chunks/ssr/[root-of-the-server]__092knlj._.js +3 -0
  195. package/.next/server/chunks/ssr/[root-of-the-server]__09q9med._.js +1 -1
  196. package/.next/server/chunks/ssr/[root-of-the-server]__0hg5gib._.js +1 -1
  197. package/.next/server/chunks/ssr/[root-of-the-server]__0i8ie7f._.js +3 -0
  198. package/.next/server/chunks/ssr/{[root-of-the-server]__0jsmmb-._.js → [root-of-the-server]__0mz33a7._.js} +1 -1
  199. package/.next/server/chunks/ssr/[root-of-the-server]__0s9dq5u._.js +3 -0
  200. package/.next/server/chunks/ssr/{[root-of-the-server]__0m1b5ky._.js → [root-of-the-server]__0s9l2sv._.js} +1 -1
  201. package/.next/server/chunks/ssr/[root-of-the-server]__0tbkdlx._.js +3 -0
  202. package/.next/server/chunks/ssr/[root-of-the-server]__0vnmv-s._.js +3 -0
  203. package/.next/server/chunks/ssr/{[root-of-the-server]__0f6s48x._.js → [root-of-the-server]__0xpcv9w._.js} +3 -3
  204. package/.next/server/chunks/ssr/[root-of-the-server]__0xsog8n._.js +3 -0
  205. package/.next/server/chunks/ssr/[root-of-the-server]__0xtssj3._.js +1 -1
  206. package/.next/server/chunks/ssr/[root-of-the-server]__10-dz5o._.js +61 -0
  207. package/.next/server/chunks/ssr/[root-of-the-server]__15_uhip._.js +3 -0
  208. package/.next/server/chunks/ssr/[root-of-the-server]__16jc196._.js +1 -1
  209. package/.next/server/chunks/ssr/[root-of-the-server]__1gtlw6v._.js +43 -0
  210. package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +1 -1
  211. package/.next/server/chunks/ssr/[root-of-the-server]__1ox7hbk._.js +4 -0
  212. package/.next/server/chunks/ssr/{[root-of-the-server]__1qfqvmg._.js → [root-of-the-server]__202p40z._.js} +2 -2
  213. package/.next/server/chunks/ssr/{_1mznrxk._.js → _001_ms1._.js} +3 -3
  214. package/.next/server/chunks/ssr/_01n3o0m._.js +3 -0
  215. package/.next/server/chunks/ssr/_0ap2fzn._.js +3 -0
  216. package/.next/server/chunks/ssr/_0fz4mf_._.js +3 -0
  217. package/.next/server/chunks/ssr/_0gb4gmf._.js +3 -0
  218. package/.next/server/chunks/ssr/_0h42fze._.js +3 -0
  219. package/.next/server/chunks/ssr/_0h7zdni._.js +1 -1
  220. package/.next/server/chunks/ssr/_0i_6q1w._.js +41 -0
  221. package/.next/server/chunks/ssr/_0ilna52._.js +42 -0
  222. package/.next/server/chunks/ssr/_0n3toaw._.js +3 -0
  223. package/.next/server/chunks/ssr/{_13punpm._.js → _0q0zan6._.js} +2 -2
  224. package/.next/server/chunks/ssr/{_077-g6s._.js → _0s99bgb._.js} +3 -3
  225. package/.next/server/chunks/ssr/_0zcjrbg._.js +3 -0
  226. package/.next/server/chunks/ssr/_0zhoc3r._.js +3 -0
  227. package/.next/server/chunks/ssr/{_1lip4th._.js → _1-_etts._.js} +2 -2
  228. package/.next/server/chunks/ssr/_1-teee5._.js +3 -3
  229. package/.next/server/chunks/ssr/_13fke0x._.js +3 -0
  230. package/.next/server/chunks/ssr/_15j2_8f._.js +3 -0
  231. package/.next/server/chunks/ssr/_1b2mjwr._.js +3 -0
  232. package/.next/server/chunks/ssr/{_09ja_w1._.js → _1f-dg-6._.js} +2 -2
  233. package/.next/server/chunks/ssr/_1fojd41._.js +4 -0
  234. package/.next/server/chunks/ssr/_1gd8q8l._.js +3 -0
  235. package/.next/server/chunks/ssr/{_1m3bsfr._.js → _1h0rw11._.js} +2 -2
  236. package/.next/server/chunks/ssr/_1jhd09t._.js +3 -0
  237. package/.next/server/chunks/ssr/{_040sk32._.js → _1lz_cq2._.js} +84 -88
  238. package/.next/server/chunks/ssr/_1nnsk7z._.js +3 -0
  239. package/.next/server/chunks/ssr/_1sji22n._.js +3 -0
  240. package/.next/server/chunks/ssr/{_0x_nu5e._.js → _1vi_uo7._.js} +2 -2
  241. package/.next/server/chunks/ssr/_1vyd1ji._.js +1 -1
  242. package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +3 -0
  243. package/.next/server/chunks/ssr/features_config_components_config-setting_tsx_0xfwqdg._.js +1 -1
  244. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
  245. package/.next/server/chunks/ssr/{node_modules_0kxz4gt._.js → node_modules_00etblx._.js} +1 -1
  246. package/.next/server/chunks/ssr/{node_modules_18_dk7_._.js → node_modules_04jf_m8._.js} +1 -1
  247. package/.next/server/chunks/ssr/{node_modules_0_n241r._.js → node_modules_08dhkiq._.js} +1 -1
  248. package/.next/server/chunks/ssr/{node_modules_0n1kj4p._.js → node_modules_0940ns5._.js} +1 -1
  249. package/.next/server/chunks/ssr/{node_modules_17lrt3y._.js → node_modules_09q35v_._.js} +1 -1
  250. package/.next/server/chunks/ssr/{node_modules_1toafc-._.js → node_modules_0_-_a--._.js} +1 -1
  251. package/.next/server/chunks/ssr/{node_modules_0lrjk5t._.js → node_modules_0arus10._.js} +1 -1
  252. package/.next/server/chunks/ssr/{node_modules_04n7mjs._.js → node_modules_0b__sgl._.js} +1 -1
  253. package/.next/server/chunks/ssr/{node_modules_0iz1rzd._.js → node_modules_0cxaxk9._.js} +1 -1
  254. package/.next/server/chunks/ssr/{node_modules_0ndvxub._.js → node_modules_0g7fzk3._.js} +1 -1
  255. package/.next/server/chunks/ssr/{node_modules_0kl7-tn._.js → node_modules_0had7xd._.js} +1 -1
  256. package/.next/server/chunks/ssr/{node_modules_19dp7ko._.js → node_modules_0pvd7q-._.js} +1 -1
  257. package/.next/server/chunks/ssr/node_modules_0x3-ply._.js +3 -0
  258. package/.next/server/chunks/ssr/{node_modules_1dwguw_._.js → node_modules_0yugy6m._.js} +1 -1
  259. package/.next/server/chunks/ssr/{node_modules_004qlr4._.js → node_modules_14om4ar._.js} +1 -1
  260. package/.next/server/chunks/ssr/{node_modules_1q0p85z._.js → node_modules_14q6g4n._.js} +1 -1
  261. package/.next/server/chunks/ssr/node_modules_17bwo43._.js +55 -0
  262. package/.next/server/chunks/ssr/{node_modules_1hd-5tv._.js → node_modules_198nmll._.js} +1 -1
  263. package/.next/server/chunks/ssr/{node_modules_0m93zeg._.js → node_modules_1_pc9kq._.js} +1 -1
  264. package/.next/server/chunks/ssr/{node_modules_1n5ygqe._.js → node_modules_1aaxuef._.js} +1 -1
  265. package/.next/server/chunks/ssr/{node_modules_01qnv-a._.js → node_modules_1cx_ql6._.js} +1 -1
  266. package/.next/server/chunks/ssr/{node_modules_1zu988w._.js → node_modules_1cz2hle._.js} +1 -1
  267. package/.next/server/chunks/ssr/{node_modules_1xyd7ov._.js → node_modules_1efrqxi._.js} +1 -1
  268. package/.next/server/chunks/ssr/{node_modules_13r44jq._.js → node_modules_1eg7m6c._.js} +1 -1
  269. package/.next/server/chunks/ssr/{node_modules_08d8bke._.js → node_modules_1gdh31p._.js} +1 -1
  270. package/.next/server/chunks/ssr/{node_modules_1hn8z1f._.js → node_modules_1hpopmo._.js} +1 -1
  271. package/.next/server/chunks/ssr/{node_modules_20yebtj._.js → node_modules_1qpjvy5._.js} +1 -1
  272. package/.next/server/chunks/ssr/{node_modules_09xhur7._.js → node_modules_1vh5ntv._.js} +1 -1
  273. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0m9a4y0._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1b748ic._.js} +1 -1
  274. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_13w_wy9._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1z4hm5e._.js} +1 -1
  275. package/.next/server/chunks/ssr/node_modules_@vercel_oidc_dist_06mk8id._.js +3 -0
  276. package/.next/server/chunks/ssr/{node_modules_next_dist_0w6mzq5._.js → node_modules_next_dist_0drixxt._.js} +4 -4
  277. package/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +2 -2
  278. package/.next/server/instrumentation.js +1 -1
  279. package/.next/server/middleware-build-manifest.js +3 -3
  280. package/.next/server/pages/404.html +1 -1
  281. package/.next/server/pages/500.html +1 -1
  282. package/.next/server/server-reference-manifest.js +1 -1
  283. package/.next/server/server-reference-manifest.json +58 -91
  284. package/.next/static/chunks/00rd4-v8r7axp.js +1 -0
  285. package/.next/static/chunks/08qe4as2q428z.js +3 -0
  286. package/.next/static/chunks/{31ttnjt6g35a0.js → 0ev4hyghq0mbb.js} +1 -1
  287. package/.next/static/chunks/0fl3gwe4-l6tz.js +1 -0
  288. package/.next/static/chunks/0gnqdjn2lvdad.js +2 -0
  289. package/.next/static/chunks/0hwap1-p0m5sa.js +1 -0
  290. package/.next/static/chunks/0r5qcctujhxli.js +1 -0
  291. package/.next/static/chunks/0w52rrrmuj9r9.js +1 -0
  292. package/.next/static/chunks/{07kl352c-lvcz.js → 0wcprt5ot5she.js} +1 -1
  293. package/.next/static/chunks/10teww7wt-f6h.js +1 -0
  294. package/.next/static/chunks/15m575979un5f.js +38 -0
  295. package/.next/static/chunks/17p6tk6k88dq6.js +1 -0
  296. package/.next/static/chunks/{3_h9txt3tq8t8.js → 1dkjg87pxk0n9.js} +1 -1
  297. package/.next/static/chunks/1jd09sxcjddu1.js +1 -0
  298. package/.next/static/chunks/1kq8h8wi_e0bc.js +53 -0
  299. package/.next/static/chunks/{18vhba44l1fc9.js → 1w73ece_6bheu.js} +1 -1
  300. package/.next/static/chunks/{3ard_noqapnw1.js → 214ya3n5gxeih.js} +1 -1
  301. package/.next/static/chunks/22_zkas21rpna.js +1 -0
  302. package/.next/static/chunks/2h0sd2vumhb15.js +1 -0
  303. package/.next/static/chunks/2t4vhi3m8ahya.js +1 -0
  304. package/.next/static/chunks/2tigdtz3dpcy8.js +1 -0
  305. package/.next/static/chunks/2u7xt6mxg44lf.js +1 -0
  306. package/.next/static/chunks/39j0fvpybzabl.js +40 -0
  307. package/.next/static/chunks/3akmqm77muj4-.js +1 -0
  308. package/.next/static/chunks/3cq5g494yuke8.css +1 -0
  309. package/.next/static/chunks/3woj-xd7ki5_i.js +1 -0
  310. package/.next/static/chunks/3xk7yqgomx7yq.js +1 -0
  311. package/.next/static/chunks/3zmcsnuo25zvq.js +1 -0
  312. package/.next/static/media/favicon.37o1pjldwi2uc.ico +0 -0
  313. package/README.md +47 -22
  314. package/app/api/bot/memory/files/route.ts +14 -0
  315. package/app/api/bot/memory/route.ts +5 -0
  316. package/app/api/events/app-event.ts +3 -7
  317. package/app/api/llm-model/credits/route.ts +11 -0
  318. package/app/api/llm-model/route.ts +6 -1
  319. package/app/api/llm-model/subscription/route.ts +11 -0
  320. package/app/api/memory/edit/route.ts +7 -0
  321. package/app/api/memory/route.ts +21 -0
  322. package/app/api/query-key.ts +19 -4
  323. package/bin/port.mjs +65 -0
  324. package/bin/thursday.mjs +5 -33
  325. package/components/ui/shiny-text.tsx +48 -134
  326. package/database/migrations/{20260909060813_optimal_sabretooth → 20260910173049_numerous_spirit}/migration.sql +24 -34
  327. package/database/migrations/{20260909060813_optimal_sabretooth → 20260910173049_numerous_spirit}/snapshot.json +71 -145
  328. package/database/migrations/20260911030320_opposite_jack_power/migration.sql +1 -0
  329. package/database/migrations/{20260909063821_bitter_tenebrous → 20260911030320_opposite_jack_power}/snapshot.json +57 -184
  330. package/docs/how-it-works.md +11 -7
  331. package/docs/images/face-dark.png +0 -0
  332. package/docs/images/face-light.png +0 -0
  333. package/docs/images/keeps-going.png +0 -0
  334. package/features/ai/chatgpt.ts +639 -0
  335. package/features/ai/components/chatgpt-sign-in.tsx +66 -0
  336. package/features/ai/components/model-picker.tsx +17 -1
  337. package/features/ai/components/provider-icon.tsx +1 -0
  338. package/features/ai/load-tools.ts +78 -56
  339. package/features/ai/model.schema.ts +232 -34
  340. package/features/ai/model.ts +169 -24
  341. package/features/ai/prompts/bot.prompt.ts +207 -114
  342. package/features/ai/prompts/memory-edit.prompt.ts +33 -0
  343. package/features/ai/prompts/prompt-helper.ts +45 -26
  344. package/features/ai/prompts/thursday.prompt.ts +57 -27
  345. package/features/ai/tools/bot.tool.ts +35 -52
  346. package/features/ai/tools/connected.ts +17 -4
  347. package/features/ai/tools/memory.tool.ts +181 -117
  348. package/features/ai/tools/search.tool.ts +136 -47
  349. package/features/ai/tools/tool-name.ts +2 -6
  350. package/features/ai/tools/workspace.tool.ts +5 -1
  351. package/features/bot/bot.action.ts +14 -19
  352. package/features/bot/bot.memory.ts +89 -0
  353. package/features/bot/bot.query.ts +37 -57
  354. package/features/bot/bot.run.ts +512 -242
  355. package/features/bot/bot.runner.ts +434 -120
  356. package/features/bot/bot.schema.ts +104 -19
  357. package/features/bot/bot.seed.ts +89 -16
  358. package/features/bot/components/bot-mark.tsx +122 -20
  359. package/features/bot/components/bot-room.tsx +214 -132
  360. package/features/bot/components/bot-setting.tsx +790 -402
  361. package/features/bot/components/bot-tool.tsx +2 -7
  362. package/features/bot/components/mark-palette.tsx +54 -0
  363. package/features/bot/components/path-chips.tsx +1 -1
  364. package/features/bot/components/task-badge.tsx +26 -6
  365. package/features/bot/components/task-reply.tsx +22 -33
  366. package/features/bot/components/task-setting.tsx +20 -17
  367. package/features/bot/task.query.ts +240 -34
  368. package/features/bot/task.store.ts +76 -13
  369. package/features/config/components/config-setting.tsx +265 -34
  370. package/features/config/components/models-badge.tsx +42 -0
  371. package/features/config/config.action.ts +12 -0
  372. package/features/config/config.const.ts +38 -2
  373. package/features/connectors/mcp.manager.ts +10 -2
  374. package/features/intro/components/intro.tsx +47 -46
  375. package/features/memory/components/memory-edit.tsx +340 -0
  376. package/features/memory/components/memory-setting.tsx +75 -77
  377. package/features/memory/memory.action.ts +3 -43
  378. package/features/memory/memory.edit.ts +87 -0
  379. package/features/memory/memory.query.ts +24 -42
  380. package/features/memory/memory.schema.ts +29 -106
  381. package/features/settings/components/settings.tsx +2 -0
  382. package/features/settings/settings.alert.ts +3 -1
  383. package/features/thursday/components/ascii-orb.tsx +106 -90
  384. package/features/thursday/components/boot.tsx +7 -0
  385. package/features/thursday/components/call-log.tsx +219 -92
  386. package/features/thursday/components/face.tsx +6 -4
  387. package/features/thursday/components/tab-state.tsx +97 -0
  388. package/features/thursday/components/thursday-mark.tsx +4 -1
  389. package/features/thursday/components/thursday-setting.tsx +17 -47
  390. package/features/thursday/components/thursday.tsx +208 -98
  391. package/features/thursday/thursday.action.ts +12 -17
  392. package/features/thursday/thursday.query.ts +61 -70
  393. package/features/thursday/thursday.schema.ts +20 -7
  394. package/features/thursday/tool-line.ts +85 -12
  395. package/features/thursday/use-thursday.ts +87 -60
  396. package/features/workspace/components/file-view.tsx +4 -2
  397. package/features/workspace/workspace.ts +110 -26
  398. package/lib/limits.ts +1 -1
  399. package/lib/oauth-page.ts +27 -0
  400. package/lib/realtime/openai.ts +2 -2
  401. package/lib/realtime/realtime.protocol.ts +1 -1
  402. package/lib/realtime/realtime.tap.ts +122 -0
  403. package/lib/sandbox.ts +59 -36
  404. package/package.json +1 -1
  405. package/skills/browser/SKILL.md +55 -34
  406. package/.next/server/app/api/bot/notes/route/app-paths-manifest.json +0 -3
  407. package/.next/server/app/api/bot/notes/route.js +0 -14
  408. package/.next/server/app/api/bot/notes/route_client-reference-manifest.js +0 -3
  409. package/.next/server/app/api/memory/tidy/route/app-paths-manifest.json +0 -3
  410. package/.next/server/app/api/memory/tidy/route.js +0 -22
  411. package/.next/server/app/api/memory/tidy/route.js.nft.json +0 -1
  412. package/.next/server/chunks/0d-b_next_dist_server_app-render_after-task-async-storage_external_0-exn7p.js +0 -3
  413. package/.next/server/chunks/[root-of-the-server]__0-npkh_._.js +0 -3
  414. package/.next/server/chunks/[root-of-the-server]__005var5._.js +0 -3
  415. package/.next/server/chunks/[root-of-the-server]__019dx9f._.js +0 -5
  416. package/.next/server/chunks/[root-of-the-server]__02_2sm-._.js +0 -5
  417. package/.next/server/chunks/[root-of-the-server]__04_8spk._.js +0 -3
  418. package/.next/server/chunks/[root-of-the-server]__05luceg._.js +0 -3
  419. package/.next/server/chunks/[root-of-the-server]__06t77t4._.js +0 -11
  420. package/.next/server/chunks/[root-of-the-server]__0bed6hu._.js +0 -8
  421. package/.next/server/chunks/[root-of-the-server]__0cro2dz._.js +0 -5
  422. package/.next/server/chunks/[root-of-the-server]__0dupqt7._.js +0 -5
  423. package/.next/server/chunks/[root-of-the-server]__0emr6go._.js +0 -3
  424. package/.next/server/chunks/[root-of-the-server]__0le4d-p._.js +0 -3
  425. package/.next/server/chunks/[root-of-the-server]__0m8ywyo._.js +0 -10
  426. package/.next/server/chunks/[root-of-the-server]__0nfuugi._.js +0 -5
  427. package/.next/server/chunks/[root-of-the-server]__0p32jfz._.js +0 -12
  428. package/.next/server/chunks/[root-of-the-server]__0phdgco._.js +0 -8
  429. package/.next/server/chunks/[root-of-the-server]__0wvgrsl._.js +0 -3
  430. package/.next/server/chunks/[root-of-the-server]__10qshki._.js +0 -3
  431. package/.next/server/chunks/[root-of-the-server]__11j52t0._.js +0 -5
  432. package/.next/server/chunks/[root-of-the-server]__1692zrv._.js +0 -3
  433. package/.next/server/chunks/[root-of-the-server]__16thabu._.js +0 -3
  434. package/.next/server/chunks/[root-of-the-server]__173nhwf._.js +0 -8
  435. package/.next/server/chunks/[root-of-the-server]__1_aoyr9._.js +0 -3
  436. package/.next/server/chunks/[root-of-the-server]__1bn73ih._.js +0 -10
  437. package/.next/server/chunks/[root-of-the-server]__1bqhuh9._.js +0 -8
  438. package/.next/server/chunks/[root-of-the-server]__1cqizfq._.js +0 -5
  439. package/.next/server/chunks/[root-of-the-server]__1e-ci13._.js +0 -3
  440. package/.next/server/chunks/[root-of-the-server]__1fboj2j._.js +0 -3
  441. package/.next/server/chunks/[root-of-the-server]__1lr7j9t._.js +0 -5
  442. package/.next/server/chunks/[root-of-the-server]__1oli73c._.js +0 -3
  443. package/.next/server/chunks/[root-of-the-server]__1txedx6._.js +0 -8
  444. package/.next/server/chunks/[root-of-the-server]__1untqv8._.js +0 -5
  445. package/.next/server/chunks/[root-of-the-server]__1zgpz1w._.js +0 -3
  446. package/.next/server/chunks/_00_584r._.js +0 -293
  447. package/.next/server/chunks/_012yhfm._.js +0 -39
  448. package/.next/server/chunks/_06mxlgn._.js +0 -3
  449. package/.next/server/chunks/_07rtlxv._.js +0 -3
  450. package/.next/server/chunks/_0a351g7._.js +0 -6
  451. package/.next/server/chunks/_0kjjmag._.js +0 -3
  452. package/.next/server/chunks/_0nn99o2._.js +0 -3
  453. package/.next/server/chunks/_0zuzmt_._.js +0 -39
  454. package/.next/server/chunks/_106co2o._.js +0 -3
  455. package/.next/server/chunks/_12bbv5i._.js +0 -100
  456. package/.next/server/chunks/_15q1pig._.js +0 -3
  457. package/.next/server/chunks/_19wg5yn._.js +0 -138
  458. package/.next/server/chunks/_1bt95p9._.js +0 -55
  459. package/.next/server/chunks/_1cbk7ms._.js +0 -26
  460. package/.next/server/chunks/_1drv2et._.js +0 -40
  461. package/.next/server/chunks/_1ic3bew._.js +0 -5
  462. package/.next/server/chunks/_1mxn_lb._.js +0 -3
  463. package/.next/server/chunks/_1rysr-r._.js +0 -10
  464. package/.next/server/chunks/_1u3rwbr._.js +0 -3
  465. package/.next/server/chunks/_1x7p319._.js +0 -6
  466. package/.next/server/chunks/_next-internal_server_app_api_bot_notes_route_actions_0qcsyu7.js +0 -3
  467. package/.next/server/chunks/_next-internal_server_app_api_memory_tidy_route_actions_0_cfuom.js +0 -3
  468. package/.next/server/chunks/features_bot_task_query_ts_11xuzl3._.js +0 -3
  469. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0gzvgfv._.js +0 -3
  470. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0vorg6w._.js +0 -3
  471. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1tpvamf._.js +0 -3
  472. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1vmaq9f._.js +0 -3
  473. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1y9-wcr._.js +0 -3
  474. package/.next/server/chunks/ssr/[externals]_node_crypto_1kn79-7._.js +0 -3
  475. package/.next/server/chunks/ssr/[root-of-the-server]__0iuoa-r._.js +0 -3
  476. package/.next/server/chunks/ssr/[root-of-the-server]__0un-erv._.js +0 -4
  477. package/.next/server/chunks/ssr/[root-of-the-server]__1-mnhyd._.js +0 -92
  478. package/.next/server/chunks/ssr/[root-of-the-server]__1bww500._.js +0 -3
  479. package/.next/server/chunks/ssr/[root-of-the-server]__1vcdast._.js +0 -3
  480. package/.next/server/chunks/ssr/[root-of-the-server]__1xwnu-h._.js +0 -3
  481. package/.next/server/chunks/ssr/_0-mc7ro._.js +0 -3
  482. package/.next/server/chunks/ssr/_01p5r6-._.js +0 -3
  483. package/.next/server/chunks/ssr/_03webdr._.js +0 -3
  484. package/.next/server/chunks/ssr/_09-0k3z._.js +0 -3
  485. package/.next/server/chunks/ssr/_0f_b_9s._.js +0 -3
  486. package/.next/server/chunks/ssr/_0fafgvl._.js +0 -3
  487. package/.next/server/chunks/ssr/_0sf1isi._.js +0 -3
  488. package/.next/server/chunks/ssr/_0su6_08._.js +0 -3
  489. package/.next/server/chunks/ssr/_0ti51xc._.js +0 -3
  490. package/.next/server/chunks/ssr/_0w5bgak._.js +0 -3
  491. package/.next/server/chunks/ssr/_0y84qo6._.js +0 -3
  492. package/.next/server/chunks/ssr/_1ciyoo3._.js +0 -3
  493. package/.next/server/chunks/ssr/_1e-dgz0._.js +0 -4
  494. package/.next/server/chunks/ssr/_1fq7zgb._.js +0 -3
  495. package/.next/server/chunks/ssr/_1g6l4uj._.js +0 -3
  496. package/.next/server/chunks/ssr/_1ruzehw._.js +0 -3
  497. package/.next/server/chunks/ssr/features_bot_components_bot-setting_tsx_01ajft6._.js +0 -4
  498. package/.next/server/chunks/ssr/features_config_components_14jqgy4._.js +0 -3
  499. package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +0 -4
  500. package/.next/server/chunks/ssr/node_modules_zod_v4_classic_external_15fmmz3.js +0 -55
  501. package/.next/static/chunks/05b6nx_o3_f_n.js +0 -1
  502. package/.next/static/chunks/0dmns-gi0raa4.js +0 -1
  503. package/.next/static/chunks/0f9sq-7q_yxyl.js +0 -1
  504. package/.next/static/chunks/0oc4_xoia77dg.css +0 -1
  505. package/.next/static/chunks/0pheo8m3a-rzi.js +0 -1
  506. package/.next/static/chunks/19i8h2t-5evjx.js +0 -1
  507. package/.next/static/chunks/1c5fcj473025q.js +0 -1
  508. package/.next/static/chunks/1pi_hbmjw-f-z.js +0 -1
  509. package/.next/static/chunks/1twt6k09vg4np.js +0 -1
  510. package/.next/static/chunks/1ytnip8wdng93.js +0 -1
  511. package/.next/static/chunks/28gotuisldmom.js +0 -1
  512. package/.next/static/chunks/2998p0y7k1c8z.js +0 -3
  513. package/.next/static/chunks/29gyb3nubj51j.js +0 -53
  514. package/.next/static/chunks/2l233xfycjpd8.js +0 -1
  515. package/.next/static/chunks/2p5n7pa3c40t-.js +0 -2
  516. package/.next/static/chunks/2ru1q132af7h7.js +0 -1
  517. package/.next/static/chunks/2t8wqycwg3y04.js +0 -2
  518. package/.next/static/chunks/2uxum7qbyeyxd.js +0 -1
  519. package/.next/static/chunks/32z_ypgtchw48.js +0 -1
  520. package/.next/static/chunks/33pyjb3xd1iez.js +0 -1
  521. package/.next/static/chunks/3omud5xunrk1f.js +0 -1
  522. package/.next/static/chunks/42dnrkbph9e-1.js +0 -1
  523. package/.next/static/media/favicon.2vob68tjqpejf.ico +0 -0
  524. package/database/migrations/20260909063821_bitter_tenebrous/migration.sql +0 -1
  525. package/features/ai/prompts/memory-tidy.prompt.ts +0 -129
  526. package/features/ai/tools/tidy.tool.ts +0 -22
  527. package/features/bot/bot.notes.ts +0 -120
  528. package/features/memory/components/memory-tidy.tsx +0 -395
  529. package/features/memory/components/memory-view.tsx +0 -84
  530. package/features/memory/memory.tidy.ts +0 -332
  531. package/features/memory/tidy.query.ts +0 -135
  532. package/features/thursday/use-call-title.ts +0 -31
  533. /package/.next/server/app/api/bot/{notes → memory/files}/route/build-manifest.json +0 -0
  534. /package/.next/server/app/api/bot/{notes → memory/files}/route/server-reference-manifest.json +0 -0
  535. /package/.next/server/app/api/bot/{notes → memory/files}/route.js.map +0 -0
  536. /package/.next/server/app/api/{memory/tidy → bot/memory}/route/build-manifest.json +0 -0
  537. /package/.next/server/app/api/{memory/tidy → bot/memory}/route/server-reference-manifest.json +0 -0
  538. /package/.next/server/app/api/{memory/tidy → bot/memory}/route.js.map +0 -0
  539. /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → kaTO0UHqF1bMnWgCDik2f}/_buildManifest.js +0 -0
  540. /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → kaTO0UHqF1bMnWgCDik2f}/_clientMiddlewareManifest.js +0 -0
  541. /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → kaTO0UHqF1bMnWgCDik2f}/_ssgManifest.js +0 -0
@@ -1,10 +1,12 @@
1
1
  import {
2
+ type FinishReason,
2
3
  generateText,
3
4
  hasToolCall,
4
5
  type LanguageModel,
5
6
  type LanguageModelUsage,
6
7
  type ModelMessage,
7
8
  type PrepareStepResult,
9
+ pruneMessages,
8
10
  type StepResult,
9
11
  stepCountIs,
10
12
  ToolLoopAgent,
@@ -14,38 +16,50 @@ import {
14
16
  import { BOT_RUN } from "@/config";
15
17
  import { loadTools } from "@/features/ai/load-tools";
16
18
  import {
19
+ compactBudget,
17
20
  getTextModel,
21
+ type ModelFailure,
18
22
  modelErrorToString,
23
+ modelFailureOf,
19
24
  resolveDefaultModel,
20
25
  } from "@/features/ai/model";
21
- import { loadBotPrompt } from "@/features/ai/prompts/bot.prompt";
22
26
  import {
27
+ buildHandoff,
28
+ chainOf,
29
+ loadBotPrompt,
30
+ } from "@/features/ai/prompts/bot.prompt";
31
+ import {
32
+ answerAccepted,
23
33
  askBackSpec,
24
34
  askBotSpec,
25
- reportAccepted,
26
35
  } from "@/features/ai/tools/bot.tool";
27
36
  import { TOOL_NAMES } from "@/features/ai/tools/tool-name";
28
37
  import {
38
+ COMPACT_AT_MIN,
29
39
  type JobBot,
30
40
  NO_TOKENS,
31
41
  type TokenUsage,
32
42
  } from "@/features/bot/bot.schema";
33
43
  import {
34
- closeJobShell,
44
+ closeHiddenBrowser,
45
+ filesOnDisk,
35
46
  openBotFolder,
36
47
  openJobScratch,
37
48
  } from "@/features/workspace/workspace";
38
49
  import { logger } from "@/lib/logger";
39
50
  import { publicError } from "@/lib/public-error";
40
51
  import { estimateTokens } from "@/lib/tokens";
41
- import { clip } from "@/lib/utils";
42
- import { hasNotesRequest, keepNotes } from "./bot.notes";
43
- import { findJobBot, readBotNotesOn } from "./bot.query";
44
- import { findTask, optionsOf, toolLine } from "./task.query";
52
+ import { findJobBot } from "./bot.query";
53
+ import {
54
+ findTask,
55
+ listWrittenPaths,
56
+ optionsOf,
57
+ writtenPathsIn,
58
+ } from "./task.query";
45
59
 
46
60
  /**
47
61
  * The bot loop behind `delegate`: one model with its own prompt, tools and
48
- * context. Only the `report` reaches Thursday; every event goes through
62
+ * context. Only the `answer` reaches Thursday; every event goes through
49
63
  * `emit` and the runner (bot.runner) writes it to the task's thread.
50
64
  */
51
65
 
@@ -55,7 +69,14 @@ export type BotEvent =
55
69
  | { type: "text"; text: string }
56
70
  /** A tool call. `id` is where the result lands. */
57
71
  | { type: "tool"; id: string; name: string; input: unknown }
58
- | { type: "tool-result"; id: string; name: string; output: unknown }
72
+ | {
73
+ type: "tool-result";
74
+ id: string;
75
+ name: string;
76
+ output: unknown;
77
+ /** The tool threw; `output` is what it said. */
78
+ error?: boolean;
79
+ }
59
80
  /**
60
81
  * A step finished; `messages` is what the model sees next time.
61
82
  * `budget` is where this run compacts (config BOT_RUN).
@@ -89,11 +110,23 @@ export type BotEvent =
89
110
  */
90
111
  | { type: "waiting"; id: string | null; question: string; options: string[] }
91
112
  /**
92
- * The only thing that reaches Thursday. `complete` is what the model said
93
- * (bot.tool reportSpec), or false when the step cap forced the report.
113
+ * The only thing that reaches Thursday. `stopped` is the app's word, never the
114
+ * model's: the step cap forced this answer, the answer was refused on the last
115
+ * step, the output limit cut it off, or the run ended with nothing handed back.
116
+ * A stopped job waits to be continued; any other answer ends it.
94
117
  */
95
- | { type: "report"; text: string; complete: boolean }
96
- | { type: "error"; message: string };
118
+ | { type: "answer"; text: string; stopped: boolean }
119
+ /**
120
+ * The run broke. `failure` is whose it is to fix (model.ts modelFailureOf) — a
121
+ * retry's, a compaction's, or a person's — and `budget` where to compact next
122
+ * time, when the model refused the context as too long.
123
+ */
124
+ | {
125
+ type: "error";
126
+ message: string;
127
+ failure: ModelFailure;
128
+ budget?: number;
129
+ };
97
130
 
98
131
  /** The event as the thread sees it: which bot, inside which `ask_bot` call. */
99
132
  export type TaskEvent = BotEvent & { bot: string; parent: string | null };
@@ -103,8 +136,17 @@ export type RunOptions = {
103
136
  depth?: number;
104
137
  /** The `ask_bot` call this run answers; null for the job's own bot. */
105
138
  parent?: string | null;
106
- /** The job this runs inside. The shell pins its browser session to it (workspace.ts). */
139
+ /**
140
+ * The job this runs inside, for every seat on it — a borrowed bot's too. Its row
141
+ * names the one folder they all work in (workspace.ts jobScratch).
142
+ */
107
143
  taskId?: string | null;
144
+ /**
145
+ * The browser session this seat's shell drives (workspace.ts jobShellEnv): the
146
+ * job's own when unset, a borrowed bot's under its call, so two seats never click
147
+ * in one window.
148
+ */
149
+ session?: string | null;
108
150
  /**
109
151
  * Drained once per step boundary; each string goes in front of the model as
110
152
  * a user turn before the next step. Only the job's own bot has one.
@@ -116,18 +158,12 @@ export type RunOptions = {
116
158
  };
117
159
 
118
160
  /**
119
- * Both limits come from config BOT_RUN. At the step cap the last step is
120
- * forced to `report` (lastStep) and the job goes to `waiting`. Past the
121
- * context budget the run compacts (compact) and continues from the summary.
161
+ * At the step cap the last step is forced to `answer` (lastStep) and the job goes
162
+ * to `waiting`. The context budget is the model's own window where that can be
163
+ * known (model.ts compactBudget); past it the run compacts and carries on from
164
+ * the summary.
122
165
  */
123
166
  const MAX_STEPS = BOT_RUN.steps;
124
- const CONTEXT_BUDGET = BOT_RUN.compactAt;
125
-
126
- /** A borrowed bot cannot borrow another. */
127
- const MAX_DEPTH = 1;
128
-
129
- /** `ask_back` calls a borrowed bot gets per part. At zero prepareStep removes the tool instead of having it refuse. */
130
- const MAX_ASK_BACK = 3;
131
167
 
132
168
  /**
133
169
  * Where a run starts: a job's thread (the opening message first time, past
@@ -136,7 +172,16 @@ const MAX_ASK_BACK = 3;
136
172
  */
137
173
  export type RunInput =
138
174
  | { bot: string; messages: ModelMessage[] }
139
- | { bot: string; request: string; context?: string | null; askedBy: string };
175
+ | {
176
+ bot: string;
177
+ request: string;
178
+ context?: string | null;
179
+ askedBy: string;
180
+ /** What the borrowing bot inherited, handed down under the part (bot.prompt buildHandoff). */
181
+ chain: string;
182
+ /** Every bot above this one on the job, the holder first; `askedBy` is the last. */
183
+ above: string[];
184
+ };
140
185
 
141
186
  export async function runBot(
142
187
  input: RunInput,
@@ -153,58 +198,97 @@ export async function runBot(
153
198
  await emit({
154
199
  type: "error",
155
200
  message: `No bot named "${input.bot}". Use a name from the list.`,
201
+ failure: "fatal",
202
+ });
203
+ return;
204
+ }
205
+ // Switched off is not a bot to pick — the backstop for every fresh start
206
+ // (`delegate`, `ask_bot`, the screen). A resume carries `messages` instead and
207
+ // is let through: its thread already exists and the user can still answer it.
208
+ if (bot.disabled && "request" in input) {
209
+ await emit({
210
+ type: "error",
211
+ message: `${bot.name} is switched off. Use a name from the list.`,
212
+ failure: "fatal",
156
213
  });
157
214
  return;
158
215
  }
159
216
 
160
217
  // Tools are built on the model: web search runs on this bot's model (load-tools).
161
218
  const model = await resolveModel(bot);
219
+ // The job's row: the same one for every seat on the job, a borrowed bot's too
220
+ const row = options.taskId ? await findTask(options.taskId) : null;
162
221
  // One folder per job, not per bot: bots borrowed with `ask_bot` work inside
163
222
  // the same job and share its material (workspace.ts jobScratch).
164
223
  const [scratch, own] = await Promise.all([
165
- options.taskId
166
- ? openJobScratch(
167
- options.taskId,
168
- (await findTask(options.taskId))?.label ?? "job",
169
- )
170
- : null,
224
+ options.taskId ? openJobScratch(options.taskId, row?.label ?? "job") : null,
171
225
  openBotFolder(name),
172
226
  ]);
173
227
  const [prompt, tools] = await Promise.all([
174
228
  loadBotPrompt(
175
229
  name,
176
230
  bot.systemPrompt,
177
- "askedBy" in input ? input.askedBy : null,
231
+ "askedBy" in input
232
+ ? {
233
+ askedBy: input.askedBy,
234
+ above: input.above,
235
+ canBorrow: depth < BOT_RUN.depth,
236
+ }
237
+ : null,
178
238
  { scratch, own },
179
239
  ),
180
240
  loadTools({
181
241
  target: "bot",
182
242
  bot: name,
183
- taskId: options.taskId,
243
+ // A borrowed bot drives a browser session of its own; everyone else the job's
244
+ session: options.session ?? options.taskId,
184
245
  model,
185
246
  }),
186
247
  ]);
187
- const budget = CONTEXT_BUDGET;
248
+ // What the owner set, else the model's own window, else the constant (model.ts
249
+ // compactBudget), and never above where this job last fit: a context the model
250
+ // refused as too long lowers the job's own number (bot.runner parkTask). That
251
+ // number is the holder's; a borrowed bot runs on a model of its own.
252
+ const budget = Math.min(
253
+ await compactBudget(model.ref, bot.compactAt),
254
+ ("askedBy" in input ? 0 : row?.contextBudget) || Number.POSITIVE_INFINITY,
255
+ );
188
256
 
189
257
  const history: ModelMessage[] =
190
258
  "messages" in input
191
259
  ? input.messages
192
- : [{ role: "user", content: taskPrompt(input) }];
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
+ ];
193
271
 
194
272
  /** Remaining `ask_back` calls for a borrowed bot; the tool counts it down. */
195
- const asks = { left: MAX_ASK_BACK };
273
+ const asks = { left: BOT_RUN.askBack };
274
+ // A borrowed bot asks back instead of asking Thursday. Any seat above the
275
+ // depth limit may borrow in turn, handing down the chain it was handed.
276
+ const seatTools =
277
+ "askedBy" in input ? withAskBack(tools, asks, options) : tools;
196
278
  const agentTools =
197
- depth >= MAX_DEPTH
198
- ? withAskBack(tools, asks, options)
199
- : withAskBot(tools, {
279
+ depth < BOT_RUN.depth
280
+ ? withAskBot(seatTools, {
200
281
  name,
201
282
  model: model.model,
202
283
  instructions: prompt.text,
203
284
  peers: prompt.peers,
204
285
  depth,
286
+ chain: chainOf(history[0]),
287
+ above: "askedBy" in input ? input.above : [],
205
288
  options,
206
289
  emit,
207
- });
290
+ })
291
+ : seatTools;
208
292
 
209
293
  /**
210
294
  * A compaction decided in `prepareStep` is emitted from the loop at
@@ -218,22 +302,27 @@ export async function runBot(
218
302
  interjections: string[];
219
303
  } = { compact: null, interjections: [] };
220
304
 
305
+ /** Stops the stream when the model goes quiet (config BOT_RUN.silenceMs). */
306
+ const quiet = silenceWatch(BOT_RUN.silenceMs);
307
+ /** Context size the latest step went out at; an overflow shrinks the budget from it. */
308
+ let sent = 0;
309
+
221
310
  const agent = new ToolLoopAgent({
222
311
  model: model.model,
223
312
  instructions: prompt.text,
224
313
  tools: agentTools,
225
- // The loop ends on an accepted report or at the step cap (the last step is
226
- // forced to report). A refused report does not end it. `ask_thursday` has
314
+ // The loop ends on an accepted answer or at the step cap (the last step is
315
+ // forced to answer). A refused answer does not end it. `ask_thursday` has
227
316
  // no execute, but invalid arguments come back as a tool-error and the loop
228
317
  // would go on, so the stop is on the call itself.
229
318
  stopWhen: [
230
319
  stepCountIs(MAX_STEPS),
231
- reportAccepted_,
320
+ answerAccepted_,
232
321
  hasToolCall(TOOL_NAMES.ask_thursday),
233
322
  ],
234
323
  prepareStep: async ({ stepNumber, steps, messages }) => {
235
324
  let step = lastStep(stepNumber);
236
- // Remove `ask_back` once spent. The last step is already narrowed to `report`.
325
+ // Remove `ask_back` once spent. The last step is already narrowed to `answer`.
237
326
  if (!step.activeTools && asks.left <= 0) {
238
327
  step = {
239
328
  ...step,
@@ -246,24 +335,30 @@ export async function runBot(
246
335
  // estimate cannot see; the estimate covers a provider that reports none.
247
336
  const measured = steps.at(-1)?.usage.inputTokens ?? 0;
248
337
  const size = Math.max(measured, sizeOf(messages));
338
+ sent = size;
249
339
  let next = messages;
250
- // Nothing past the opening is nothing to compact
251
- if (size > budget && messages.length >= 2) {
340
+ // The opening survives every compaction, so past it and one summary there is nothing to compact
341
+ if (size > budget && messages.length > 2) {
342
+ // One long call that sends nothing until it is done, and bounds itself
343
+ quiet.hold();
252
344
  const summary = await compact(model.model, agentTools, messages, {
253
345
  signal: options.signal,
254
346
  budget,
255
- });
347
+ instructions: prompt.text,
348
+ }).finally(quiet.release);
256
349
  logger.debug(
257
350
  `compacted ${messages.length} messages at ${size} tokens (budget ${budget})`,
258
351
  );
352
+ // Which files the job has is read off its rows and the disk, never asked of the model
353
+ const text = `${summary.text}${await filesUnder(row?.id ?? null, messages, scratch)}`;
259
354
  pending.compact = {
260
355
  type: "compact",
261
- text: summary.text,
356
+ text,
262
357
  usage: summary.usage,
263
358
  messages: messages.length,
264
359
  tokens: size,
265
360
  };
266
- next = [{ role: "user", content: summary.text }];
361
+ next = [messages[0], { role: "user", content: text }];
267
362
  }
268
363
 
269
364
  // Notes said during the last step go in as user turns after everything
@@ -286,9 +381,15 @@ export async function runBot(
286
381
  // Step messages arrive by callback and the stream by loop, with no ordering
287
382
  // between them: the callback queues, the loop takes at `finish-step`.
288
383
  const steps = stepQueue();
384
+ // The job stopping and the model going quiet end the stream the same way;
385
+ // which of the two it was is read at `abort`
386
+ const stop = options.signal
387
+ ? AbortSignal.any([options.signal, quiet.signal])
388
+ : quiet.signal;
389
+ quiet.touch();
289
390
  const result = await agent.stream({
290
391
  messages: history,
291
- abortSignal: options.signal,
392
+ abortSignal: stop,
292
393
  onStepEnd: (step) =>
293
394
  steps.push({
294
395
  messages: step.response.messages,
@@ -297,21 +398,44 @@ export async function runBot(
297
398
  });
298
399
 
299
400
  let writing = "";
300
- /** The last chunk of prose; used only if the run ends without a report. */
401
+ /** The last chunk of prose; used only if the run ends without an answer. */
301
402
  let last: string | null = null;
302
403
  /** The `ask_thursday` call the loop stopped on. */
303
404
  let asked: { id: string; question: string; options: string[] } | null = null;
304
- /** The report (bot.tool reportSpec). */
305
- let reported: { text: string; complete: boolean } | null = null;
306
- /** What the bot wants changed about its own instructions (bot.tool `notes`); applied after the report. */
307
- let wants: string | null = null;
405
+ /** The answer (bot.tool answerSpec). */
406
+ let answered: { text: string; stopped: boolean } | null = null;
407
+ /** Index of the step being read; `lastStep` narrows the one at MAX_STEPS - 1. */
408
+ let stepAt = -1;
409
+ /** Why the latest step ended; prose cut off at the output limit is not an answer. */
410
+ let finish: FinishReason | null = null;
411
+ /** Tool calls still running: the model is not expected to send anything meanwhile. */
412
+ const working = new Set<string>();
413
+
414
+ /** A break as the runner takes it; an overflow carries where to compact next time. */
415
+ const failed = (cause: unknown): Extract<BotEvent, { type: "error" }> => {
416
+ const failure = modelFailureOf(cause);
417
+ const message = modelErrorToString(cause);
418
+ if (failure !== "overflow") return { type: "error", message, failure };
419
+ const shrunk = Math.floor(
420
+ Math.min(budget, sent || budget) * BOT_RUN.overflowShrink,
421
+ );
422
+ return {
423
+ type: "error",
424
+ message,
425
+ failure,
426
+ budget: Math.max(COMPACT_AT_MIN, shrunk),
427
+ };
428
+ };
308
429
 
309
430
  // An abort ends the stream without a finish; release any waiting take.
310
- options.signal?.addEventListener("abort", () => steps.end(), { once: true });
431
+ stop.addEventListener("abort", () => steps.end(), { once: true });
311
432
  try {
312
433
  for await (const part of result.fullStream) {
434
+ // Anything at all is the model still there
435
+ quiet.touch();
313
436
  switch (part.type) {
314
437
  case "start-step":
438
+ stepAt += 1;
315
439
  if (pending.compact) {
316
440
  await emit(pending.compact);
317
441
  pending.compact = null;
@@ -345,13 +469,22 @@ export async function runBot(
345
469
  question: String(args.question ?? ""),
346
470
  options: optionsOf(args.options),
347
471
  };
348
- } else if (part.toolName === TOOL_NAMES.report) {
349
- const args = part.input as { result?: unknown; complete?: unknown };
350
- reported = {
472
+ } else if (part.toolName === TOOL_NAMES.answer) {
473
+ const args = part.input as { result?: unknown };
474
+ answered = {
351
475
  text: String(args.result ?? "").trim(),
352
- complete: args.complete !== false,
476
+ // The last step offers nothing but `answer`: an answer there is the cap's doing
477
+ stopped: stepAt >= MAX_STEPS - 1,
353
478
  };
354
- wants = notesRequestOf(part.input);
479
+ }
480
+ // A tool at work sends nothing, and bounds itself (bash, connected
481
+ // tools, a borrowed bot's own watch); `ask_thursday` never runs
482
+ if (
483
+ !part.providerExecuted &&
484
+ part.toolName !== TOOL_NAMES.ask_thursday
485
+ ) {
486
+ working.add(part.toolCallId);
487
+ quiet.hold();
355
488
  }
356
489
  await emit({
357
490
  type: "tool",
@@ -363,16 +496,17 @@ export async function runBot(
363
496
  }
364
497
 
365
498
  case "tool-result":
366
- // A refused report is not the ending, unless the step cap ends the run
367
- // on this step; then the text still reaches the user, never as complete.
499
+ if (working.delete(part.toolCallId)) quiet.release();
500
+ // A refused answer is not the ending, unless the step cap ends the run
501
+ // on this step; then the text still reaches the user, as a stop.
368
502
  if (
369
- part.toolName === TOOL_NAMES.report &&
370
- reported &&
371
- !reportAccepted(part.output)
503
+ part.toolName === TOOL_NAMES.answer &&
504
+ answered &&
505
+ !answerAccepted(part.output)
372
506
  ) {
373
- reported = {
374
- text: `${reported.text}\n\n${outputText(part.output)}`,
375
- complete: false,
507
+ answered = {
508
+ text: `${answered.text}\n\n${outputText(part.output)}`,
509
+ stopped: true,
376
510
  };
377
511
  }
378
512
  // A provider-executed tool (web search) lands here too; the step's own
@@ -385,92 +519,109 @@ export async function runBot(
385
519
  });
386
520
  break;
387
521
 
522
+ case "tool-error":
523
+ if (working.delete(part.toolCallId)) quiet.release();
524
+ // The model reads it and carries on; the row shows it now rather than
525
+ // when the step ends
526
+ await emit({
527
+ type: "tool-result",
528
+ id: part.toolCallId,
529
+ name: part.toolName,
530
+ output: modelErrorToString(part.error),
531
+ error: true,
532
+ });
533
+ break;
534
+
388
535
  case "finish-step": {
536
+ finish = part.finishReason;
389
537
  const step = await steps.take();
390
538
  if (step) await emit({ type: "step", ...step, budget });
391
539
  break;
392
540
  }
393
541
 
394
- case "error":
542
+ case "abort":
543
+ // The job was stopped: whoever stopped it writes the row (bot.runner)
544
+ if (options.signal?.aborted) return;
545
+ // Otherwise the model went quiet (silenceWatch)
395
546
  await emit({
396
547
  type: "error",
397
- message: modelErrorToString(part.error),
548
+ message: part.reason ?? "The model stopped sending.",
549
+ failure: "transient",
398
550
  });
399
551
  return;
400
552
 
553
+ case "error":
554
+ await emit(failed(part.error));
555
+ return;
556
+
401
557
  default:
402
558
  break;
403
559
  }
404
560
  }
405
561
  } finally {
406
562
  steps.end();
563
+ quiet.end();
407
564
  }
408
565
 
409
566
  try {
410
567
  await result.response;
411
568
  } catch (cause) {
412
- await emit({ type: "error", message: modelErrorToString(cause) });
569
+ // Stopped between steps: whoever stopped it writes the row
570
+ if (options.signal?.aborted) return;
571
+ await emit(failed(cause));
413
572
  return;
414
573
  }
415
574
 
416
- // Asking wins over reporting: the loop halts on the ask.
575
+ // Asking wins over answering: the loop halts on the ask.
417
576
  if (asked) {
418
577
  await emit({ type: "waiting", ...asked });
419
578
  return;
420
579
  }
421
580
 
422
- if (reported) {
423
- await emit({ type: "report", ...reported });
424
- // After the report, never before, and only when the bot asked for something:
425
- // a job that changed nothing about this machine must not cost a model call.
426
- if (hasNotesRequest(wants) && (await readBotNotesOn())) {
427
- await keepNotes({
428
- bot: name,
429
- model: model.model,
430
- want: wants as string,
431
- signal: options.signal,
432
- });
433
- }
581
+ if (answered) {
582
+ await emit({ type: "answer", ...answered });
583
+ return;
584
+ }
585
+
586
+ // The provider's filter stopped the answer: there is nothing to continue past
587
+ if (finish === "content-filter") {
588
+ await emit({
589
+ type: "error",
590
+ message: "The provider's content filter stopped the response.",
591
+ failure: "fatal",
592
+ });
434
593
  return;
435
594
  }
436
595
 
437
- // The sdk stops on a step with no tool call regardless of `stopWhen`. The
438
- // prose is passed on as a report, never as complete.
596
+ // The sdk stops on a step with no tool call regardless of `stopWhen`. Prose
597
+ // there is an answer given without the tool and ends the job like one;
598
+ // nothing at all, prose cut off at the output limit, or prose on the step the
599
+ // cap narrowed to `answer`, is a stop — continuing picks up where it broke off.
439
600
  await emit({
440
- type: "report",
601
+ type: "answer",
441
602
  text: last ?? "Stopped without handing anything back.",
442
- complete: false,
603
+ stopped: last === null || finish === "length" || stepAt >= MAX_STEPS - 1,
443
604
  });
444
605
  }
445
606
 
446
- /** The last step ended on a report the tool accepted. `hasToolCall` would also stop on a refused one. */
447
- const reportAccepted_ = ({ steps }: { steps: StepResult<ToolSet>[] }) =>
607
+ /** The last step ended on an answer the tool accepted. `hasToolCall` would also stop on a refused one. */
608
+ const answerAccepted_ = ({ steps }: { steps: StepResult<ToolSet>[] }) =>
448
609
  steps
449
610
  .at(-1)
450
611
  ?.toolResults.some(
451
612
  (result) =>
452
- result.toolName === TOOL_NAMES.report && reportAccepted(result.output),
613
+ result.toolName === TOOL_NAMES.answer && answerAccepted(result.output),
453
614
  ) ?? false;
454
615
 
455
- /** A provider that cannot send null in a string field writes the word instead. */
456
- const NO_NOTES = new Set(["null", "none", "n/a", "-", "없음"]);
457
-
458
- /** `report`'s `notes` as the pass takes it. */
459
- function notesRequestOf(input: unknown): string | null {
460
- const said = (input as { notes?: unknown })?.notes;
461
- const text = typeof said === "string" ? said.trim() : "";
462
- return text && !NO_NOTES.has(text.toLowerCase()) ? text : null;
463
- }
464
-
465
616
  /**
466
- * The step before the cap is narrowed to `report` and required to call it,
617
+ * The step before the cap is narrowed to `answer` and required to call it,
467
618
  * so a run out of budget ends with something a person can act on.
468
619
  */
469
620
  function lastStep(stepNumber: number): NonNullable<PrepareStepResult<ToolSet>> {
470
621
  if (stepNumber < MAX_STEPS - 1) return {};
471
622
  return {
472
- activeTools: [TOOL_NAMES.report],
473
- toolChoice: { type: "tool", toolName: TOOL_NAMES.report },
623
+ activeTools: [TOOL_NAMES.answer],
624
+ toolChoice: { type: "tool", toolName: TOOL_NAMES.answer },
474
625
  };
475
626
  }
476
627
 
@@ -492,15 +643,18 @@ function sizeOf(messages: ModelMessage[]): number {
492
643
  /** Instructions for compacting. The summary replaces everything above it, opening included, so it must restate the job. */
493
644
  const compactInstructions = (
494
645
  words: number,
495
- ) => `Compact your context. Everything above is your own work on this job so far; after this message it is replaced by what you write now, and you carry on from that alone. Write what a fresh copy of you needs to continue without re-reading anything:
646
+ ) => `Compact your context. The first message above the job, the call it came from, and anything handed over with it — stays exactly as it is. Everything after it is replaced by what you write now, and you carry on from that first message and this alone; nothing else above can be read again. Write it for a fresh copy of you that has your tools, your instructions and that first message, but has seen none of the rest.
496
647
 
497
- - The job as it was given the request, quoted, and the details from the call that matter.
498
- - What you have done and what you found, with the exact values: numbers, names, urls, dates, ids, error messages.
499
- - Files you wrote or read, by path, and what each holds.
500
- - What is still left to do, in order, and any decision or answer you are waiting on.
501
- - What did not work, so it is not tried again.
648
+ Cover all of it, and prefer a fact over a description of a fact:
502
649
 
503
- Plain text, under ${words} words. No preamble and no headings about the summary itselfstart with the job.`;
650
+ - **Where the job stands against that first message.** Do not restate the job or the call; say what has changed since a choice made, a detail settled, an answer that came back, a correction, anything you are still waiting on.
651
+ - **What is already true.** Every value you have — numbers, names, dates, ids, urls, prices, times — written out, not referred to. A value you leave out is one the next steps go and fetch again.
652
+ - **Where you are.** Which page the browser is on by url, whether you are signed in and to what, what is running, what you were in the middle of. Element refs do not survive this: name what to look for, never a ref.
653
+ - **Files.** Every path you wrote or read, and one line on what each holds.
654
+ - **What did not work**, and why: the url that 404s, the endpoint that needs POST not GET, the selector that is not there, the site that refuses a headless browser, the argument a tool rejected. This is the part that stops the next steps repeating an hour of yours.
655
+ - **What is left**, in the order you would do it.
656
+
657
+ Plain text, under ${words} words. No preamble, no headings about the summary itself, no account of how you have been going.`;
504
658
 
505
659
  /** Word cap for a summary, scaled to the budget it stands in for (config BOT_RUN.summaryWords). */
506
660
  const summaryWords = (budget: number) => {
@@ -508,54 +662,122 @@ const summaryWords = (budget: number) => {
508
662
  return Math.min(max, Math.max(min, Math.round(budget / perTokens)));
509
663
  };
510
664
 
511
- /** Prefix of the first message after a compaction (and on a resume). */
665
+ /** Prefix of the summary that follows the first message after a compaction (and on a resume). */
512
666
  const COMPACT_PREAMBLE =
513
- "The context was compacted here. Below is your own summary of the job up to this point; continue from it.";
667
+ "Everything between the first message and here was compacted. Below is your own summary of it; carry on from the first message and this.";
668
+
669
+ /** Heads the file list under a compaction summary (filesUnder). */
670
+ const FILES_HEAD =
671
+ "Files this job has on disk now — open one rather than fetching again what is in it:";
672
+
673
+ /**
674
+ * The files a job has on disk, as lines under its compaction summary. Read off the
675
+ * job's rows — every `write_file`, its borrowed bots' included — and off its folder,
676
+ * never asked of the model: a path a summary leaves out is work the next steps redo.
677
+ * A borrowed run has no rows of its own and reads the messages it is compacting.
678
+ * A listing that fails costs the list, never the compaction.
679
+ */
680
+ async function filesUnder(
681
+ taskId: string | null,
682
+ messages: ModelMessage[],
683
+ folder: string | null,
684
+ ): Promise<string> {
685
+ try {
686
+ const written = taskId
687
+ ? await listWrittenPaths(taskId)
688
+ : writtenPathsIn(messages.map((message) => message.content));
689
+ const files = await filesOnDisk(written, folder);
690
+ if (!files.length) return "";
691
+ const shown = files.slice(-BOT_RUN.compactFiles);
692
+ const older = files.length - shown.length;
693
+ const lines = shown.map((path) => `- ${path}`);
694
+ if (older) lines.unshift(`- …and ${older} older ones`);
695
+ return `\n\n${FILES_HEAD}\n${lines.join("\n")}`;
696
+ } catch (cause) {
697
+ logger.warn("files under the summary could not be listed", cause);
698
+ return "";
699
+ }
700
+ }
514
701
 
515
702
  /**
516
703
  * The model summarizes its own context and continues from the summary. Tools
517
704
  * are passed with calling off: providers refuse a history of tool calls
518
- * without the tools that made them. A failure throws and the runner closes
519
- * the run as `failed`; the thread stays.
705
+ * without the tools that made them. The run's own instructions go too: the
706
+ * summary is written by the bot the thread belongs to, and the provider's
707
+ * cached prefix, instructions first, still matches the run's. A context too
708
+ * long to summarise whole — the very thing a compaction is for — is tried once
709
+ * more without the tool calls and results but the last few. A failure throws
710
+ * with its cause kept: the runner parks what a retry or a smaller budget can
711
+ * fix (bot.runner parkTask) and fails the rest; the thread stays.
520
712
  */
521
713
  async function compact(
522
714
  model: LanguageModel,
523
715
  tools: ToolSet,
524
716
  messages: ModelMessage[],
525
- options: { signal?: AbortSignal; budget: number },
717
+ options: { signal?: AbortSignal; budget: number; instructions: string },
526
718
  ): Promise<{ text: string; usage: TokenUsage }> {
719
+ let failure: unknown;
527
720
  try {
528
- const { text, usage } = await generateText({
529
- model,
530
- tools,
531
- toolChoice: "none",
532
- messages: [
533
- ...messages,
534
- {
535
- role: "user",
536
- content: compactInstructions(summaryWords(options.budget)),
537
- },
538
- ],
539
- abortSignal: options.signal,
540
- });
541
- const summary = text.trim();
542
- if (summary) {
543
- return {
544
- text: `${COMPACT_PREAMBLE}\n\n${summary}`,
545
- usage: usageOf(usage),
546
- };
547
- }
721
+ return await summarize(model, tools, messages, options);
548
722
  } catch (cause) {
549
723
  if (options.signal?.aborted) throw cause;
550
- throw new Error(
551
- `Could not compact the context (${modelErrorToString(cause)}).`,
552
- );
724
+ failure = cause;
725
+ }
726
+ if (modelFailureOf(failure) === "overflow") {
727
+ // pruneMessages drops a call together with its result, so the thread stays whole
728
+ const lighter = pruneMessages({
729
+ messages,
730
+ toolCalls: "before-last-4-messages",
731
+ emptyMessages: "remove",
732
+ });
733
+ try {
734
+ return await summarize(model, tools, lighter, options);
735
+ } catch (cause) {
736
+ if (options.signal?.aborted) throw cause;
737
+ failure = cause;
738
+ }
553
739
  }
554
740
  throw new Error(
555
- "The summary of the context came back empty, so there is nothing to carry on from.",
741
+ `Could not compact the context (${modelErrorToString(failure)}).`,
742
+ { cause: failure },
556
743
  );
557
744
  }
558
745
 
746
+ /** One summarising call. Nothing streams back, so its whole length is one silence (config BOT_RUN.silenceMs). */
747
+ async function summarize(
748
+ model: LanguageModel,
749
+ tools: ToolSet,
750
+ messages: ModelMessage[],
751
+ options: { signal?: AbortSignal; budget: number; instructions: string },
752
+ ): Promise<{ text: string; usage: TokenUsage }> {
753
+ const { text, usage } = await generateText({
754
+ model,
755
+ system: options.instructions,
756
+ tools,
757
+ toolChoice: "none",
758
+ messages: [
759
+ ...messages,
760
+ {
761
+ role: "user",
762
+ content: compactInstructions(summaryWords(options.budget)),
763
+ },
764
+ ],
765
+ abortSignal: options.signal,
766
+ timeout: BOT_RUN.silenceMs,
767
+ });
768
+ const summary = text.trim();
769
+ if (!summary) {
770
+ // A blank reply is the model's hiccup, not the job's: worth another try
771
+ throw Object.assign(
772
+ new Error(
773
+ "The summary of the context came back empty, so there is nothing to carry on from.",
774
+ ),
775
+ { isRetryable: true },
776
+ );
777
+ }
778
+ return { text: `${COMPACT_PREAMBLE}\n\n${summary}`, usage: usageOf(usage) };
779
+ }
780
+
559
781
  /** Step messages handed from `onStepEnd` to the loop. A take that arrives before the push waits. */
560
782
  type FinishedStep = { messages: ModelMessage[]; usage: TokenUsage };
561
783
 
@@ -588,6 +810,51 @@ function stepQueue() {
588
810
  };
589
811
  }
590
812
 
813
+ /**
814
+ * The clock behind `BOT_RUN.silenceMs`: aborts its signal once nothing has
815
+ * arrived for that long. Held while something other than the model does the
816
+ * work — a tool, a compaction — since those send nothing and bound themselves;
817
+ * `touch` starts it over. Not the sdk's `timeout.chunkMs`: that clock keeps
818
+ * running while tools execute, so a long command or a borrowed bot's whole run
819
+ * would trip it.
820
+ */
821
+ function silenceWatch(ms: number) {
822
+ const stop = new AbortController();
823
+ let timer: ReturnType<typeof setTimeout> | undefined;
824
+ let held = 0;
825
+ let ended = false;
826
+ const arm = () => {
827
+ clearTimeout(timer);
828
+ timer = undefined;
829
+ if (ended || held > 0) return;
830
+ timer = setTimeout(() => {
831
+ stop.abort(
832
+ new DOMException(
833
+ `The model sent nothing for ${Math.round(ms / 60_000)} minutes.`,
834
+ "TimeoutError",
835
+ ),
836
+ );
837
+ }, ms);
838
+ };
839
+ return {
840
+ signal: stop.signal,
841
+ touch: arm,
842
+ hold: () => {
843
+ held += 1;
844
+ arm();
845
+ },
846
+ release: () => {
847
+ held = Math.max(0, held - 1);
848
+ arm();
849
+ },
850
+ /** The run is over; nothing is watched any more. */
851
+ end: () => {
852
+ ended = true;
853
+ arm();
854
+ },
855
+ };
856
+ }
857
+
591
858
  /** The sdk gives undefined when unknown; 0 sums correctly. */
592
859
  const usageOf = (usage: LanguageModelUsage): TokenUsage => ({
593
860
  input: usage.inputTokens ?? 0,
@@ -602,7 +869,7 @@ function resolveModel(bot: JobBot) {
602
869
  return resolveDefaultModel().then(getTextModel);
603
870
  }
604
871
 
605
- /** A whole run as one string; this is all `ask_bot` receives. An incomplete report still comes back, marked as such. */
872
+ /** A whole run as one string; this is all `ask_bot` receives. A stopped run still comes back, marked as such. */
606
873
  export async function runBotToText(
607
874
  input: Extract<RunInput, { request: string }>,
608
875
  options: RunOptions,
@@ -612,10 +879,10 @@ export async function runBotToText(
612
879
  await runBot(input, {
613
880
  ...options,
614
881
  emit: async (event) => {
615
- if (event.type === "report") {
616
- outcome = event.complete
617
- ? event.text
618
- : `${event.text}\n\n(As far as they got — they ran out of steps.)`;
882
+ if (event.type === "answer") {
883
+ outcome = event.stopped
884
+ ? `${event.text}\n\n(As far as they got — they stopped before finishing.)`
885
+ : event.text;
619
886
  }
620
887
  if (event.type === "error") failure = event.message;
621
888
  await options.emit(event);
@@ -642,6 +909,10 @@ function withAskBot(
642
909
  /** Bots it can call; itself excluded (bot.prompt). */
643
910
  peers: string[];
644
911
  depth: number;
912
+ /** The chain this bot inherited, handed down under the part it hands over. */
913
+ chain: string;
914
+ /** Bots above this one on the job, which are blocked waiting on it. */
915
+ above: string[];
645
916
  options: RunOptions;
646
917
  emit: (event: BotEvent) => Promise<void>;
647
918
  },
@@ -654,10 +925,16 @@ function withAskBot(
654
925
  description: askBotSpec.description,
655
926
  inputSchema: askBotSpec.parameters,
656
927
  execute: async ({ bot, request, context }, call) => {
657
- // The borrowed bot gets its own browser session under this call's id,
658
- // closed when the call ends; the runner closes the job's own.
659
- const taskId = asker.options.taskId
660
- ? `${asker.options.taskId}-${call.toolCallId.slice(0, 8)}`
928
+ // A bot above is blocked waiting on this very call; borrowing it back never returns
929
+ if (bot === asker.name || asker.above.includes(bot)) {
930
+ return `${bot} is already on this job, above you and waiting on your part. Pick another bot, or do it yourself.`;
931
+ }
932
+ // The borrowed bot works in the job's folder but drives a browser session
933
+ // of its own, under this call's id. When it answers, the same rule as a
934
+ // job's end: what it showed on their screen stays, what nobody can see
935
+ // closes (workspace.ts closeHiddenBrowser).
936
+ const session = asker.options.taskId
937
+ ? `${asker.options.session ?? asker.options.taskId}-${call.toolCallId.slice(0, 8)}`
661
938
  : null;
662
939
  const exchanges: { question: string; answer: string }[] = [];
663
940
  const answer = async (question: string) => {
@@ -677,12 +954,22 @@ function withAskBot(
677
954
  };
678
955
  try {
679
956
  const outcome = await runBotToText(
680
- { bot, request, context, askedBy: asker.name },
957
+ {
958
+ bot,
959
+ request,
960
+ context,
961
+ askedBy: asker.name,
962
+ chain: asker.chain,
963
+ above: [...asker.above, asker.name],
964
+ },
681
965
  {
682
966
  ...asker.options,
967
+ // This call's signal, not the job's: a stream that ends — the job
968
+ // stopping, or the model going quiet — ends the part it waited on
969
+ signal: call.abortSignal ?? asker.options.signal,
683
970
  depth: asker.depth + 1,
684
971
  parent: call.toolCallId,
685
- taskId,
972
+ session,
686
973
  answer,
687
974
  },
688
975
  );
@@ -690,7 +977,7 @@ function withAskBot(
690
977
  ? `${outcome}\n\n${exchangeLines(bot, exchanges)}`
691
978
  : outcome;
692
979
  } finally {
693
- if (taskId) void closeJobShell(taskId);
980
+ if (session) void closeHiddenBrowser(session);
694
981
  }
695
982
  },
696
983
  }),
@@ -698,7 +985,7 @@ function withAskBot(
698
985
  return agentTools;
699
986
  }
700
987
 
701
- /** Appended under the report: what the borrowed bot asked and what this bot answered. */
988
+ /** Appended under the answer: what the borrowed bot asked and what this bot answered. */
702
989
  function exchangeLines(
703
990
  bot: string,
704
991
  exchanges: { question: string; answer: string }[],
@@ -780,6 +1067,8 @@ async function answerBack(input: {
780
1067
  },
781
1068
  ],
782
1069
  abortSignal: input.signal,
1070
+ // One-shot: nothing streams back, so its whole length is one silence
1071
+ timeout: BOT_RUN.silenceMs,
783
1072
  });
784
1073
  return {
785
1074
  text:
@@ -798,90 +1087,83 @@ async function answerBack(input: {
798
1087
  }
799
1088
 
800
1089
  /**
801
- * The stored thread cut down for a resume: tool results dropped, tool calls
802
- * kept as one line each (task.query toolLine), ask/answer/report exchanges
803
- * kept as text. The opening always, then only the last `keep` messages.
804
- * Output is user/assistant text only, so a run stopped mid-step has no
805
- * dangling tool call.
1090
+ * The stored thread as a resuming run reads it: the rows as they were written,
1091
+ * each tool call with its result, so a resumed run carries on the conversation
1092
+ * it was having and a provider's prefix cache still matches from its first call.
1093
+ * The one repair is a call whose result never arrived the run was stopped
1094
+ * between the two (a closed browser, a restart, a model that went quiet) and a
1095
+ * call with no result is refused outright — which gets one saying so, right
1096
+ * after it. An `ask_bot` call cut off that way carries what the borrowed bot
1097
+ * had written by then, so its part is picked up rather than asked for again
1098
+ * from nothing. Nothing else changes: `compact` is the one thing that shortens
1099
+ * a bot's own thread.
806
1100
  */
807
- export function condenseThread(
1101
+ export function resumeThread(
808
1102
  thread: ModelMessage[],
809
- keep = BOT_RUN.resumeMessages,
1103
+ /** Borrowed bots' last lines, by `ask_bot` call id (task.query listBorrowedTails). */
1104
+ borrowed: ReadonlyMap<string, { bot: string; tail: string }> = new Map(),
810
1105
  ): ModelMessage[] {
811
- const [opening, ...rest] = thread;
812
- const flat: { role: "user" | "assistant"; content: string }[] = [];
813
- const say = (role: "user" | "assistant", text: string) => {
814
- const line = text.trim();
815
- if (!line) return;
816
- const last = flat.at(-1);
817
- // Merge consecutive same-role messages; some providers require alternation.
818
- if (last?.role === role) last.content = `${last.content}\n\n${line}`;
819
- else flat.push({ role, content: line });
820
- };
821
-
822
- for (const message of rest) {
823
- if (message.role === "user") {
824
- say(
825
- "user",
826
- typeof message.content === "string"
827
- ? message.content
828
- : message.content
829
- .flatMap((part) => (part.type === "text" ? [part.text] : []))
830
- .join("\n"),
831
- );
832
- } else if (message.role === "assistant") {
833
- if (typeof message.content === "string") {
834
- say("assistant", message.content);
835
- continue;
836
- }
837
- for (const part of message.content) {
838
- if (part.type === "text") say("assistant", part.text);
839
- else if (part.type === "tool-call") say("assistant", callNote(part));
840
- }
841
- } else if (message.role === "tool") {
842
- for (const part of message.content) {
843
- if (part.type !== "tool-result") continue;
844
- if (
845
- part.toolName === TOOL_NAMES.ask_thursday ||
846
- part.toolName === TOOL_NAMES.ask_back
847
- ) {
848
- // Thursday, or the bot that borrowed this one
849
- say("user", outputText(part.output));
850
- } else if (part.toolName === TOOL_NAMES.ask_bot) {
851
- say(
852
- "assistant",
853
- `(they answered: ${clip(outputText(part.output), 600)})`,
854
- );
855
- }
856
- // Other tool results are dropped
857
- }
1106
+ const answered = new Set<string>();
1107
+ for (const message of thread) {
1108
+ // A provider-run tool (web search) answers inside the assistant message
1109
+ if (message.role !== "tool" && message.role !== "assistant") continue;
1110
+ if (typeof message.content === "string") continue;
1111
+ for (const part of message.content) {
1112
+ if (part.type === "tool-result") answered.add(part.toolCallId);
858
1113
  }
859
1114
  }
860
1115
 
861
- const tail = flat.slice(-keep);
862
- return opening ? [opening, ...tail] : tail;
1116
+ const out: ModelMessage[] = [];
1117
+ let owed: Extract<ModelMessage, { role: "tool" }>["content"] = [];
1118
+ for (const message of thread) {
1119
+ if (owed.length && message.role === "tool") {
1120
+ out.push({ ...message, content: [...owed, ...message.content] });
1121
+ owed = [];
1122
+ continue;
1123
+ }
1124
+ if (owed.length) {
1125
+ out.push({ role: "tool", content: owed });
1126
+ owed = [];
1127
+ }
1128
+ out.push(message);
1129
+ if (message.role !== "assistant" || typeof message.content === "string") {
1130
+ continue;
1131
+ }
1132
+ owed = message.content.flatMap((part) =>
1133
+ part.type === "tool-call" && !answered.has(part.toolCallId)
1134
+ ? [
1135
+ {
1136
+ type: "tool-result" as const,
1137
+ toolCallId: part.toolCallId,
1138
+ toolName: part.toolName,
1139
+ output: {
1140
+ type: "text" as const,
1141
+ value: interrupted(part, borrowed),
1142
+ },
1143
+ },
1144
+ ]
1145
+ : [],
1146
+ );
1147
+ }
1148
+ if (owed.length) out.push({ role: "tool", content: owed });
1149
+ return out;
863
1150
  }
864
1151
 
865
- /**
866
- * One tool call as one line for a resuming model. Conversation calls read as
867
- * conversation; the rest use the same `toolLine` the screen draws (task.query).
868
- */
869
- function callNote(part: { toolName: string; input: unknown }): string {
870
- const args = (part.input ?? {}) as Record<string, unknown>;
871
- if (part.toolName === TOOL_NAMES.ask_thursday) {
872
- return `(asked Thursday: ${String(args.question ?? "")})`;
873
- }
874
- if (part.toolName === TOOL_NAMES.ask_back) {
875
- return `(asked back: ${String(args.question ?? "")})`;
876
- }
877
- if (part.toolName === TOOL_NAMES.ask_bot) {
878
- return `(asked ${String(args.bot ?? "")} for: ${String(args.request ?? "")})`;
879
- }
880
- if (part.toolName === TOOL_NAMES.report) {
881
- // A mid-thread report is a stretch that ended; the follow-up under it needs it.
882
- return `(handed back: ${clip(String(args.result ?? ""), 300)})`;
883
- }
884
- return `[${part.toolName} ${toolLine(part.toolName, args)}]`;
1152
+ /** What a resumed run reads where a call's result never arrived (resumeThread). */
1153
+ const INTERRUPTED =
1154
+ "No result: the run was stopped before this call returned. Check what it did before relying on it.";
1155
+
1156
+ /** INTERRUPTED, or for a borrowed bot's part, what it had written when the run stopped. */
1157
+ function interrupted(
1158
+ call: { toolCallId: string; toolName: string },
1159
+ borrowed: ReadonlyMap<string, { bot: string; tail: string }>,
1160
+ ): string {
1161
+ const cut =
1162
+ call.toolName === TOOL_NAMES.ask_bot
1163
+ ? borrowed.get(call.toolCallId)
1164
+ : undefined;
1165
+ if (!cut) return INTERRUPTED;
1166
+ return `No result: the run was stopped before ${cut.bot} answered, and they are not working on it any more. What they had written by then:\n\n${cut.tail}\n\nCheck what they did before relying on it, then finish the part yourself or hand them what is left.`;
885
1167
  }
886
1168
 
887
1169
  /** Just the text out of the shape a tool result reached the model in (ai-sdk ToolResultOutput). */
@@ -895,15 +1177,3 @@ function outputText(output: unknown): string {
895
1177
  }
896
1178
  return JSON.stringify(output ?? "");
897
1179
  }
898
-
899
- /** The borrowed bot's first user message: the request plus what the asking bot already knows. */
900
- function taskPrompt(input: {
901
- request: string;
902
- context?: string | null;
903
- askedBy: string;
904
- }) {
905
- const context = input.context?.trim();
906
- return context
907
- ? `${input.request}\n\n## What ${input.askedBy} already knows\n\n${context}`
908
- : input.request;
909
- }