thursday-agent 0.2.0 → 0.3.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 (535) 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 +11 -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 +27 -0
  134. package/.next/server/chunks/{_0pppbb5._.js → _03kp0jp._.js} +163 -36
  135. package/.next/server/chunks/_051qh8u._.js +3 -0
  136. package/.next/server/chunks/{node_modules_next_04q-o58._.js → _05dla15._.js} +2 -2
  137. package/.next/server/chunks/_05h2cii._.js +1 -1
  138. package/.next/server/chunks/_0c9j6rb._.js +49 -0
  139. package/.next/server/chunks/_0fzqt2y._.js +130 -0
  140. package/.next/server/chunks/{_0vnutv9._.js → _0ioz3nd._.js} +1 -1
  141. package/.next/server/chunks/_0iwfby5._.js +3 -0
  142. package/.next/server/chunks/_0mecv0f._.js +26 -0
  143. package/.next/server/chunks/_0mh8j4m._.js +3 -0
  144. package/.next/server/chunks/_0n74zsf._.js +4 -4
  145. package/.next/server/chunks/_0obojvv._.js +3 -0
  146. package/.next/server/chunks/_0recufk._.js +49 -0
  147. package/.next/server/chunks/_0rnf-6n._.js +4 -4
  148. package/.next/server/chunks/_0sb7tlz._.js +3 -0
  149. package/.next/server/chunks/_0u10wmw._.js +5 -5
  150. package/.next/server/chunks/_0u4aiy2._.js +3 -0
  151. package/.next/server/chunks/_0wp4le3._.js +1 -1
  152. package/.next/server/chunks/_0y5fg-x._.js +1 -1
  153. package/.next/server/chunks/_1230wbx._.js +10 -0
  154. package/.next/server/chunks/_12nheja._.js +3 -0
  155. package/.next/server/chunks/_13510xh._.js +3 -0
  156. package/.next/server/chunks/_15hq89o._.js +1 -1
  157. package/.next/server/chunks/_17l5ygt._.js +49 -0
  158. package/.next/server/chunks/_18-1wcc._.js +5 -5
  159. package/.next/server/chunks/_1_5mnq4._.js +4 -4
  160. package/.next/server/chunks/_1_9mjx-._.js +3 -0
  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]__09q9med._.js +1 -1
  195. package/.next/server/chunks/ssr/{[root-of-the-server]__1qfqvmg._.js → [root-of-the-server]__0_69wqr._.js} +2 -2
  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]__0tbkdlx._.js +3 -0
  200. package/.next/server/chunks/ssr/[root-of-the-server]__0vnmv-s._.js +3 -0
  201. package/.next/server/chunks/ssr/{[root-of-the-server]__0f6s48x._.js → [root-of-the-server]__0xpcv9w._.js} +3 -3
  202. package/.next/server/chunks/ssr/[root-of-the-server]__0xtssj3._.js +1 -1
  203. package/.next/server/chunks/ssr/[root-of-the-server]__0zkv7z_._.js +3 -0
  204. package/.next/server/chunks/ssr/[root-of-the-server]__10-dz5o._.js +57 -0
  205. package/.next/server/chunks/ssr/[root-of-the-server]__15_uhip._.js +3 -0
  206. package/.next/server/chunks/ssr/[root-of-the-server]__16jc196._.js +1 -1
  207. package/.next/server/chunks/ssr/{[root-of-the-server]__0m1b5ky._.js → [root-of-the-server]__1cyjywu._.js} +1 -1
  208. package/.next/server/chunks/ssr/[root-of-the-server]__1eh7hid._.js +3 -0
  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]__1pduf5n._.js +3 -0
  213. package/.next/server/chunks/ssr/{_1mznrxk._.js → _001_ms1._.js} +3 -3
  214. package/.next/server/chunks/ssr/_01_6m32._.js +3 -0
  215. package/.next/server/chunks/ssr/_01n3o0m._.js +3 -0
  216. package/.next/server/chunks/ssr/_07uchnc._.js +3 -0
  217. package/.next/server/chunks/ssr/_0ap2fzn._.js +3 -0
  218. package/.next/server/chunks/ssr/_0c-ql2m._.js +3 -0
  219. package/.next/server/chunks/ssr/_0h42fze._.js +3 -0
  220. package/.next/server/chunks/ssr/_0h7zdni._.js +1 -1
  221. package/.next/server/chunks/ssr/_0i_6q1w._.js +41 -0
  222. package/.next/server/chunks/ssr/_0kbkmar._.js +3 -0
  223. package/.next/server/chunks/ssr/{_077-g6s._.js → _0mb8zc8._.js} +3 -3
  224. package/.next/server/chunks/ssr/_0mo2w06._.js +3 -0
  225. package/.next/server/chunks/ssr/{_13punpm._.js → _0q0zan6._.js} +2 -2
  226. package/.next/server/chunks/ssr/{_040sk32._.js → _0u2jv_x._.js} +76 -82
  227. package/.next/server/chunks/ssr/_0y0tiht._.js +4 -0
  228. package/.next/server/chunks/ssr/_0zhoc3r._.js +3 -0
  229. package/.next/server/chunks/ssr/_1-teee5._.js +3 -3
  230. package/.next/server/chunks/ssr/_13fke0x._.js +3 -0
  231. package/.next/server/chunks/ssr/{_1lip4th._.js → _13z3f85._.js} +2 -2
  232. package/.next/server/chunks/ssr/_14_lw10._.js +3 -0
  233. package/.next/server/chunks/ssr/_15j2_8f._.js +3 -0
  234. package/.next/server/chunks/ssr/{_09ja_w1._.js → _1eoqg_g._.js} +2 -2
  235. package/.next/server/chunks/ssr/_1sji22n._.js +3 -0
  236. package/.next/server/chunks/ssr/_1spzm8z._.js +3 -0
  237. package/.next/server/chunks/ssr/{_0x_nu5e._.js → _1vi_uo7._.js} +2 -2
  238. package/.next/server/chunks/ssr/_1vyd1ji._.js +1 -1
  239. package/.next/server/chunks/ssr/{_1m3bsfr._.js → _1xvgn77._.js} +2 -2
  240. package/.next/server/chunks/ssr/_20kd_n0._.js +42 -0
  241. package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +3 -0
  242. package/.next/server/chunks/ssr/features_config_components_config-setting_tsx_0xfwqdg._.js +1 -1
  243. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
  244. package/.next/server/chunks/ssr/lib_date-like_ts_02is0pw._.js +3 -0
  245. package/.next/server/chunks/ssr/{node_modules_18_dk7_._.js → node_modules_063xwpp._.js} +1 -1
  246. package/.next/server/chunks/ssr/{node_modules_01qnv-a._.js → node_modules_0bvraul._.js} +1 -1
  247. package/.next/server/chunks/ssr/{node_modules_0n1kj4p._.js → node_modules_0eu1va6._.js} +1 -1
  248. package/.next/server/chunks/ssr/{node_modules_0kl7-tn._.js → node_modules_0h50b8n._.js} +1 -1
  249. package/.next/server/chunks/ssr/{node_modules_0_n241r._.js → node_modules_0h5hnr1._.js} +1 -1
  250. package/.next/server/chunks/ssr/{node_modules_0lrjk5t._.js → node_modules_0h7u60n._.js} +1 -1
  251. package/.next/server/chunks/ssr/{node_modules_0ndvxub._.js → node_modules_0lm7_vm._.js} +1 -1
  252. package/.next/server/chunks/ssr/{node_modules_13r44jq._.js → node_modules_0mwym0g._.js} +1 -1
  253. package/.next/server/chunks/ssr/{node_modules_17lrt3y._.js → node_modules_0rjg6t1._.js} +1 -1
  254. package/.next/server/chunks/ssr/node_modules_0x3-ply._.js +3 -0
  255. package/.next/server/chunks/ssr/{node_modules_004qlr4._.js → node_modules_0y_cded._.js} +1 -1
  256. package/.next/server/chunks/ssr/{node_modules_0kxz4gt._.js → node_modules_0z5tsu8._.js} +1 -1
  257. package/.next/server/chunks/ssr/{node_modules_0iz1rzd._.js → node_modules_0zp9r92._.js} +1 -1
  258. package/.next/server/chunks/ssr/{node_modules_19dp7ko._.js → node_modules_1-hc6uv._.js} +1 -1
  259. package/.next/server/chunks/ssr/{node_modules_1q0p85z._.js → node_modules_113tz0l._.js} +1 -1
  260. package/.next/server/chunks/ssr/{node_modules_1toafc-._.js → node_modules_124bue5._.js} +1 -1
  261. package/.next/server/chunks/ssr/node_modules_17bwo43._.js +55 -0
  262. package/.next/server/chunks/ssr/{node_modules_08d8bke._.js → node_modules_1_n3ztw._.js} +1 -1
  263. package/.next/server/chunks/ssr/{node_modules_1dwguw_._.js → node_modules_1bc7gu8._.js} +1 -1
  264. package/.next/server/chunks/ssr/{node_modules_20yebtj._.js → node_modules_1bgb6-f._.js} +1 -1
  265. package/.next/server/chunks/ssr/{node_modules_1n5ygqe._.js → node_modules_1h4a4pp._.js} +1 -1
  266. package/.next/server/chunks/ssr/{node_modules_04n7mjs._.js → node_modules_1i7u1ia._.js} +1 -1
  267. package/.next/server/chunks/ssr/{node_modules_1hn8z1f._.js → node_modules_1juq3hs._.js} +1 -1
  268. package/.next/server/chunks/ssr/{node_modules_1xyd7ov._.js → node_modules_1l0qtdu._.js} +1 -1
  269. package/.next/server/chunks/ssr/{node_modules_0m93zeg._.js → node_modules_1t9cyee._.js} +1 -1
  270. package/.next/server/chunks/ssr/{node_modules_09xhur7._.js → node_modules_1uuj9x1._.js} +1 -1
  271. package/.next/server/chunks/ssr/{node_modules_1hd-5tv._.js → node_modules_1vqb6po._.js} +1 -1
  272. package/.next/server/chunks/ssr/{node_modules_1zu988w._.js → node_modules_1xx28h1._.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_0d3skpl._.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_0i44kk5._.js} +1 -1
  275. package/.next/server/chunks/ssr/node_modules_@vercel_oidc_dist_0qe30a_._.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/{31ttnjt6g35a0.js → 0ev4hyghq0mbb.js} +1 -1
  285. package/.next/static/chunks/0r5qcctujhxli.js +1 -0
  286. package/.next/static/chunks/0tmgrnlje14-s.js +3 -0
  287. package/.next/static/chunks/0x79tx04dxbpp.js +1 -0
  288. package/.next/static/chunks/0xzgvc0fkdb6t.js +1 -0
  289. package/.next/static/chunks/10teww7wt-f6h.js +1 -0
  290. package/.next/static/chunks/14hgtsjg-kael.css +1 -0
  291. package/.next/static/chunks/17donp69y4do7.js +40 -0
  292. package/.next/static/chunks/{3_h9txt3tq8t8.js → 1dkjg87pxk0n9.js} +1 -1
  293. package/.next/static/chunks/1ekc7ul556kbl.js +1 -0
  294. package/.next/static/chunks/{3ard_noqapnw1.js → 1grhjo7a5a0_-.js} +1 -1
  295. package/.next/static/chunks/1kq8h8wi_e0bc.js +53 -0
  296. package/.next/static/chunks/1okqiww3pnwka.js +1 -0
  297. package/.next/static/chunks/26-pr2c9ro222.js +1 -0
  298. package/.next/static/chunks/{18vhba44l1fc9.js → 280t0_e99i276.js} +1 -1
  299. package/.next/static/chunks/28cizcdaabd4h.js +1 -0
  300. package/.next/static/chunks/2a0k6jlq0_v1u.js +2 -0
  301. package/.next/static/chunks/2fzs9fm8yarvq.js +1 -0
  302. package/.next/static/chunks/2h0sd2vumhb15.js +1 -0
  303. package/.next/static/chunks/2hw2f6zmt7fmt.js +1 -0
  304. package/.next/static/chunks/2t4vhi3m8ahya.js +1 -0
  305. package/.next/static/chunks/2tigdtz3dpcy8.js +1 -0
  306. package/.next/static/chunks/3111pj5wnjgos.js +1 -0
  307. package/.next/static/chunks/{07kl352c-lvcz.js → 3agpgkwaa321p.js} +1 -1
  308. package/.next/static/chunks/3cfi_q6d_wtci.js +1 -0
  309. package/.next/static/chunks/3ok9_x74rip3v.js +1 -0
  310. package/.next/static/chunks/3xk7yqgomx7yq.js +1 -0
  311. package/.next/static/chunks/3xpbkl66ssib5.js +38 -0
  312. package/.next/static/media/favicon.37o1pjldwi2uc.ico +0 -0
  313. package/README.md +6 -4
  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 -5
  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/thursday.mjs +3 -0
  324. package/components/ui/shiny-text.tsx +48 -134
  325. package/database/migrations/{20260909060813_optimal_sabretooth → 20260910173049_numerous_spirit}/migration.sql +24 -34
  326. package/database/migrations/{20260909060813_optimal_sabretooth → 20260910173049_numerous_spirit}/snapshot.json +71 -145
  327. package/database/migrations/20260911030320_opposite_jack_power/migration.sql +1 -0
  328. package/database/migrations/{20260909063821_bitter_tenebrous → 20260911030320_opposite_jack_power}/snapshot.json +57 -184
  329. package/features/ai/chatgpt.ts +617 -0
  330. package/features/ai/components/chatgpt-sign-in.tsx +66 -0
  331. package/features/ai/components/model-picker.tsx +17 -1
  332. package/features/ai/components/provider-icon.tsx +1 -0
  333. package/features/ai/load-tools.ts +58 -42
  334. package/features/ai/model.schema.ts +232 -34
  335. package/features/ai/model.ts +169 -24
  336. package/features/ai/prompts/bot.prompt.ts +156 -65
  337. package/features/ai/prompts/memory-edit.prompt.ts +33 -0
  338. package/features/ai/prompts/prompt-helper.ts +45 -26
  339. package/features/ai/prompts/thursday.prompt.ts +40 -17
  340. package/features/ai/tools/bot.tool.ts +35 -52
  341. package/features/ai/tools/connected.ts +17 -4
  342. package/features/ai/tools/memory.tool.ts +133 -72
  343. package/features/ai/tools/search.tool.ts +136 -47
  344. package/features/ai/tools/tool-name.ts +2 -5
  345. package/features/ai/tools/workspace.tool.ts +2 -1
  346. package/features/bot/bot.action.ts +8 -18
  347. package/features/bot/bot.memory.ts +89 -0
  348. package/features/bot/bot.query.ts +37 -57
  349. package/features/bot/bot.run.ts +507 -241
  350. package/features/bot/bot.runner.ts +410 -114
  351. package/features/bot/bot.schema.ts +79 -19
  352. package/features/bot/bot.seed.ts +89 -16
  353. package/features/bot/components/bot-mark.tsx +122 -20
  354. package/features/bot/components/bot-room.tsx +159 -130
  355. package/features/bot/components/bot-setting.tsx +790 -402
  356. package/features/bot/components/bot-tool.tsx +2 -6
  357. package/features/bot/components/mark-palette.tsx +54 -0
  358. package/features/bot/components/path-chips.tsx +1 -1
  359. package/features/bot/components/task-badge.tsx +26 -6
  360. package/features/bot/components/task-reply.tsx +22 -33
  361. package/features/bot/components/task-setting.tsx +20 -17
  362. package/features/bot/task.query.ts +215 -28
  363. package/features/bot/task.store.ts +53 -9
  364. package/features/config/components/config-setting.tsx +265 -34
  365. package/features/config/components/models-badge.tsx +42 -0
  366. package/features/config/config.action.ts +12 -0
  367. package/features/config/config.const.ts +38 -2
  368. package/features/connectors/mcp.manager.ts +10 -2
  369. package/features/intro/components/intro.tsx +47 -46
  370. package/features/memory/components/memory-edit.tsx +338 -0
  371. package/features/memory/components/memory-setting.tsx +75 -77
  372. package/features/memory/memory.action.ts +3 -43
  373. package/features/memory/memory.edit.ts +87 -0
  374. package/features/memory/memory.query.ts +24 -42
  375. package/features/memory/memory.schema.ts +29 -106
  376. package/features/settings/components/settings.tsx +2 -0
  377. package/features/settings/settings.alert.ts +3 -1
  378. package/features/thursday/components/ascii-orb.tsx +106 -90
  379. package/features/thursday/components/boot.tsx +7 -0
  380. package/features/thursday/components/call-log.tsx +219 -92
  381. package/features/thursday/components/face.tsx +6 -4
  382. package/features/thursday/components/tab-state.tsx +97 -0
  383. package/features/thursday/components/thursday-mark.tsx +4 -1
  384. package/features/thursday/components/thursday-setting.tsx +17 -47
  385. package/features/thursday/components/thursday.tsx +208 -95
  386. package/features/thursday/thursday.action.ts +12 -17
  387. package/features/thursday/thursday.query.ts +61 -70
  388. package/features/thursday/thursday.schema.ts +20 -7
  389. package/features/thursday/tool-line.ts +85 -11
  390. package/features/thursday/use-thursday.ts +86 -59
  391. package/features/workspace/components/file-view.tsx +4 -2
  392. package/features/workspace/workspace.ts +110 -26
  393. package/lib/limits.ts +1 -1
  394. package/lib/oauth-page.ts +27 -0
  395. package/lib/realtime/openai.ts +2 -2
  396. package/lib/realtime/realtime.protocol.ts +1 -1
  397. package/lib/realtime/realtime.tap.ts +122 -0
  398. package/lib/sandbox.ts +59 -36
  399. package/package.json +1 -1
  400. package/skills/browser/SKILL.md +55 -34
  401. package/.next/server/app/api/bot/notes/route/app-paths-manifest.json +0 -3
  402. package/.next/server/app/api/bot/notes/route.js +0 -14
  403. package/.next/server/app/api/bot/notes/route_client-reference-manifest.js +0 -3
  404. package/.next/server/app/api/memory/tidy/route/app-paths-manifest.json +0 -3
  405. package/.next/server/app/api/memory/tidy/route.js +0 -22
  406. package/.next/server/app/api/memory/tidy/route.js.nft.json +0 -1
  407. package/.next/server/chunks/0d-b_next_dist_server_app-render_after-task-async-storage_external_0-exn7p.js +0 -3
  408. package/.next/server/chunks/[root-of-the-server]__0-npkh_._.js +0 -3
  409. package/.next/server/chunks/[root-of-the-server]__005var5._.js +0 -3
  410. package/.next/server/chunks/[root-of-the-server]__019dx9f._.js +0 -5
  411. package/.next/server/chunks/[root-of-the-server]__02_2sm-._.js +0 -5
  412. package/.next/server/chunks/[root-of-the-server]__04_8spk._.js +0 -3
  413. package/.next/server/chunks/[root-of-the-server]__05luceg._.js +0 -3
  414. package/.next/server/chunks/[root-of-the-server]__06t77t4._.js +0 -11
  415. package/.next/server/chunks/[root-of-the-server]__0bed6hu._.js +0 -8
  416. package/.next/server/chunks/[root-of-the-server]__0cro2dz._.js +0 -5
  417. package/.next/server/chunks/[root-of-the-server]__0dupqt7._.js +0 -5
  418. package/.next/server/chunks/[root-of-the-server]__0emr6go._.js +0 -3
  419. package/.next/server/chunks/[root-of-the-server]__0le4d-p._.js +0 -3
  420. package/.next/server/chunks/[root-of-the-server]__0m8ywyo._.js +0 -10
  421. package/.next/server/chunks/[root-of-the-server]__0nfuugi._.js +0 -5
  422. package/.next/server/chunks/[root-of-the-server]__0p32jfz._.js +0 -12
  423. package/.next/server/chunks/[root-of-the-server]__0phdgco._.js +0 -8
  424. package/.next/server/chunks/[root-of-the-server]__0wvgrsl._.js +0 -3
  425. package/.next/server/chunks/[root-of-the-server]__10qshki._.js +0 -3
  426. package/.next/server/chunks/[root-of-the-server]__11j52t0._.js +0 -5
  427. package/.next/server/chunks/[root-of-the-server]__1692zrv._.js +0 -3
  428. package/.next/server/chunks/[root-of-the-server]__16thabu._.js +0 -3
  429. package/.next/server/chunks/[root-of-the-server]__173nhwf._.js +0 -8
  430. package/.next/server/chunks/[root-of-the-server]__1_aoyr9._.js +0 -3
  431. package/.next/server/chunks/[root-of-the-server]__1bn73ih._.js +0 -10
  432. package/.next/server/chunks/[root-of-the-server]__1bqhuh9._.js +0 -8
  433. package/.next/server/chunks/[root-of-the-server]__1cqizfq._.js +0 -5
  434. package/.next/server/chunks/[root-of-the-server]__1e-ci13._.js +0 -3
  435. package/.next/server/chunks/[root-of-the-server]__1fboj2j._.js +0 -3
  436. package/.next/server/chunks/[root-of-the-server]__1lr7j9t._.js +0 -5
  437. package/.next/server/chunks/[root-of-the-server]__1oli73c._.js +0 -3
  438. package/.next/server/chunks/[root-of-the-server]__1txedx6._.js +0 -8
  439. package/.next/server/chunks/[root-of-the-server]__1untqv8._.js +0 -5
  440. package/.next/server/chunks/[root-of-the-server]__1zgpz1w._.js +0 -3
  441. package/.next/server/chunks/_00_584r._.js +0 -293
  442. package/.next/server/chunks/_012yhfm._.js +0 -39
  443. package/.next/server/chunks/_06mxlgn._.js +0 -3
  444. package/.next/server/chunks/_07rtlxv._.js +0 -3
  445. package/.next/server/chunks/_0a351g7._.js +0 -6
  446. package/.next/server/chunks/_0kjjmag._.js +0 -3
  447. package/.next/server/chunks/_0nn99o2._.js +0 -3
  448. package/.next/server/chunks/_0zuzmt_._.js +0 -39
  449. package/.next/server/chunks/_106co2o._.js +0 -3
  450. package/.next/server/chunks/_12bbv5i._.js +0 -100
  451. package/.next/server/chunks/_15q1pig._.js +0 -3
  452. package/.next/server/chunks/_19wg5yn._.js +0 -138
  453. package/.next/server/chunks/_1bt95p9._.js +0 -55
  454. package/.next/server/chunks/_1cbk7ms._.js +0 -26
  455. package/.next/server/chunks/_1drv2et._.js +0 -40
  456. package/.next/server/chunks/_1ic3bew._.js +0 -5
  457. package/.next/server/chunks/_1mxn_lb._.js +0 -3
  458. package/.next/server/chunks/_1rysr-r._.js +0 -10
  459. package/.next/server/chunks/_1u3rwbr._.js +0 -3
  460. package/.next/server/chunks/_1x7p319._.js +0 -6
  461. package/.next/server/chunks/_next-internal_server_app_api_bot_notes_route_actions_0qcsyu7.js +0 -3
  462. package/.next/server/chunks/_next-internal_server_app_api_memory_tidy_route_actions_0_cfuom.js +0 -3
  463. package/.next/server/chunks/features_bot_task_query_ts_11xuzl3._.js +0 -3
  464. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0gzvgfv._.js +0 -3
  465. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0vorg6w._.js +0 -3
  466. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1tpvamf._.js +0 -3
  467. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1vmaq9f._.js +0 -3
  468. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1y9-wcr._.js +0 -3
  469. package/.next/server/chunks/ssr/[externals]_node_crypto_1kn79-7._.js +0 -3
  470. package/.next/server/chunks/ssr/[root-of-the-server]__0iuoa-r._.js +0 -3
  471. package/.next/server/chunks/ssr/[root-of-the-server]__0un-erv._.js +0 -4
  472. package/.next/server/chunks/ssr/[root-of-the-server]__1-mnhyd._.js +0 -92
  473. package/.next/server/chunks/ssr/[root-of-the-server]__1bww500._.js +0 -3
  474. package/.next/server/chunks/ssr/[root-of-the-server]__1vcdast._.js +0 -3
  475. package/.next/server/chunks/ssr/[root-of-the-server]__1xwnu-h._.js +0 -3
  476. package/.next/server/chunks/ssr/_0-mc7ro._.js +0 -3
  477. package/.next/server/chunks/ssr/_01p5r6-._.js +0 -3
  478. package/.next/server/chunks/ssr/_03webdr._.js +0 -3
  479. package/.next/server/chunks/ssr/_09-0k3z._.js +0 -3
  480. package/.next/server/chunks/ssr/_0f_b_9s._.js +0 -3
  481. package/.next/server/chunks/ssr/_0fafgvl._.js +0 -3
  482. package/.next/server/chunks/ssr/_0sf1isi._.js +0 -3
  483. package/.next/server/chunks/ssr/_0su6_08._.js +0 -3
  484. package/.next/server/chunks/ssr/_0ti51xc._.js +0 -3
  485. package/.next/server/chunks/ssr/_0w5bgak._.js +0 -3
  486. package/.next/server/chunks/ssr/_0y84qo6._.js +0 -3
  487. package/.next/server/chunks/ssr/_1ciyoo3._.js +0 -3
  488. package/.next/server/chunks/ssr/_1e-dgz0._.js +0 -4
  489. package/.next/server/chunks/ssr/_1fq7zgb._.js +0 -3
  490. package/.next/server/chunks/ssr/_1g6l4uj._.js +0 -3
  491. package/.next/server/chunks/ssr/_1ruzehw._.js +0 -3
  492. package/.next/server/chunks/ssr/features_bot_components_bot-setting_tsx_01ajft6._.js +0 -4
  493. package/.next/server/chunks/ssr/features_config_components_14jqgy4._.js +0 -3
  494. package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +0 -4
  495. package/.next/server/chunks/ssr/node_modules_zod_v4_classic_external_15fmmz3.js +0 -55
  496. package/.next/static/chunks/05b6nx_o3_f_n.js +0 -1
  497. package/.next/static/chunks/0dmns-gi0raa4.js +0 -1
  498. package/.next/static/chunks/0f9sq-7q_yxyl.js +0 -1
  499. package/.next/static/chunks/0oc4_xoia77dg.css +0 -1
  500. package/.next/static/chunks/0pheo8m3a-rzi.js +0 -1
  501. package/.next/static/chunks/19i8h2t-5evjx.js +0 -1
  502. package/.next/static/chunks/1c5fcj473025q.js +0 -1
  503. package/.next/static/chunks/1pi_hbmjw-f-z.js +0 -1
  504. package/.next/static/chunks/1twt6k09vg4np.js +0 -1
  505. package/.next/static/chunks/1ytnip8wdng93.js +0 -1
  506. package/.next/static/chunks/28gotuisldmom.js +0 -1
  507. package/.next/static/chunks/2998p0y7k1c8z.js +0 -3
  508. package/.next/static/chunks/29gyb3nubj51j.js +0 -53
  509. package/.next/static/chunks/2l233xfycjpd8.js +0 -1
  510. package/.next/static/chunks/2p5n7pa3c40t-.js +0 -2
  511. package/.next/static/chunks/2ru1q132af7h7.js +0 -1
  512. package/.next/static/chunks/2t8wqycwg3y04.js +0 -2
  513. package/.next/static/chunks/2uxum7qbyeyxd.js +0 -1
  514. package/.next/static/chunks/32z_ypgtchw48.js +0 -1
  515. package/.next/static/chunks/33pyjb3xd1iez.js +0 -1
  516. package/.next/static/chunks/3omud5xunrk1f.js +0 -1
  517. package/.next/static/chunks/42dnrkbph9e-1.js +0 -1
  518. package/.next/static/media/favicon.2vob68tjqpejf.ico +0 -0
  519. package/database/migrations/20260909063821_bitter_tenebrous/migration.sql +0 -1
  520. package/features/ai/prompts/memory-tidy.prompt.ts +0 -129
  521. package/features/ai/tools/tidy.tool.ts +0 -22
  522. package/features/bot/bot.notes.ts +0 -120
  523. package/features/memory/components/memory-tidy.tsx +0 -395
  524. package/features/memory/memory.tidy.ts +0 -332
  525. package/features/memory/tidy.query.ts +0 -135
  526. package/features/thursday/use-call-title.ts +0 -31
  527. /package/.next/server/app/api/bot/{notes → memory/files}/route/build-manifest.json +0 -0
  528. /package/.next/server/app/api/bot/{notes → memory/files}/route/server-reference-manifest.json +0 -0
  529. /package/.next/server/app/api/bot/{notes → memory/files}/route.js.map +0 -0
  530. /package/.next/server/app/api/{memory/tidy → bot/memory}/route/build-manifest.json +0 -0
  531. /package/.next/server/app/api/{memory/tidy → bot/memory}/route/server-reference-manifest.json +0 -0
  532. /package/.next/server/app/api/{memory/tidy → bot/memory}/route.js.map +0 -0
  533. /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → x1cCqY0kWpR_VcYx_lWO5}/_buildManifest.js +0 -0
  534. /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → x1cCqY0kWpR_VcYx_lWO5}/_clientMiddlewareManifest.js +0 -0
  535. /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → x1cCqY0kWpR_VcYx_lWO5}/_ssgManifest.js +0 -0
@@ -1,8 +1,15 @@
1
1
  import type { ModelMessage } from "ai";
2
- import { after } from "next/server";
3
- import { appEventStream, appEvents } from "@/app/api/events/app-event.server";
4
- import { PATHS } from "@/config";
5
- import { modelErrorToString } from "@/features/ai/model";
2
+ import {
3
+ appEventStream,
4
+ appEvents,
5
+ presence,
6
+ } from "@/app/api/events/app-event.server";
7
+ import { BOT_RUN, PATHS, WORKSPACE_KEEP } from "@/config";
8
+ import {
9
+ type ModelFailure,
10
+ modelErrorToString,
11
+ modelFailureOf,
12
+ } from "@/features/ai/model";
6
13
  import {
7
14
  buildTaskOpening,
8
15
  OPENING_TURNS,
@@ -14,24 +21,33 @@ import {
14
21
  } from "@/features/thursday/thursday.query";
15
22
  import { pathsIn } from "@/features/workspace/file-kind";
16
23
  import {
24
+ closeHiddenBrowser,
17
25
  closeJobShell,
26
+ jobScratch,
27
+ listScratchFolders,
28
+ pruneJobFiles,
18
29
  removeJobScratch,
30
+ removeUnchangedFolders,
19
31
  } from "@/features/workspace/workspace";
20
32
  import { desktopNotify } from "@/lib/desktop-notify";
21
33
  import { logger } from "@/lib/logger";
22
34
  import { publicError } from "@/lib/public-error";
23
35
  import { createKeyedLock } from "@/lib/queue";
24
- import { condenseThread, runBot, type TaskEvent } from "./bot.run";
25
- import { TASK_CONTINUE, type TaskStatus } from "./bot.schema";
36
+ import { resumeThread, runBot, type TaskEvent } from "./bot.run";
37
+ import { TASK_CONTINUE, type TaskPending, type TaskStatus } from "./bot.schema";
26
38
  import {
27
39
  addTaskUsage,
40
+ countStopsSinceSpoken,
28
41
  deleteFinishedTasks,
29
42
  deleteMessages,
30
43
  deleteTask,
31
44
  findTask,
32
45
  insertTask,
33
46
  lastSeq,
47
+ listAutoStoppedTasks,
48
+ listBorrowedTails,
34
49
  listRunningTaskIds,
50
+ listTaskFolders,
35
51
  listThread,
36
52
  type TaskMessageInput,
37
53
  updateTask,
@@ -39,9 +55,10 @@ import {
39
55
  } from "./task.query";
40
56
 
41
57
  /**
42
- * Runs a job under `after()` so it outlives the request, writing every event
43
- * to the task's thread (task_message). A run always starts from the thread;
44
- * `answerTask` is the one way to add to it, serialized per job by `taskLock`.
58
+ * Runs a job so it outlives the request, writing every event to the task's
59
+ * thread (task_message). A run always starts from the thread. `answerTask` is
60
+ * the one way a person adds to it, serialized per job by `taskLock`, and
61
+ * `resumeTask` the one way the app picks a job back up by itself.
45
62
  */
46
63
 
47
64
  type Run = {
@@ -58,6 +75,8 @@ type Run = {
58
75
  type Pinned = {
59
76
  __botRuns?: Map<string, Run>;
60
77
  __botTaskLock?: ReturnType<typeof createKeyedLock>;
78
+ __botResumes?: Map<string, ReturnType<typeof setTimeout>>;
79
+ __botPausing?: { current: Promise<void> };
61
80
  };
62
81
 
63
82
  /** Pinned to globalThis: next dev reloads this module under a running run, and the abort controller and lock must be the originals. */
@@ -70,7 +89,17 @@ const running: Map<string, Run> = ((globalThis as Pinned).__botRuns ??=
70
89
  */
71
90
  const taskLock = ((globalThis as Pinned).__botTaskLock ??= createKeyedLock());
72
91
 
73
- /** Opens a job. The opening message is the request plus the last turns of the call (bot.prompt buildTaskOpening) and is the thread's first row. */
92
+ /** When each parked job picks itself back up (scheduleResume); one timer per job. */
93
+ const resumes: Map<string, ReturnType<typeof setTimeout>> = ((
94
+ globalThis as Pinned
95
+ ).__botResumes ??= new Map());
96
+
97
+ /** The pause under way, if any; a returning browser waits for it (resumeStoppedTasks). */
98
+ const pausing = ((globalThis as Pinned).__botPausing ??= {
99
+ current: Promise.resolve(),
100
+ });
101
+
102
+ /** Opens a job. The opening message — who is who, the job, the call it came from (bot.prompt buildTaskOpening) — is the thread's first row. */
74
103
  export async function startTask(input: {
75
104
  bot: string;
76
105
  request: string;
@@ -80,7 +109,11 @@ export async function startTask(input: {
80
109
  const conversation = input.callId
81
110
  ? await listCallTurns(input.callId, OPENING_TURNS)
82
111
  : [];
83
- const opening = buildTaskOpening({ request: input.request, conversation });
112
+ const opening = buildTaskOpening({
113
+ bot: input.bot,
114
+ request: input.request,
115
+ conversation,
116
+ });
84
117
  const task = await insertTask({ ...input, opening });
85
118
  launch(task.id, {
86
119
  bot: input.bot,
@@ -110,11 +143,13 @@ export async function answerTask(id: string, answer: string) {
110
143
  live.stop.abort();
111
144
  await live.done;
112
145
  }
146
+ // A person picked it up; whatever the app meant to do by itself is moot
147
+ forgetResume(id);
113
148
 
114
- const { reply, as } = joinThread(task, answer);
149
+ const thread = await listThread(id);
150
+ const { reply, as } = joinThread(task, answer, thread);
115
151
  logger.debug(`task ${id}: ${as}`);
116
152
 
117
- const thread = await listThread(id);
118
153
  await upsertMessage(id, (await lastSeq(id)) + 1, {
119
154
  bot: null,
120
155
  parent: null,
@@ -125,14 +160,53 @@ export async function answerTask(id: string, answer: string) {
125
160
  status: "running",
126
161
  outcome: null,
127
162
  pending: null,
128
- reported: false,
163
+ seen: false,
129
164
  endedAt: null,
130
165
  });
131
- // Stored whole, read back folded (bot.run condenseThread)
132
- launch(id, { bot: task.bot, messages: condenseThread([...thread, reply]) });
166
+ // Resumed on the rows as they were stored (bot.run resumeThread)
167
+ launch(id, {
168
+ bot: task.bot,
169
+ messages: resumeThread([...thread, reply], await listBorrowedTails(id)),
170
+ });
133
171
  });
134
172
  }
135
173
 
174
+ /**
175
+ * The `ask_thursday` calls still open in the step a job stopped on: a model can
176
+ * ask twice at once, and the one answer goes to each rather than leaving the
177
+ * second to read as a call cut off (bot.run resumeThread). A call that already
178
+ * has a result — arguments the tool refused — is not open, and the words go in
179
+ * as a turn instead: a second result for one call is refused outright.
180
+ */
181
+ function openAsks(thread: ModelMessage[], stoppedOn: string): string[] {
182
+ const answered = new Set(
183
+ thread.flatMap((message) =>
184
+ message.role === "tool"
185
+ ? message.content.flatMap((part) =>
186
+ part.type === "tool-result" ? [part.toolCallId] : [],
187
+ )
188
+ : [],
189
+ ),
190
+ );
191
+ const step = thread.findLast(
192
+ (message) =>
193
+ message.role === "assistant" &&
194
+ typeof message.content !== "string" &&
195
+ message.content.some(
196
+ (part) => part.type === "tool-call" && part.toolCallId === stoppedOn,
197
+ ),
198
+ );
199
+ const asks =
200
+ step?.role === "assistant" && typeof step.content !== "string"
201
+ ? step.content.flatMap((part) =>
202
+ part.type === "tool-call" && part.toolName === TOOL_NAMES.ask_thursday
203
+ ? [part.toolCallId]
204
+ : [],
205
+ )
206
+ : [stoppedOn];
207
+ return asks.filter((toolCallId) => !answered.has(toolCallId));
208
+ }
209
+
136
210
  /**
137
211
  * How a person's words enter the thread. A job stopped on `ask_thursday` gets
138
212
  * them as that call's tool result (a call with no result would break the
@@ -140,23 +214,23 @@ export async function answerTask(id: string, answer: string) {
140
214
  * continue has no call to answer, so it is a turn as well.
141
215
  */
142
216
  function joinThread(
143
- task: { status: TaskStatus; pending: { toolCallId: string | null } | null },
217
+ task: { status: TaskStatus; pending: TaskPending | null },
144
218
  answer: string,
219
+ thread: ModelMessage[],
145
220
  ): { reply: ModelMessage; as: "answer" | "interruption" | "follow-up" } {
146
221
  const pending = task.status === "waiting" ? task.pending : null;
147
- if (pending?.toolCallId) {
222
+ const asks = pending?.toolCallId ? openAsks(thread, pending.toolCallId) : [];
223
+ if (asks.length) {
148
224
  return {
149
225
  as: "answer",
150
226
  reply: {
151
227
  role: "tool",
152
- content: [
153
- {
154
- type: "tool-result",
155
- toolCallId: pending.toolCallId,
156
- toolName: TOOL_NAMES.ask_thursday,
157
- output: { type: "text", value: answer },
158
- },
159
- ],
228
+ content: asks.map((toolCallId) => ({
229
+ type: "tool-result" as const,
230
+ toolCallId,
231
+ toolName: TOOL_NAMES.ask_thursday,
232
+ output: { type: "text" as const, value: answer },
233
+ })),
160
234
  },
161
235
  };
162
236
  }
@@ -175,13 +249,17 @@ export async function cancelTask(id: string) {
175
249
  return taskLock(id, async () => {
176
250
  const task = await findTask(id);
177
251
  if (!task) publicError("No such job.");
252
+ // A job that already ended has an answer in `outcome`; cancelling would
253
+ // write "Cancelled." over the thing it was run for.
254
+ if (task.endedAt) publicError("That job has already ended.");
255
+ forgetResume(id);
178
256
  running.get(id)?.stop.abort();
179
257
  await updateTask(id, {
180
258
  status: "failed",
181
259
  outcome: "Cancelled.",
182
260
  pending: null,
183
261
  // Nobody needs to be told what they just did themselves
184
- reported: true,
262
+ seen: true,
185
263
  endedAt: new Date(),
186
264
  });
187
265
  // Cancel is a real end, so the browser session closes here; the abort in
@@ -194,6 +272,7 @@ export async function cancelTask(id: string) {
194
272
  export async function removeTask(id: string) {
195
273
  return taskLock(id, async () => {
196
274
  const task = await findTask(id);
275
+ forgetResume(id);
197
276
  running.get(id)?.stop.abort();
198
277
  running.delete(id);
199
278
  void closeJobShell(id);
@@ -217,57 +296,234 @@ export async function removeFinishedTasks(): Promise<number> {
217
296
  }
218
297
 
219
298
  /**
220
- * At boot (instrumentation): rows the last process left as running become
221
- * `waiting` with the one continue option, not failed. The answer resumes the
222
- * stored thread (answerTask). Nothing else needs to reconcile — a run that ends
223
- * badly records it itself (drive), and the write that does so is durable
224
- * (database/db.ts oneAtATime).
299
+ * Clears what jobs left behind by age (config WORKSPACE_KEEP), at boot and on a
300
+ * timer (instrumentation): a job's folder once the job ended that long ago — a job
301
+ * running or waiting keeps its own however old — a scratch folder no job owns once
302
+ * it has not changed that long, and spilled output and browser snapshots that old
303
+ * (pruneJobFiles). A job's folder goes under the job's lock, so a word that picks
304
+ * the job back up in that moment never finds its material gone. Returns the
305
+ * folders that went.
306
+ */
307
+ export async function sweepJobFiles(): Promise<string[]> {
308
+ const cutoff = Date.now() - WORKSPACE_KEEP.forMs;
309
+ const stale = (task: {
310
+ status: TaskStatus;
311
+ endedAt: Date | null;
312
+ updatedAt: Date;
313
+ }) =>
314
+ (task.status === "done" || task.status === "failed") &&
315
+ (task.endedAt ?? task.updatedAt).getTime() < cutoff;
316
+
317
+ // Folders on disk; each one a job owns is taken out as its job is read
318
+ const unowned = new Set(await listScratchFolders());
319
+ const removed: string[] = [];
320
+ for (const task of await listTaskFolders()) {
321
+ const folder = jobScratch(task.id, task.label);
322
+ if (!unowned.delete(folder) || !stale(task)) continue;
323
+ await taskLock(task.id, async () => {
324
+ const now = await findTask(task.id);
325
+ if (!now || !stale(now)) return;
326
+ await removeJobScratch(task.id, task.label);
327
+ removed.push(folder);
328
+ });
329
+ }
330
+ removed.push(...(await removeUnchangedFolders([...unowned])));
331
+ await pruneJobFiles();
332
+ if (removed.length) {
333
+ logger.info(
334
+ `cleared ${removed.length} old job folder(s): ${removed.join(", ")}`,
335
+ );
336
+ }
337
+ return removed;
338
+ }
339
+
340
+ /**
341
+ * At boot (instrumentation): rows the last process left as running are not
342
+ * running now. Each is parked the way every stop the app causes is (parkTask),
343
+ * so it picks itself back up once a browser is on the app. Nothing else needs
344
+ * to reconcile — a run that ends badly records it itself (drive), and the write
345
+ * that does so is durable (database/db.ts oneAtATime).
225
346
  */
226
347
  export async function sweepTasks() {
227
348
  for (const id of await listRunningTaskIds()) {
228
349
  if (!running.has(id)) {
229
- await abandon(id, "The server restarted while this was running");
350
+ await parkTask(id, {
351
+ why: "The server restarted while this was running.",
352
+ });
230
353
  }
231
354
  }
232
355
  }
233
356
 
234
357
  /**
235
- * Puts a run that is no longer running back where the user can pick it up: the
236
- * same shape a step limit leaves (`waiting` with the one continue option), so
237
- * the screen and Thursday need no third state for it. The thread stays whole,
238
- * and answering resumes from it.
358
+ * The browser closed, or the server is going down (instrumentation): every run
359
+ * stops where it is and is parked (parkTask). All at once — a run slow to let go
360
+ * must not keep the others running. The thread stays, and so does the window: a
361
+ * resume carries on in the page it left off at, including a sign-in the user is
362
+ * mid-way through, which only that window can finish.
363
+ */
364
+ export async function pauseTasks(reason: string) {
365
+ const work = Promise.all(
366
+ [...running].map(([id, run]) =>
367
+ taskLock(id, async () => {
368
+ run.stop.abort();
369
+ await run.done;
370
+ const task = await findTask(id);
371
+ if (!task || task.status !== "running") return;
372
+ await parkTask(id, { why: reason });
373
+ }).catch((cause) => logger.error(`task ${id}: pause`, cause)),
374
+ ),
375
+ ).then(() => {});
376
+ pausing.current = work;
377
+ await work;
378
+ }
379
+
380
+ /**
381
+ * A browser is on the app again (instrumentation `presence.onBack`): every job
382
+ * the app parked picks itself back up — now, or when the wait a failed model
383
+ * call set runs out. A pause still under way is waited for first, so a quick
384
+ * reload does not miss what it parks.
385
+ */
386
+ export async function resumeStoppedTasks() {
387
+ await pausing.current;
388
+ for (const task of await listAutoStoppedTasks()) {
389
+ scheduleResume(task.id, task.retryAt - Date.now());
390
+ }
391
+ }
392
+
393
+ /** One timer per job: a newer stop replaces what an older one set. */
394
+ function scheduleResume(id: string, inMs: number) {
395
+ forgetResume(id);
396
+ resumes.set(
397
+ id,
398
+ setTimeout(
399
+ () => {
400
+ resumes.delete(id);
401
+ void resumeTask(id).catch((cause) =>
402
+ logger.error(`task ${id}: resume`, cause),
403
+ );
404
+ },
405
+ Math.max(0, inMs),
406
+ ),
407
+ );
408
+ }
409
+
410
+ function forgetResume(id: string) {
411
+ clearTimeout(resumes.get(id));
412
+ resumes.delete(id);
413
+ }
414
+
415
+ /**
416
+ * Picks a parked job back up. Only with a browser on the app — nothing runs
417
+ * without one, and its return tries again — and only if it is still parked: a
418
+ * person may have answered, cancelled or deleted it since. No turn is added,
419
+ * because nobody said anything; the note parkTask left is what the run reads last.
420
+ */
421
+ async function resumeTask(id: string) {
422
+ if (!presence.watching) return;
423
+ await taskLock(id, async () => {
424
+ const task = await findTask(id);
425
+ if (!task || task.status !== "waiting" || !task.pending?.auto) return;
426
+ if (running.has(id)) return;
427
+ logger.debug(`task ${id}: picking back up`);
428
+ const thread = await listThread(id);
429
+ await updateTask(id, {
430
+ status: "running",
431
+ outcome: null,
432
+ pending: null,
433
+ seen: false,
434
+ endedAt: null,
435
+ });
436
+ launch(id, {
437
+ bot: task.bot,
438
+ messages: resumeThread(thread, await listBorrowedTails(id)),
439
+ });
440
+ });
441
+ }
442
+
443
+ /**
444
+ * Stops a job the app stopped, rather than the bot or a person: a restart, a
445
+ * closed browser, a model call a retry or a compaction can fix. It waits with
446
+ * the one continue option like any stop, with a line in its thread saying why —
447
+ * the resumed run reads it — and picks itself back up (scheduleResume). Unless
448
+ * the app has already stopped it `BOT_RUN.autoResumes` times since a person last
449
+ * spoke to it: that is what a job that keeps taking the server down looks like,
450
+ * and it waits for a person instead.
239
451
  */
240
- async function abandon(id: string, why: string) {
241
- await updateTask(id, {
452
+ async function parkTask(
453
+ id: string,
454
+ stop: {
455
+ why: string;
456
+ /** A failed model call: wait before trying again (config BOT_RUN.retryAfterMs). */
457
+ retry?: boolean;
458
+ /** Where the job compacts from now on (bot.run `budget`). */
459
+ budget?: number;
460
+ },
461
+ ) {
462
+ const stops = await countStopsSinceSpoken(id);
463
+ const auto = stops < BOT_RUN.autoResumes;
464
+ const waits = BOT_RUN.retryAfterMs;
465
+ const inMs =
466
+ auto && stop.retry ? waits[Math.min(stops, waits.length - 1)] : 0;
467
+
468
+ await writeNote(id, `${stop.why} ${IN_FLIGHT}`).catch((cause) =>
469
+ logger.warn(`task ${id}: stop not written`, cause),
470
+ );
471
+ await writeEnding(id, {
242
472
  status: "waiting",
243
- outcome: `${why}. Everything it had done is still here. Pick it back up?`,
244
- pending: { toolCallId: null, options: [TASK_CONTINUE] },
245
- reported: false,
473
+ outcome: auto
474
+ ? `${stop.why} ${inMs ? `It tries again in ${spell(inMs)}.` : "It picks back up by itself."}`
475
+ : `${stop.why} It has stopped ${stops + 1} times in a row, so it waits for you. Everything it had done is still here.`,
476
+ pending: {
477
+ toolCallId: null,
478
+ options: [TASK_CONTINUE],
479
+ ...(auto ? { auto: true, retryAt: Date.now() + inMs } : {}),
480
+ },
481
+ ...(stop.budget ? { contextBudget: stop.budget } : {}),
482
+ seen: false,
246
483
  endedAt: null,
247
484
  });
485
+ if (auto) scheduleResume(id, inMs);
486
+ }
487
+
488
+ /** Said to a resumed run after why it stopped. */
489
+ const IN_FLIGHT =
490
+ "Anything that was under way — a command, a page loading, a download — may not have finished: check before relying on it, then carry on.";
491
+
492
+ const spell = (ms: number) =>
493
+ ms < 60_000
494
+ ? `${Math.round(ms / 1000)} seconds`
495
+ : `${Math.round(ms / 60_000)} minutes`;
496
+
497
+ /** A line from the app in a thread no run is writing (ThreadWriter.note is the one for a run that is). */
498
+ async function writeNote(id: string, text: string) {
499
+ await upsertMessage(id, (await lastSeq(id)) + 1, {
500
+ bot: null,
501
+ parent: null,
502
+ role: "user",
503
+ content: text,
504
+ note: true,
505
+ });
248
506
  }
249
507
 
250
508
  /**
251
- * The browser closed (app-event.server presence): every run stops where it is
252
- * and waits with the one continue option, the way a restart leaves it
253
- * (sweepTasks). The thread stays; answering resumes it.
509
+ * The write that tells anyone a run ended at all. Tried again: everything else
510
+ * in the run has happened, and a miss leaves the row `running` until the next
511
+ * boot. Writes are serialised, so what is left is a database that briefly
512
+ * cannot be written to (database/db.ts).
254
513
  */
255
- export async function pauseTasks(reason: string) {
256
- for (const [id, run] of [...running]) {
257
- await taskLock(id, async () => {
258
- run.stop.abort();
259
- await run.done;
260
- const task = await findTask(id);
261
- if (!task || task.status !== "running") return;
262
- await updateTask(id, {
263
- status: "waiting",
264
- outcome: `${reason} Everything it had done is still here. Pick it back up?`,
265
- pending: { toolCallId: null, options: [TASK_CONTINUE] },
266
- reported: false,
267
- endedAt: null,
268
- });
269
- void closeJobShell(id);
270
- });
514
+ async function writeEnding(
515
+ id: string,
516
+ patch: Parameters<typeof updateTask>[1],
517
+ ) {
518
+ for (const wait of [1_000, 3_000, null]) {
519
+ try {
520
+ await updateTask(id, patch);
521
+ return;
522
+ } catch (cause) {
523
+ if (wait === null) throw cause;
524
+ logger.warn(`task ${id}: ending not written, trying again`, cause);
525
+ await new Promise((resolve) => setTimeout(resolve, wait));
526
+ }
271
527
  }
272
528
  }
273
529
 
@@ -290,14 +546,14 @@ function launch(id: string, input: { bot: string; messages: ModelMessage[] }) {
290
546
  },
291
547
  };
292
548
  running.set(id, run);
293
- // The response goes out first, and the run carries on behind it
294
- after(() =>
295
- drive(id, input, stop.signal, notes)
296
- .catch((cause) => {
297
- logger.error(`task ${id}`, cause);
298
- })
299
- .finally(finish),
300
- );
549
+ // Held by the server, not by a request: a run also starts from a returning
550
+ // browser and a retry timer, where `after()` throws (no request) or waits for
551
+ // the event stream to close
552
+ void drive(id, input, stop.signal, notes)
553
+ .catch((cause) => {
554
+ logger.error(`task ${id}`, cause);
555
+ })
556
+ .finally(finish);
301
557
  }
302
558
 
303
559
  /**
@@ -314,6 +570,9 @@ class ThreadWriter {
314
570
  { assistant: number | null; tool: number | null; parts: StepParts }
315
571
  >();
316
572
 
573
+ /** Set once the run is over: a borrowed run still unwinding must not write into a thread a resume has taken. */
574
+ private closed = false;
575
+
317
576
  constructor(
318
577
  private taskId: string,
319
578
  seq: number,
@@ -321,6 +580,10 @@ class ThreadWriter {
321
580
  this.seq = seq;
322
581
  }
323
582
 
583
+ close() {
584
+ this.closed = true;
585
+ }
586
+
324
587
  /**
325
588
  * A line from the app rather than from anyone in the room. A user row so a
326
589
  * resumed run reads it as something it was told, and `note` so the screen
@@ -368,6 +631,7 @@ class ThreadWriter {
368
631
  }
369
632
 
370
633
  async on(event: TaskEvent) {
634
+ if (this.closed) return;
371
635
  const step = this.step(event);
372
636
  switch (event.type) {
373
637
  case "text":
@@ -398,8 +662,9 @@ class ThreadWriter {
398
662
  type: "tool-result",
399
663
  toolCallId: event.id,
400
664
  toolName: event.name,
401
- output:
402
- typeof event.output === "string"
665
+ output: event.error
666
+ ? { type: "error-text", value: String(event.output) }
667
+ : typeof event.output === "string"
403
668
  ? { type: "text", value: event.output }
404
669
  : { type: "json", value: event.output as never },
405
670
  });
@@ -468,10 +733,17 @@ async function drive(
468
733
  let ending: {
469
734
  status: TaskStatus;
470
735
  outcome: string;
471
- pending: { toolCallId: string | null; options: string[] } | null;
736
+ pending: TaskPending | null;
737
+ } | null = null;
738
+ /**
739
+ * What broke, as the provider said it — the thread carries it too (below) —
740
+ * and whose it is to fix (model.ts modelFailureOf).
741
+ */
742
+ let broken: {
743
+ message: string;
744
+ failure: ModelFailure;
745
+ budget?: number;
472
746
  } | null = null;
473
- /** What broke, as the provider said it; the thread carries it too (below). */
474
- let broken: string | null = null;
475
747
 
476
748
  try {
477
749
  await runBot(input, {
@@ -503,19 +775,23 @@ async function drive(
503
775
  outcome: event.question,
504
776
  pending: { toolCallId: event.id, options: event.options },
505
777
  };
506
- } else if (event.type === "report") {
507
- // An incomplete report goes back to waiting with the continue option.
508
- // The outcome is the report text verbatim; the same text is the
509
- // thread line (bot.query linesOf).
510
- ending = {
511
- status: event.complete ? "done" : "waiting",
512
- outcome: event.text,
513
- pending: event.complete
514
- ? null
515
- : { toolCallId: null, options: [TASK_CONTINUE] },
516
- };
778
+ } else if (event.type === "answer") {
779
+ // An answer ends the job; one the app stopped (bot.run `stopped`) waits
780
+ // with the continue option. The outcome is the answer text verbatim;
781
+ // the same text is the thread line (bot.query linesOf).
782
+ ending = event.stopped
783
+ ? {
784
+ status: "waiting",
785
+ outcome: event.text,
786
+ pending: { toolCallId: null, options: [TASK_CONTINUE] },
787
+ }
788
+ : { status: "done", outcome: event.text, pending: null };
517
789
  } else if (event.type === "error") {
518
- broken = event.message;
790
+ broken = {
791
+ message: event.message,
792
+ failure: event.failure,
793
+ budget: event.budget,
794
+ };
519
795
  ending = {
520
796
  status: "failed",
521
797
  outcome: broke(event.message),
@@ -525,17 +801,22 @@ async function drive(
525
801
  },
526
802
  });
527
803
  } catch (cause) {
528
- broken = modelErrorToString(cause);
804
+ broken = {
805
+ message: modelErrorToString(cause),
806
+ failure: modelFailureOf(cause),
807
+ };
529
808
  ending = {
530
809
  status: "failed",
531
- outcome: broke(broken),
810
+ outcome: broke(broken.message),
532
811
  pending: null,
533
812
  };
534
813
  } finally {
814
+ thread.close();
535
815
  running.delete(id);
536
816
  }
537
817
 
538
- // Whoever aborted writes the row: cancel marks failed, answerTask relaunches.
818
+ // Whoever aborted writes the row: cancel marks failed, answerTask relaunches,
819
+ // a pause parks.
539
820
  if (signal.aborted) return;
540
821
 
541
822
  const final = ending ?? {
@@ -543,37 +824,49 @@ async function drive(
543
824
  outcome: "Stopped without handing anything back.",
544
825
  pending: null,
545
826
  };
546
- // A thread that simply stops cannot be read: the room shows a tool call with
547
- // no answer, and a resumed run has no idea why. The task row says it too, but
548
- // the row is a status and this is the story. Never at the cost of the status
549
- // write, which is what tells anyone the job ended at all.
550
- if (final.status === "failed") {
551
- await thread
552
- .note(`The run stopped here: ${broken ?? final.outcome}`)
553
- .catch((cause) => logger.warn(`task ${id}: break not written`, cause));
554
- }
827
+ // A break a moment or a compaction can fix is the app's to pick back up, not
828
+ // a failure for a person to find (parkTask)
829
+ const park = broken && broken.failure !== "fatal" ? broken : null;
555
830
 
556
- // The one write that must land: everything else in this run has already
557
- // happened, and this is what tells the screen and Thursday it ended at all.
558
- // Writes are serialised, so contention no longer throws here (database/db.ts);
559
- // what is left is a database that cannot be written to, which a retry would
560
- // not help either. Said plainly rather than thrown into `after`, where it
561
- // would read as an unexplained request failure — and the row stays `running`,
562
- // which the next boot reconciles (sweepTasks).
563
831
  try {
564
- await updateTask(id, {
565
- ...final,
566
- reported: false,
567
- endedAt: final.status === "waiting" ? null : new Date(),
568
- });
832
+ if (park) {
833
+ await parkTask(id, {
834
+ why:
835
+ park.failure === "overflow"
836
+ ? `The model refused the context as too long (${park.message}); it is compacted before going on.`
837
+ : `The model call failed (${park.message}).`,
838
+ retry: park.failure === "transient",
839
+ budget: park.budget,
840
+ });
841
+ } else {
842
+ // A thread that simply stops cannot be read: the room shows a tool call
843
+ // with no answer, and a resumed run has no idea why. The task row says it
844
+ // too, but the row is a status and this is the story. Never at the cost of
845
+ // the status write, which is what tells anyone the job ended at all.
846
+ if (final.status === "failed") {
847
+ await thread
848
+ .note(`The run stopped here: ${broken?.message ?? final.outcome}`)
849
+ .catch((cause) =>
850
+ logger.warn(`task ${id}: break not written`, cause),
851
+ );
852
+ }
853
+ await writeEnding(id, {
854
+ ...final,
855
+ seen: false,
856
+ endedAt: final.status === "waiting" ? null : new Date(),
857
+ });
858
+ }
569
859
  } catch (cause) {
860
+ // Said plainly rather than thrown into a promise nobody awaits; the row
861
+ // stays `running`, which the next boot reconciles (sweepTasks)
570
862
  logger.error(`task ${id}: could not record how it ended`, cause);
571
863
  return;
572
864
  }
573
865
 
574
- // Close the job's browser session (workspace.ts jobShellEnv) unless waiting:
575
- // a bot stopped at a login wall needs the page the user is typing into.
576
- if (final.status !== "waiting") void closeJobShell(id);
866
+ // A waiting job keeps its browser: answering resumes into the page it left
867
+ // off at. An ended one closes only what nobody can see (closeHiddenBrowser).
868
+ if (park || final.status === "waiting") void pruneJobFiles();
869
+ else void closeHiddenBrowser(id);
577
870
 
578
871
  const task = await findTask(id);
579
872
  if (!task) return;
@@ -589,6 +882,9 @@ async function drive(
589
882
  return;
590
883
  }
591
884
 
885
+ // A parked job picks itself back up; nobody needs telling yet
886
+ if (park) return;
887
+
592
888
  await announce(task.label, final);
593
889
 
594
890
  // A finished job naming a document opens it on the screen (workspace artifact-view).
@@ -600,7 +896,7 @@ async function drive(
600
896
  }
601
897
  }
602
898
 
603
- /** The document a report names: the artifacts folder first, otherwise the first drawable file. */
899
+ /** The document an answer names: the artifacts folder first, otherwise the first drawable file. */
604
900
  function artifactIn(outcome: string): string | null {
605
901
  const paths = pathsIn(outcome);
606
902
  return (