@agent-native/core 0.113.0 → 0.114.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 (1508) hide show
  1. package/agent-native.eject.json +164 -0
  2. package/corpus/README.md +3 -3
  3. package/corpus/core/CHANGELOG.md +11 -0
  4. package/corpus/core/README.md +61 -174
  5. package/corpus/core/agent-native.eject.json +164 -0
  6. package/corpus/core/docs/content/agent-native-toolkit.mdx +38 -0
  7. package/corpus/core/docs/content/package-lifecycle.mdx +54 -4
  8. package/corpus/core/package.json +15 -2
  9. package/corpus/core/src/cli/eject.ts +1594 -0
  10. package/corpus/core/src/cli/index.ts +16 -0
  11. package/corpus/core/src/client/index.ts +4 -0
  12. package/corpus/core/src/client/resources/McpConnectionSuggestion.tsx +8 -2
  13. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +7 -2
  14. package/corpus/core/src/client/resources/ResourcesPanel.tsx +17 -3
  15. package/corpus/core/src/client/resources/index.ts +19 -0
  16. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +9 -1
  17. package/corpus/core/src/client/resources/runtime.ts +1 -0
  18. package/corpus/core/src/client/setup-connections/catalog.ts +6 -0
  19. package/corpus/core/src/client/setup-connections/index.ts +1 -0
  20. package/corpus/core/src/client/setup-connections/runtime.ts +1 -0
  21. package/corpus/core/src/connections/catalog.ts +41 -24
  22. package/corpus/core/src/connections/index.ts +1 -0
  23. package/corpus/core/src/connections/runtime.ts +1 -0
  24. package/corpus/core/src/eject/provider-api-definitions.ts +193 -0
  25. package/corpus/core/src/eject/remote-mcp-presets.ts +137 -0
  26. package/corpus/core/src/eject/setup-readiness-ui.tsx +29 -0
  27. package/corpus/core/src/eject/workspace-connections.ts +92 -0
  28. package/corpus/core/src/index.ts +1 -0
  29. package/corpus/core/src/integrations/adapter-overrides.ts +12 -0
  30. package/corpus/core/src/integrations/eject/messaging-adapters.ts +85 -0
  31. package/corpus/core/src/integrations/index.ts +5 -1
  32. package/corpus/core/src/integrations/plugin.ts +34 -12
  33. package/corpus/core/src/integrations/runtime.ts +1 -0
  34. package/corpus/core/src/integrations/types.ts +3 -1
  35. package/corpus/core/src/package-lifecycle/eject-manifest.ts +345 -0
  36. package/corpus/core/src/package-lifecycle/index.ts +1 -0
  37. package/corpus/core/src/provider-api/index.ts +46 -12
  38. package/corpus/core/src/provider-api/runtime.ts +1 -0
  39. package/corpus/core/src/setup-connections/index.ts +1 -0
  40. package/corpus/core/src/shared/merge-by-id.ts +14 -0
  41. package/corpus/core/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +15 -13
  42. package/corpus/core/src/templates/default/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  43. package/corpus/core/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +9 -6
  44. package/corpus/core/src/templates/default/AGENTS.md +6 -5
  45. package/corpus/core/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +15 -13
  46. package/corpus/core/src/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  47. package/corpus/core/src/templates/headless/AGENTS.md +5 -4
  48. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +15 -13
  49. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +3 -2
  50. package/corpus/core/src/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  51. package/corpus/core/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +9 -6
  52. package/corpus/core/src/templates/workspace-core/AGENTS.md +4 -4
  53. package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -4
  54. package/corpus/core/src/vite/client.ts +25 -2
  55. package/corpus/templates/analytics/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  56. package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +9 -6
  57. package/corpus/templates/assets/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  58. package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +9 -6
  59. package/corpus/templates/brain/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  60. package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +9 -6
  61. package/corpus/templates/brain/actions/remediate-legacy-capture-audiences.ts +167 -0
  62. package/corpus/templates/brain/app/lib/brain-chat-readiness.ts +6 -0
  63. package/corpus/templates/brain/app/routes/_index.tsx +8 -0
  64. package/corpus/templates/brain/app/routes.ts +1 -0
  65. package/corpus/templates/brain/changelog/2026-07-19-ask-starts-immediately-when-builder-s-managed-ai-connection-.md +6 -0
  66. package/corpus/templates/brain/changelog/2026-07-19-slack-and-granola-backfills-now-process-captures-concurrentl.md +6 -0
  67. package/corpus/templates/brain/changelog/2026-07-19-source-review-settings-now-control-knowledge-approval.md +6 -0
  68. package/corpus/templates/brain/changelog/2026-07-19-the-manage-agent-page-now-opens-correctly-from-brain-navigat.md +6 -0
  69. package/corpus/templates/brain/server/db/schema.ts +3 -0
  70. package/corpus/templates/brain/server/lib/brain.ts +41 -2
  71. package/corpus/templates/brain/server/lib/connectors.ts +379 -113
  72. package/corpus/templates/brain/server/lib/meeting-audience.ts +38 -0
  73. package/corpus/templates/brain/server/plugins/db.ts +10 -0
  74. package/corpus/templates/brain/server/routes/api/_agent-native/brain/ingest.post.ts +6 -4
  75. package/corpus/templates/calendar/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  76. package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +9 -6
  77. package/corpus/templates/chat/.agents/skills/agent-native-docs/SKILL.md +15 -13
  78. package/corpus/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  79. package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +9 -6
  80. package/corpus/templates/chat/AGENTS.md +3 -2
  81. package/corpus/templates/clips/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  82. package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +9 -6
  83. package/corpus/templates/content/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  84. package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +9 -6
  85. package/corpus/templates/design/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  86. package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +9 -6
  87. package/corpus/templates/dispatch/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  88. package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +9 -6
  89. package/corpus/templates/forms/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  90. package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +9 -6
  91. package/corpus/templates/macros/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  92. package/corpus/templates/mail/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  93. package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +9 -6
  94. package/corpus/templates/plan/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  95. package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +9 -6
  96. package/corpus/templates/slides/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  97. package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +9 -6
  98. package/corpus/templates/tasks/.agents/skills/agent-native-docs/SKILL.md +15 -13
  99. package/corpus/templates/tasks/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  100. package/corpus/templates/tasks/.agents/skills/self-modifying-code/SKILL.md +9 -6
  101. package/corpus/toolkit/CHANGELOG.md +6 -0
  102. package/corpus/toolkit/agent-native.eject.json +411 -0
  103. package/corpus/toolkit/package.json +5 -2
  104. package/dist/cli/eject.d.ts +21 -0
  105. package/dist/cli/eject.d.ts.map +1 -0
  106. package/dist/cli/eject.js +1145 -0
  107. package/dist/cli/eject.js.map +1 -0
  108. package/dist/cli/index.js +15 -0
  109. package/dist/cli/index.js.map +1 -1
  110. package/dist/client/index.d.ts +1 -1
  111. package/dist/client/index.d.ts.map +1 -1
  112. package/dist/client/index.js +1 -1
  113. package/dist/client/index.js.map +1 -1
  114. package/dist/client/resources/McpConnectionSuggestion.d.ts +4 -3
  115. package/dist/client/resources/McpConnectionSuggestion.d.ts.map +1 -1
  116. package/dist/client/resources/McpConnectionSuggestion.js +3 -3
  117. package/dist/client/resources/McpConnectionSuggestion.js.map +1 -1
  118. package/dist/client/resources/McpIntegrationDialog.d.ts +4 -3
  119. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  120. package/dist/client/resources/McpIntegrationDialog.js +2 -2
  121. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  122. package/dist/client/resources/ResourcesPanel.d.ts +4 -1
  123. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  124. package/dist/client/resources/ResourcesPanel.js +9 -7
  125. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  126. package/dist/client/resources/index.d.ts +3 -0
  127. package/dist/client/resources/index.d.ts.map +1 -1
  128. package/dist/client/resources/index.js +3 -0
  129. package/dist/client/resources/index.js.map +1 -1
  130. package/dist/client/resources/mcp-integration-catalog.d.ts +2 -1
  131. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  132. package/dist/client/resources/mcp-integration-catalog.js +6 -2
  133. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  134. package/dist/client/resources/runtime.d.ts +2 -0
  135. package/dist/client/resources/runtime.d.ts.map +1 -0
  136. package/dist/client/resources/runtime.js +2 -0
  137. package/dist/client/resources/runtime.js.map +1 -0
  138. package/dist/client/setup-connections/catalog.d.ts +2 -0
  139. package/dist/client/setup-connections/catalog.d.ts.map +1 -0
  140. package/dist/client/setup-connections/catalog.js +7 -0
  141. package/dist/client/setup-connections/catalog.js.map +1 -0
  142. package/dist/client/setup-connections/index.d.ts +1 -0
  143. package/dist/client/setup-connections/index.d.ts.map +1 -1
  144. package/dist/client/setup-connections/index.js +1 -0
  145. package/dist/client/setup-connections/index.js.map +1 -1
  146. package/dist/client/setup-connections/runtime.d.ts +2 -0
  147. package/dist/client/setup-connections/runtime.d.ts.map +1 -0
  148. package/dist/client/setup-connections/runtime.js +2 -0
  149. package/dist/client/setup-connections/runtime.js.map +1 -0
  150. package/dist/collab/awareness.d.ts +2 -2
  151. package/dist/collab/awareness.d.ts.map +1 -1
  152. package/dist/collab/routes.d.ts +1 -1
  153. package/dist/connections/catalog.d.ts +5 -3
  154. package/dist/connections/catalog.d.ts.map +1 -1
  155. package/dist/connections/catalog.js +16 -8
  156. package/dist/connections/catalog.js.map +1 -1
  157. package/dist/connections/index.d.ts +1 -1
  158. package/dist/connections/index.d.ts.map +1 -1
  159. package/dist/connections/index.js +1 -1
  160. package/dist/connections/index.js.map +1 -1
  161. package/dist/connections/runtime.d.ts +2 -0
  162. package/dist/connections/runtime.d.ts.map +1 -0
  163. package/dist/connections/runtime.js +2 -0
  164. package/dist/connections/runtime.js.map +1 -0
  165. package/dist/eject/provider-api-definitions.d.ts +34 -0
  166. package/dist/eject/provider-api-definitions.d.ts.map +1 -0
  167. package/dist/eject/provider-api-definitions.js +88 -0
  168. package/dist/eject/provider-api-definitions.js.map +1 -0
  169. package/dist/eject/remote-mcp-presets.d.ts +17 -0
  170. package/dist/eject/remote-mcp-presets.d.ts.map +1 -0
  171. package/dist/eject/remote-mcp-presets.js +81 -0
  172. package/dist/eject/remote-mcp-presets.js.map +1 -0
  173. package/dist/eject/setup-readiness-ui.d.ts +7 -0
  174. package/dist/eject/setup-readiness-ui.d.ts.map +1 -0
  175. package/dist/eject/setup-readiness-ui.js +13 -0
  176. package/dist/eject/setup-readiness-ui.js.map +1 -0
  177. package/dist/eject/workspace-connections.d.ts +11 -0
  178. package/dist/eject/workspace-connections.d.ts.map +1 -0
  179. package/dist/eject/workspace-connections.js +47 -0
  180. package/dist/eject/workspace-connections.js.map +1 -0
  181. package/dist/index.d.ts +1 -1
  182. package/dist/index.d.ts.map +1 -1
  183. package/dist/index.js +1 -1
  184. package/dist/index.js.map +1 -1
  185. package/dist/integrations/adapter-overrides.d.ts +3 -0
  186. package/dist/integrations/adapter-overrides.d.ts.map +1 -0
  187. package/dist/integrations/adapter-overrides.js +7 -0
  188. package/dist/integrations/adapter-overrides.js.map +1 -0
  189. package/dist/integrations/eject/messaging-adapters.d.ts +12 -0
  190. package/dist/integrations/eject/messaging-adapters.d.ts.map +1 -0
  191. package/dist/integrations/eject/messaging-adapters.js +49 -0
  192. package/dist/integrations/eject/messaging-adapters.js.map +1 -0
  193. package/dist/integrations/index.d.ts +3 -2
  194. package/dist/integrations/index.d.ts.map +1 -1
  195. package/dist/integrations/index.js +2 -1
  196. package/dist/integrations/index.js.map +1 -1
  197. package/dist/integrations/plugin.d.ts +32 -1
  198. package/dist/integrations/plugin.d.ts.map +1 -1
  199. package/dist/integrations/plugin.js +19 -12
  200. package/dist/integrations/plugin.js.map +1 -1
  201. package/dist/integrations/runtime.d.ts +2 -0
  202. package/dist/integrations/runtime.d.ts.map +1 -0
  203. package/dist/integrations/runtime.js +2 -0
  204. package/dist/integrations/runtime.js.map +1 -0
  205. package/dist/integrations/types.d.ts +3 -1
  206. package/dist/integrations/types.d.ts.map +1 -1
  207. package/dist/integrations/types.js.map +1 -1
  208. package/dist/notifications/routes.d.ts +3 -3
  209. package/dist/package-lifecycle/eject-manifest.d.ts +43 -0
  210. package/dist/package-lifecycle/eject-manifest.d.ts.map +1 -0
  211. package/dist/package-lifecycle/eject-manifest.js +219 -0
  212. package/dist/package-lifecycle/eject-manifest.js.map +1 -0
  213. package/dist/package-lifecycle/index.d.ts +1 -0
  214. package/dist/package-lifecycle/index.d.ts.map +1 -1
  215. package/dist/package-lifecycle/index.js +1 -0
  216. package/dist/package-lifecycle/index.js.map +1 -1
  217. package/dist/progress/routes.d.ts +1 -1
  218. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  219. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  220. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  221. package/dist/provider-api/index.d.ts +6 -2
  222. package/dist/provider-api/index.d.ts.map +1 -1
  223. package/dist/provider-api/index.js +25 -12
  224. package/dist/provider-api/index.js.map +1 -1
  225. package/dist/provider-api/runtime.d.ts +2 -0
  226. package/dist/provider-api/runtime.d.ts.map +1 -0
  227. package/dist/provider-api/runtime.js +2 -0
  228. package/dist/provider-api/runtime.js.map +1 -0
  229. package/dist/secrets/routes.d.ts +9 -9
  230. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  231. package/dist/server/transcribe-voice.d.ts +1 -1
  232. package/dist/setup-connections/index.d.ts +1 -1
  233. package/dist/setup-connections/index.d.ts.map +1 -1
  234. package/dist/setup-connections/index.js +1 -1
  235. package/dist/setup-connections/index.js.map +1 -1
  236. package/dist/shared/merge-by-id.d.ts +5 -0
  237. package/dist/shared/merge-by-id.d.ts.map +1 -0
  238. package/dist/shared/merge-by-id.js +7 -0
  239. package/dist/shared/merge-by-id.js.map +1 -0
  240. package/dist/templates/chat/.agents/skills/agent-native-docs/SKILL.md +15 -13
  241. package/dist/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  242. package/dist/templates/chat/.agents/skills/self-modifying-code/SKILL.md +9 -6
  243. package/dist/templates/chat/AGENTS.md +3 -2
  244. package/dist/templates/default/.agents/skills/agent-native-docs/SKILL.md +15 -13
  245. package/dist/templates/default/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  246. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +9 -6
  247. package/dist/templates/default/AGENTS.md +6 -5
  248. package/dist/templates/headless/.agents/skills/agent-native-docs/SKILL.md +15 -13
  249. package/dist/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  250. package/dist/templates/headless/AGENTS.md +5 -4
  251. package/dist/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +15 -13
  252. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +3 -2
  253. package/dist/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  254. package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +9 -6
  255. package/dist/templates/workspace-core/AGENTS.md +4 -4
  256. package/dist/templates/workspace-root/AGENTS.md +4 -4
  257. package/dist/vite/client.d.ts.map +1 -1
  258. package/dist/vite/client.js +22 -2
  259. package/dist/vite/client.js.map +1 -1
  260. package/docs/content/agent-native-toolkit.mdx +38 -0
  261. package/docs/content/package-lifecycle.mdx +54 -4
  262. package/package.json +15 -2
  263. package/src/a2a/agent-card.ts +83 -0
  264. package/src/a2a/artifact-response.ts +1648 -0
  265. package/src/a2a/auth-policy.ts +66 -0
  266. package/src/a2a/caller-auth.ts +102 -0
  267. package/src/a2a/client.ts +876 -0
  268. package/src/a2a/correlation.ts +50 -0
  269. package/src/a2a/handlers.ts +1285 -0
  270. package/src/a2a/index.ts +50 -0
  271. package/src/a2a/invoke.ts +348 -0
  272. package/src/a2a/response-text.ts +53 -0
  273. package/src/a2a/server.ts +586 -0
  274. package/src/a2a/task-store.ts +917 -0
  275. package/src/a2a/types.ts +221 -0
  276. package/src/a2a-claims.ts +52 -0
  277. package/src/action-change-marker.ts +74 -0
  278. package/src/action-ui.ts +38 -0
  279. package/src/action.ts +1406 -0
  280. package/src/adapters/cli/index.ts +6 -0
  281. package/src/adapters/cli/registry.ts +73 -0
  282. package/src/adapters/cli/shell-adapter.ts +92 -0
  283. package/src/adapters/cli/types.ts +27 -0
  284. package/src/agent/action-continuation-guidance.ts +38 -0
  285. package/src/agent/app-model-defaults.ts +201 -0
  286. package/src/agent/context-xray/actions/context-evict.ts +56 -0
  287. package/src/agent/context-xray/actions/context-manifest-get.ts +75 -0
  288. package/src/agent/context-xray/actions/context-pin.ts +56 -0
  289. package/src/agent/context-xray/actions/context-preview-get.ts +134 -0
  290. package/src/agent/context-xray/actions/context-report.ts +75 -0
  291. package/src/agent/context-xray/actions/context-restore.ts +51 -0
  292. package/src/agent/context-xray/actions/errors.ts +40 -0
  293. package/src/agent/context-xray/apply-directives.ts +137 -0
  294. package/src/agent/context-xray/directives-store.ts +178 -0
  295. package/src/agent/context-xray/identity.ts +37 -0
  296. package/src/agent/context-xray/manifest.ts +284 -0
  297. package/src/agent/context-xray/migrations.ts +32 -0
  298. package/src/agent/context-xray/plugin.ts +23 -0
  299. package/src/agent/context-xray/schema.ts +17 -0
  300. package/src/agent/context-xray/segments.ts +194 -0
  301. package/src/agent/context-xray/tokenize.ts +93 -0
  302. package/src/agent/default-model.ts +5 -0
  303. package/src/agent/durable-background.ts +547 -0
  304. package/src/agent/engine/ai-sdk-engine.ts +565 -0
  305. package/src/agent/engine/anthropic-engine.ts +320 -0
  306. package/src/agent/engine/builder-engine.ts +1110 -0
  307. package/src/agent/engine/builder-gateway-headers.ts +44 -0
  308. package/src/agent/engine/builtin.ts +122 -0
  309. package/src/agent/engine/context-directives-transform.ts +73 -0
  310. package/src/agent/engine/continuation-dispatch-retry.ts +168 -0
  311. package/src/agent/engine/credential-errors.ts +69 -0
  312. package/src/agent/engine/index.ts +54 -0
  313. package/src/agent/engine/openai-compatible-endpoint.ts +25 -0
  314. package/src/agent/engine/output-tokens.ts +181 -0
  315. package/src/agent/engine/provider-env-vars.ts +69 -0
  316. package/src/agent/engine/registry.ts +853 -0
  317. package/src/agent/engine/tool-call-journal-seed.ts +85 -0
  318. package/src/agent/engine/translate-ai-sdk.ts +407 -0
  319. package/src/agent/engine/translate-anthropic.ts +611 -0
  320. package/src/agent/engine/types.ts +310 -0
  321. package/src/agent/harness/acp-adapter.ts +893 -0
  322. package/src/agent/harness/acp-builtin.ts +70 -0
  323. package/src/agent/harness/ai-sdk-adapter.ts +509 -0
  324. package/src/agent/harness/background.ts +365 -0
  325. package/src/agent/harness/builtin.ts +50 -0
  326. package/src/agent/harness/index.ts +86 -0
  327. package/src/agent/harness/lifecycle.ts +498 -0
  328. package/src/agent/harness/registry.ts +97 -0
  329. package/src/agent/harness/runner.ts +182 -0
  330. package/src/agent/harness/store.ts +466 -0
  331. package/src/agent/harness/translate.ts +130 -0
  332. package/src/agent/harness/types.ts +129 -0
  333. package/src/agent/index.ts +32 -0
  334. package/src/agent/loop-settings.ts +186 -0
  335. package/src/agent/model-config.ts +363 -0
  336. package/src/agent/observational-memory/compactor.ts +78 -0
  337. package/src/agent/observational-memory/config.ts +78 -0
  338. package/src/agent/observational-memory/index.ts +79 -0
  339. package/src/agent/observational-memory/internal-run.ts +89 -0
  340. package/src/agent/observational-memory/message-text.ts +49 -0
  341. package/src/agent/observational-memory/migrations.ts +44 -0
  342. package/src/agent/observational-memory/observer.ts +128 -0
  343. package/src/agent/observational-memory/plugin.ts +35 -0
  344. package/src/agent/observational-memory/prompts.ts +53 -0
  345. package/src/agent/observational-memory/read.ts +131 -0
  346. package/src/agent/observational-memory/reflector.ts +119 -0
  347. package/src/agent/observational-memory/schema.ts +48 -0
  348. package/src/agent/observational-memory/store.ts +275 -0
  349. package/src/agent/observational-memory/types.ts +65 -0
  350. package/src/agent/processors.ts +209 -0
  351. package/src/agent/production-agent.ts +8254 -0
  352. package/src/agent/run-loop-with-resume.ts +394 -0
  353. package/src/agent/run-manager.ts +1825 -0
  354. package/src/agent/run-ownership.ts +75 -0
  355. package/src/agent/run-store.ts +2601 -0
  356. package/src/agent/runtime-context.ts +169 -0
  357. package/src/agent/thread-data-builder.ts +1584 -0
  358. package/src/agent/tool-call-journal.ts +368 -0
  359. package/src/agent/tool-error-redaction.ts +52 -0
  360. package/src/agent/tool-result-images.ts +178 -0
  361. package/src/agent/tool-search.ts +376 -0
  362. package/src/agent/types.ts +372 -0
  363. package/src/agent-native/index.ts +198 -0
  364. package/src/agent-web/config.ts +253 -0
  365. package/src/agent-web/generator.ts +382 -0
  366. package/src/agent-web/index.ts +36 -0
  367. package/src/appearance/actions/change-appearance.ts +35 -0
  368. package/src/application-state/emitter.ts +81 -0
  369. package/src/application-state/handlers.ts +121 -0
  370. package/src/application-state/index.ts +44 -0
  371. package/src/application-state/script-helpers.ts +155 -0
  372. package/src/application-state/store.ts +274 -0
  373. package/src/assets/branding/agent-native.icon/Assets/background.svg +3 -0
  374. package/src/assets/branding/agent-native.icon/Assets/foreground.svg +12 -0
  375. package/src/assets/branding/agent-native.icon/icon.json +49 -0
  376. package/src/assets/branding/favicon.png +0 -0
  377. package/src/assets/branding/favicon.svg +1 -0
  378. package/src/assets/branding/icon-on-dark.svg +10 -0
  379. package/src/assets/branding/icon-on-light.svg +10 -0
  380. package/src/assets/branding/mac-app-icon.svg +1 -0
  381. package/src/assets/branding/slack-bot/agent-native-1024.png +0 -0
  382. package/src/assets/branding/slack-bot/agent-native-512.png +0 -0
  383. package/src/assets/branding/tray-icon.svg +6 -0
  384. package/src/assets/branding/wordmark-on-dark.svg +21 -0
  385. package/src/assets/branding/wordmark-on-light.svg +21 -0
  386. package/src/assets/fonts/LICENSE_LIBERATION +96 -0
  387. package/src/assets/fonts/LICENSE_NOTO_NASKH_ARABIC +93 -0
  388. package/src/assets/fonts/LiberationSans-Bold.ttf +0 -0
  389. package/src/assets/fonts/LiberationSans-Regular.ttf +0 -0
  390. package/src/assets/fonts/NotoNaskhArabic-Variable.ttf +0 -0
  391. package/src/audit/actions/export-audit-events.ts +162 -0
  392. package/src/audit/actions/get-audit-event.ts +24 -0
  393. package/src/audit/actions/list-audit-events.ts +66 -0
  394. package/src/audit/cleanup-job.ts +100 -0
  395. package/src/audit/config.ts +90 -0
  396. package/src/audit/index.ts +43 -0
  397. package/src/audit/record.ts +180 -0
  398. package/src/audit/redact.ts +109 -0
  399. package/src/audit/store.ts +346 -0
  400. package/src/audit/types.ts +141 -0
  401. package/src/automation/index.ts +802 -0
  402. package/src/brand-kit/brand-signals.ts +124 -0
  403. package/src/brand-kit/fig/index.ts +64 -0
  404. package/src/brand-kit/index.ts +72 -0
  405. package/src/brand-kit/types.ts +113 -0
  406. package/src/browser-sessions/actions.ts +302 -0
  407. package/src/browser-sessions/routes.ts +234 -0
  408. package/src/browser-sessions/store.ts +679 -0
  409. package/src/browser-sessions/types.ts +84 -0
  410. package/src/changelog/parse.ts +328 -0
  411. package/src/chat-threads/emitter.ts +22 -0
  412. package/src/chat-threads/schema.ts +43 -0
  413. package/src/chat-threads/store.ts +1311 -0
  414. package/src/checkpoints/index.ts +15 -0
  415. package/src/checkpoints/service.ts +142 -0
  416. package/src/checkpoints/store.ts +153 -0
  417. package/src/cli/add.ts +425 -0
  418. package/src/cli/agent.ts +424 -0
  419. package/src/cli/agents.ts +122 -0
  420. package/src/cli/app-skill.ts +1455 -0
  421. package/src/cli/atomic-json-file.ts +324 -0
  422. package/src/cli/audit-agent-web.ts +324 -0
  423. package/src/cli/changelog.ts +199 -0
  424. package/src/cli/claude-code-participant.ts +515 -0
  425. package/src/cli/code-agent-commands.ts +270 -0
  426. package/src/cli/code-agent-connector.ts +1109 -0
  427. package/src/cli/code-agent-executor.ts +1970 -0
  428. package/src/cli/code-agent-output-smoother.ts +140 -0
  429. package/src/cli/code-agent-runs.concurrent-worker.ts +33 -0
  430. package/src/cli/code-agent-runs.ts +548 -0
  431. package/src/cli/code.ts +1805 -0
  432. package/src/cli/codex-cli-participant.ts +496 -0
  433. package/src/cli/connect.ts +2714 -0
  434. package/src/cli/content-local.ts +442 -0
  435. package/src/cli/context-xray-local.ts +2193 -0
  436. package/src/cli/create-workspace.ts +30 -0
  437. package/src/cli/create.ts +2247 -0
  438. package/src/cli/deploy-build.ts +50 -0
  439. package/src/cli/design-connect.ts +3187 -0
  440. package/src/cli/doctor.ts +472 -0
  441. package/src/cli/eject.ts +1594 -0
  442. package/src/cli/eval.ts +134 -0
  443. package/src/cli/gateway-helpers.ts +70 -0
  444. package/src/cli/index.ts +1217 -0
  445. package/src/cli/info.ts +105 -0
  446. package/src/cli/invoke.ts +288 -0
  447. package/src/cli/mcp-config-writers.ts +857 -0
  448. package/src/cli/mcp.ts +594 -0
  449. package/src/cli/migrate.ts +1813 -0
  450. package/src/cli/migration-codemod.ts +646 -0
  451. package/src/cli/multi-frontier-runs.ts +1952 -0
  452. package/src/cli/package-lifecycle.ts +823 -0
  453. package/src/cli/plan-blocks.ts +143 -0
  454. package/src/cli/plan-local.ts +3189 -0
  455. package/src/cli/plan-publish-store.ts +138 -0
  456. package/src/cli/pr-visual-recap-workflow.ts +2 -0
  457. package/src/cli/recap.ts +5 -0
  458. package/src/cli/setup-agents.ts +120 -0
  459. package/src/cli/skills-content/assets-skill.ts +83 -0
  460. package/src/cli/skills-content/canvas.ts +153 -0
  461. package/src/cli/skills-content/connection.ts +52 -0
  462. package/src/cli/skills-content/content-skill.ts +120 -0
  463. package/src/cli/skills-content/design-exploration-skill.ts +118 -0
  464. package/src/cli/skills-content/design-visual-edit-skill.ts +228 -0
  465. package/src/cli/skills-content/document-quality.ts +196 -0
  466. package/src/cli/skills-content/exemplar.ts +69 -0
  467. package/src/cli/skills-content/help.ts +77 -0
  468. package/src/cli/skills-content/index.ts +22 -0
  469. package/src/cli/skills-content/local-files.ts +100 -0
  470. package/src/cli/skills-content/plan-setup-auth.ts +80 -0
  471. package/src/cli/skills-content/visual-plan-skill.ts +493 -0
  472. package/src/cli/skills-content/visual-recap-skill.ts +553 -0
  473. package/src/cli/skills-content/visualize-repo-skill.ts +93 -0
  474. package/src/cli/skills-content/wireframe.ts +349 -0
  475. package/src/cli/skills.ts +4340 -0
  476. package/src/cli/sync-builder-starter-manifest.ts +631 -0
  477. package/src/cli/telemetry.ts +136 -0
  478. package/src/cli/templates-meta.ts +269 -0
  479. package/src/cli/upgrade.ts +956 -0
  480. package/src/cli/visualize-repo.ts +715 -0
  481. package/src/cli/workspace-dev.ts +1486 -0
  482. package/src/cli/workspacify.ts +258 -0
  483. package/src/client/AgentAskPopover.tsx +89 -0
  484. package/src/client/AgentChatHome.tsx +66 -0
  485. package/src/client/AgentNative.tsx +161 -0
  486. package/src/client/AgentNativeEmbedded.tsx +288 -0
  487. package/src/client/AgentNativeFrame.tsx +151 -0
  488. package/src/client/AgentPanel.tsx +3437 -0
  489. package/src/client/AgentTaskCard.tsx +273 -0
  490. package/src/client/AppearancePicker.tsx +79 -0
  491. package/src/client/AssistantChat.tsx +5793 -0
  492. package/src/client/ClientOnly.tsx +22 -0
  493. package/src/client/CommandMenu.tsx +726 -0
  494. package/src/client/ConnectBuilderCard.tsx +390 -0
  495. package/src/client/DefaultSpinner.tsx +47 -0
  496. package/src/client/ErrorBoundary.tsx +359 -0
  497. package/src/client/ErrorReportActions.tsx +69 -0
  498. package/src/client/FeedbackButton.tsx +553 -0
  499. package/src/client/HighlightedCodeBlock.tsx +173 -0
  500. package/src/client/IframeEmbed.tsx +151 -0
  501. package/src/client/KeepTabOpenNotice.tsx +181 -0
  502. package/src/client/MultiTabAssistantChat.tsx +2576 -0
  503. package/src/client/NewWorkspaceAppFlow.tsx +525 -0
  504. package/src/client/PoweredByBadge.tsx +284 -0
  505. package/src/client/RunStuckBanner.tsx +431 -0
  506. package/src/client/StarfieldBackground.tsx +466 -0
  507. package/src/client/Turnstile.tsx +125 -0
  508. package/src/client/active-run-state.ts +139 -0
  509. package/src/client/agent-chat/index.ts +279 -0
  510. package/src/client/agent-chat-adapter.ts +3692 -0
  511. package/src/client/agent-chat.ts +1233 -0
  512. package/src/client/agent-engine-key.ts +122 -0
  513. package/src/client/agent-page/AgentContextTab.tsx +487 -0
  514. package/src/client/agent-page/AgentEmptyState.tsx +44 -0
  515. package/src/client/agent-page/AgentJobsTab.tsx +441 -0
  516. package/src/client/agent-page/AgentTabFrame.tsx +39 -0
  517. package/src/client/agent-page/AgentTabsPage.tsx +1096 -0
  518. package/src/client/agent-page/types.ts +18 -0
  519. package/src/client/agent-page/use-jobs.ts +150 -0
  520. package/src/client/agent-sidebar-state.ts +166 -0
  521. package/src/client/analytics/index.ts +40 -0
  522. package/src/client/analytics-session.ts +70 -0
  523. package/src/client/analytics.ts +1757 -0
  524. package/src/client/api-path.ts +124 -0
  525. package/src/client/app-providers.tsx +230 -0
  526. package/src/client/appearance.ts +130 -0
  527. package/src/client/application-state.ts +154 -0
  528. package/src/client/assistant-ui-recovery.tsx +205 -0
  529. package/src/client/automation/index.ts +4 -0
  530. package/src/client/automation.ts +22 -0
  531. package/src/client/blocks/AiEditableField.tsx +30 -0
  532. package/src/client/blocks/BlockView.tsx +145 -0
  533. package/src/client/blocks/RegistryBlockDataProvider.tsx +121 -0
  534. package/src/client/blocks/SchemaBlockEditor.tsx +457 -0
  535. package/src/client/blocks/agent.ts +95 -0
  536. package/src/client/blocks/code-block-direction.ts +21 -0
  537. package/src/client/blocks/index.ts +308 -0
  538. package/src/client/blocks/library/AnnotatedCodeBlock.tsx +710 -0
  539. package/src/client/blocks/library/ApiEndpointBlock.tsx +1120 -0
  540. package/src/client/blocks/library/DataModelBlock.tsx +775 -0
  541. package/src/client/blocks/library/DiffBlock.tsx +1854 -0
  542. package/src/client/blocks/library/FileTreeBlock.tsx +868 -0
  543. package/src/client/blocks/library/HighlightedCode.tsx +260 -0
  544. package/src/client/blocks/library/JsonExplorerBlock.tsx +608 -0
  545. package/src/client/blocks/library/MermaidBlock.tsx +362 -0
  546. package/src/client/blocks/library/OpenApiSpecBlock.tsx +998 -0
  547. package/src/client/blocks/library/annotated-code.config.ts +101 -0
  548. package/src/client/blocks/library/annotation-rail.tsx +1020 -0
  549. package/src/client/blocks/library/api-endpoint.config.ts +207 -0
  550. package/src/client/blocks/library/block-copy.ts +399 -0
  551. package/src/client/blocks/library/callout.config.ts +57 -0
  552. package/src/client/blocks/library/callout.tsx +149 -0
  553. package/src/client/blocks/library/checklist.config.ts +56 -0
  554. package/src/client/blocks/library/checklist.tsx +208 -0
  555. package/src/client/blocks/library/code-filename-label.tsx +59 -0
  556. package/src/client/blocks/library/code-highlight.tsx +178 -0
  557. package/src/client/blocks/library/code-tabs.config.ts +70 -0
  558. package/src/client/blocks/library/code-tabs.tsx +606 -0
  559. package/src/client/blocks/library/code.config.ts +69 -0
  560. package/src/client/blocks/library/code.tsx +440 -0
  561. package/src/client/blocks/library/columns.config.ts +85 -0
  562. package/src/client/blocks/library/columns.tsx +334 -0
  563. package/src/client/blocks/library/data-model.config.ts +152 -0
  564. package/src/client/blocks/library/dev-doc-ui.tsx +191 -0
  565. package/src/client/blocks/library/diagram.config.ts +222 -0
  566. package/src/client/blocks/library/diagram.tsx +958 -0
  567. package/src/client/blocks/library/diff.config.ts +115 -0
  568. package/src/client/blocks/library/file-tree.config.ts +92 -0
  569. package/src/client/blocks/library/html.config.ts +79 -0
  570. package/src/client/blocks/library/html.tsx +258 -0
  571. package/src/client/blocks/library/json-explorer.config.ts +74 -0
  572. package/src/client/blocks/library/mermaid.config.ts +49 -0
  573. package/src/client/blocks/library/narrow-container.tsx +37 -0
  574. package/src/client/blocks/library/openapi-spec.config.ts +66 -0
  575. package/src/client/blocks/library/question-form.config.ts +127 -0
  576. package/src/client/blocks/library/question-form.tsx +944 -0
  577. package/src/client/blocks/library/sanitize-html.ts +343 -0
  578. package/src/client/blocks/library/server-specs.ts +338 -0
  579. package/src/client/blocks/library/specs.tsx +308 -0
  580. package/src/client/blocks/library/table.config.ts +61 -0
  581. package/src/client/blocks/library/table.tsx +449 -0
  582. package/src/client/blocks/library/tabs.config.ts +96 -0
  583. package/src/client/blocks/library/tabs.tsx +665 -0
  584. package/src/client/blocks/library/wireframe-icons.ts +210 -0
  585. package/src/client/blocks/library/wireframe-kit.tsx +1850 -0
  586. package/src/client/blocks/library/wireframe.config.ts +539 -0
  587. package/src/client/blocks/library/wireframe.tsx +535 -0
  588. package/src/client/blocks/mdx.ts +364 -0
  589. package/src/client/blocks/provider.tsx +51 -0
  590. package/src/client/blocks/registry.ts +83 -0
  591. package/src/client/blocks/schema-form/introspect.ts +201 -0
  592. package/src/client/blocks/server.ts +170 -0
  593. package/src/client/blocks/types.ts +415 -0
  594. package/src/client/browser-session-bridge.ts +548 -0
  595. package/src/client/browser-tab-id.ts +36 -0
  596. package/src/client/builder-frame.ts +194 -0
  597. package/src/client/builder-mark.tsx +21 -0
  598. package/src/client/changelog/Changelog.tsx +333 -0
  599. package/src/client/changelog/index.ts +1 -0
  600. package/src/client/chat/ChatHistoryList.tsx +7 -0
  601. package/src/client/chat/attachment-adapters.ts +426 -0
  602. package/src/client/chat/connectors.ts +1579 -0
  603. package/src/client/chat/index.ts +160 -0
  604. package/src/client/chat/markdown-renderer.tsx +693 -0
  605. package/src/client/chat/message-components.tsx +1326 -0
  606. package/src/client/chat/repo-helpers.ts +281 -0
  607. package/src/client/chat/run-recovery.tsx +994 -0
  608. package/src/client/chat/runtime.ts +1810 -0
  609. package/src/client/chat/tool-call-display.tsx +1270 -0
  610. package/src/client/chat/tool-render-registry.tsx +144 -0
  611. package/src/client/chat/use-agent-chat-lifecycle-tracking.ts +74 -0
  612. package/src/client/chat/use-reconnect-reader-owner.ts +28 -0
  613. package/src/client/chat/widgets/DataChartRenderer.tsx +242 -0
  614. package/src/client/chat/widgets/DataChartWidget.tsx +51 -0
  615. package/src/client/chat/widgets/DataInsightsWidget.tsx +59 -0
  616. package/src/client/chat/widgets/DataTableWidget.tsx +240 -0
  617. package/src/client/chat/widgets/InlineExtensionWidget.tsx +60 -0
  618. package/src/client/chat/widgets/builtin-tool-renderers.tsx +213 -0
  619. package/src/client/chat/widgets/data-widget-types.ts +1 -0
  620. package/src/client/chat/widgets/inline-extension-result.ts +86 -0
  621. package/src/client/chat-model-groups.ts +171 -0
  622. package/src/client/chat-thread-url.ts +11 -0
  623. package/src/client/chat-view-transition.ts +213 -0
  624. package/src/client/client-action.ts +21 -0
  625. package/src/client/client-bootstrap.ts +7 -0
  626. package/src/client/client-surface.ts +41 -0
  627. package/src/client/clipboard.ts +89 -0
  628. package/src/client/code-agent-chat-adapter.ts +537 -0
  629. package/src/client/collab/index.ts +60 -0
  630. package/src/client/command-navigation/index.ts +15 -0
  631. package/src/client/comments-review/index.ts +1 -0
  632. package/src/client/components/AgentPresenceChip.tsx +4 -0
  633. package/src/client/components/ApiKeySettings.tsx +309 -0
  634. package/src/client/components/CodeAgentIndicator.tsx +59 -0
  635. package/src/client/components/CodeRequiredDialog.tsx +470 -0
  636. package/src/client/components/LiveCursorOverlay.tsx +5 -0
  637. package/src/client/components/MissingKeyCard.tsx +61 -0
  638. package/src/client/components/PresenceBar.tsx +4 -0
  639. package/src/client/components/RecentEditHighlights.tsx +4 -0
  640. package/src/client/components/RemoteSelectionRings.tsx +5 -0
  641. package/src/client/components/icons/AgentNativeIcon.tsx +46 -0
  642. package/src/client/components/ui/dialog.tsx +1 -0
  643. package/src/client/components/ui/dropdown-menu.tsx +1 -0
  644. package/src/client/components/ui/hover-card.tsx +1 -0
  645. package/src/client/components/ui/message-scroller.tsx +131 -0
  646. package/src/client/components/ui/popover.tsx +1 -0
  647. package/src/client/components/ui/sheet.tsx +1 -0
  648. package/src/client/components/ui/tooltip.tsx +1 -0
  649. package/src/client/composer/index.ts +11 -0
  650. package/src/client/composer/runtime-adapters.tsx +123 -0
  651. package/src/client/composer/use-mention-search.ts +7 -0
  652. package/src/client/composer/wired-components.tsx +53 -0
  653. package/src/client/context-xray/ContextMeter.tsx +124 -0
  654. package/src/client/context-xray/ContextSegmentRow.tsx +27 -0
  655. package/src/client/context-xray/ContextTreemap.tsx +24 -0
  656. package/src/client/context-xray/ContextXRayPanel.tsx +59 -0
  657. package/src/client/context-xray/SegmentProvenancePopover.tsx +18 -0
  658. package/src/client/context-xray/format.ts +27 -0
  659. package/src/client/conversation/AgentConversation.tsx +555 -0
  660. package/src/client/conversation/code-agent-transcript.ts +283 -0
  661. package/src/client/conversation/index.ts +22 -0
  662. package/src/client/conversation/types.ts +85 -0
  663. package/src/client/conversation/use-near-bottom-autoscroll.ts +278 -0
  664. package/src/client/create-query-client.ts +86 -0
  665. package/src/client/db-admin/DataGrid.tsx +551 -0
  666. package/src/client/db-admin/DbAdminPage.tsx +218 -0
  667. package/src/client/db-admin/DevDatabaseLink.tsx +16 -0
  668. package/src/client/db-admin/EditableCell.tsx +451 -0
  669. package/src/client/db-admin/FilterBar.tsx +203 -0
  670. package/src/client/db-admin/ResultsGrid.tsx +114 -0
  671. package/src/client/db-admin/RowSidePanel.tsx +337 -0
  672. package/src/client/db-admin/SqlEditor.tsx +840 -0
  673. package/src/client/db-admin/TableBrowser.tsx +212 -0
  674. package/src/client/db-admin/TableEditor.tsx +760 -0
  675. package/src/client/db-admin/cell-format.ts +246 -0
  676. package/src/client/db-admin/changeset.ts +296 -0
  677. package/src/client/db-admin/export-utils.ts +78 -0
  678. package/src/client/db-admin/index.ts +33 -0
  679. package/src/client/db-admin/sql-storage.ts +134 -0
  680. package/src/client/db-admin/storage.ts +68 -0
  681. package/src/client/db-admin/useAgentSync.ts +226 -0
  682. package/src/client/db-admin/useDbAdmin.ts +294 -0
  683. package/src/client/dev-overlay/DevOverlay.tsx +576 -0
  684. package/src/client/dev-overlay/builtins.ts +40 -0
  685. package/src/client/dev-overlay/index.ts +23 -0
  686. package/src/client/dev-overlay/registry.ts +67 -0
  687. package/src/client/dev-overlay/types.ts +64 -0
  688. package/src/client/dev-overlay/use-dev-option.ts +78 -0
  689. package/src/client/dev-overlay/use-dev-overlay-shortcut.ts +31 -0
  690. package/src/client/dynamic-suggestions.ts +455 -0
  691. package/src/client/embed-auth.ts +544 -0
  692. package/src/client/embed.ts +54 -0
  693. package/src/client/error-capture.ts +690 -0
  694. package/src/client/error-format.ts +211 -0
  695. package/src/client/error-reporting.ts +100 -0
  696. package/src/client/extensions/AgentNativeExtensionFrame.e2e-host.tsx +136 -0
  697. package/src/client/extensions/AgentNativeExtensionFrame.tsx +708 -0
  698. package/src/client/extensions/EmbeddedExtension.tsx +604 -0
  699. package/src/client/extensions/ExtensionEditor.tsx +450 -0
  700. package/src/client/extensions/ExtensionQueryErrorState.tsx +47 -0
  701. package/src/client/extensions/ExtensionSlot.tsx +345 -0
  702. package/src/client/extensions/ExtensionViewer.tsx +1502 -0
  703. package/src/client/extensions/ExtensionViewerPage.tsx +32 -0
  704. package/src/client/extensions/ExtensionsListPage.tsx +432 -0
  705. package/src/client/extensions/ExtensionsSidebarSection.tsx +1259 -0
  706. package/src/client/extensions/InlineExtensionFrame.tsx +660 -0
  707. package/src/client/extensions/agent-native-extension-runtime.ts +495 -0
  708. package/src/client/extensions/delete-extension.ts +76 -0
  709. package/src/client/extensions/extension-load-error.ts +26 -0
  710. package/src/client/extensions/extension-order.ts +49 -0
  711. package/src/client/extensions/extension-popularity.ts +61 -0
  712. package/src/client/extensions/iframe-bridge.ts +377 -0
  713. package/src/client/extensions/index.ts +84 -0
  714. package/src/client/extensions/portable-extension.ts +712 -0
  715. package/src/client/feature-flags/FeatureFlagsPanel.tsx +519 -0
  716. package/src/client/feature-flags/helpers.ts +77 -0
  717. package/src/client/feature-flags/index.ts +13 -0
  718. package/src/client/feature-flags/types.ts +29 -0
  719. package/src/client/feature-flags/use-feature-flag.ts +26 -0
  720. package/src/client/feedback-context.ts +90 -0
  721. package/src/client/frame-protocol.ts +168 -0
  722. package/src/client/frame.ts +329 -0
  723. package/src/client/guided-questions.tsx +1152 -0
  724. package/src/client/history/VersionHistoryPanel.tsx +175 -0
  725. package/src/client/history/index.ts +21 -0
  726. package/src/client/history/use-history.ts +114 -0
  727. package/src/client/hooks/index.ts +46 -0
  728. package/src/client/host/index.ts +170 -0
  729. package/src/client/host-bridge.ts +1272 -0
  730. package/src/client/host-tools.ts +190 -0
  731. package/src/client/i18n.tsx +798 -0
  732. package/src/client/index.ts +370 -0
  733. package/src/client/integrations/IntegrationCard.tsx +186 -0
  734. package/src/client/integrations/IntegrationsPanel.tsx +633 -0
  735. package/src/client/integrations/api.ts +295 -0
  736. package/src/client/integrations/index.ts +29 -0
  737. package/src/client/integrations/useIntegrationStatus.ts +52 -0
  738. package/src/client/mcp-app-host.ts +720 -0
  739. package/src/client/mcp-apps/McpAppRenderer.tsx +760 -0
  740. package/src/client/navigation/index.ts +54 -0
  741. package/src/client/notifications/NotificationsBell.tsx +383 -0
  742. package/src/client/notifications/index.ts +1 -0
  743. package/src/client/observability/ObservabilityDashboard.tsx +890 -0
  744. package/src/client/observability/ThumbsFeedback.tsx +160 -0
  745. package/src/client/observability/index.ts +20 -0
  746. package/src/client/observability/useObservability.ts +266 -0
  747. package/src/client/onboarding/OnboardingBanner.tsx +73 -0
  748. package/src/client/onboarding/OnboardingPanel.tsx +1036 -0
  749. package/src/client/onboarding/SetupButton.tsx +64 -0
  750. package/src/client/onboarding/index.ts +25 -0
  751. package/src/client/onboarding/use-onboarding.ts +164 -0
  752. package/src/client/onboarding/use-preview-mode.ts +40 -0
  753. package/src/client/org/InvitationBanner.tsx +148 -0
  754. package/src/client/org/OrgSwitcher.tsx +754 -0
  755. package/src/client/org/RequireActiveOrg.tsx +337 -0
  756. package/src/client/org/TeamPage.tsx +1432 -0
  757. package/src/client/org/hooks.ts +359 -0
  758. package/src/client/org/index.ts +70 -0
  759. package/src/client/org/workspace-app-links.ts +367 -0
  760. package/src/client/org-team/index.ts +1 -0
  761. package/src/client/progress/RunsTray.tsx +680 -0
  762. package/src/client/progress/index.ts +1 -0
  763. package/src/client/require-session.tsx +142 -0
  764. package/src/client/resources/BuiltinCapabilityDetail.tsx +258 -0
  765. package/src/client/resources/McpConnectionSuggestion.tsx +276 -0
  766. package/src/client/resources/McpIntegrationDialog.tsx +808 -0
  767. package/src/client/resources/McpServerDetail.tsx +230 -0
  768. package/src/client/resources/ResourceEditor.tsx +872 -0
  769. package/src/client/resources/ResourceTree.tsx +860 -0
  770. package/src/client/resources/ResourcesPanel.tsx +1960 -0
  771. package/src/client/resources/index.ts +25 -0
  772. package/src/client/resources/mcp-integration-catalog.ts +771 -0
  773. package/src/client/resources/mcp-integration-logos.ts +108 -0
  774. package/src/client/resources/runtime.ts +1 -0
  775. package/src/client/resources/use-builtin-capabilities.ts +113 -0
  776. package/src/client/resources/use-mcp-servers.ts +217 -0
  777. package/src/client/resources/use-resources.ts +402 -0
  778. package/src/client/review/ReviewCommentComposer.tsx +123 -0
  779. package/src/client/review/ReviewStatusBadge.tsx +39 -0
  780. package/src/client/review/ReviewThreadPanel.tsx +625 -0
  781. package/src/client/review/index.ts +38 -0
  782. package/src/client/review/use-review.ts +186 -0
  783. package/src/client/route-chunk-recovery/index.ts +1 -0
  784. package/src/client/route-chunk-recovery.ts +331 -0
  785. package/src/client/route-state.ts +455 -0
  786. package/src/client/route-warmup.tsx +543 -0
  787. package/src/client/session-replay-iframe.e2e-host.tsx +100 -0
  788. package/src/client/session-replay.ts +3417 -0
  789. package/src/client/settings/AgentsSection.tsx +441 -0
  790. package/src/client/settings/AutomationsSection.tsx +516 -0
  791. package/src/client/settings/BackgroundAgentSection.tsx +189 -0
  792. package/src/client/settings/BrowserSection.tsx +110 -0
  793. package/src/client/settings/DemoModeSection.tsx +48 -0
  794. package/src/client/settings/SecretsSection.tsx +875 -0
  795. package/src/client/settings/SettingsPanel.tsx +3162 -0
  796. package/src/client/settings/SettingsSection.tsx +283 -0
  797. package/src/client/settings/SettingsTabsPage.tsx +592 -0
  798. package/src/client/settings/UsageSection.tsx +401 -0
  799. package/src/client/settings/VoiceTranscriptionSection.tsx +960 -0
  800. package/src/client/settings/agent-settings-search.ts +220 -0
  801. package/src/client/settings/index.ts +27 -0
  802. package/src/client/settings/useBuilderStatus.ts +1055 -0
  803. package/src/client/setup-connections/BuilderConnectCard.tsx +89 -0
  804. package/src/client/setup-connections/ProviderReadinessBadge.tsx +73 -0
  805. package/src/client/setup-connections/SetupConnectionsPage.tsx +94 -0
  806. package/src/client/setup-connections/catalog.ts +6 -0
  807. package/src/client/setup-connections/index.ts +47 -0
  808. package/src/client/setup-connections/runtime.ts +1 -0
  809. package/src/client/sharing/ShareButton.tsx +1748 -0
  810. package/src/client/sharing/ShareDialog.tsx +846 -0
  811. package/src/client/sharing/VisibilityBadge.tsx +4 -0
  812. package/src/client/sharing/index.ts +2 -0
  813. package/src/client/sse-event-processor.ts +2002 -0
  814. package/src/client/terminal/AgentTerminal.tsx +488 -0
  815. package/src/client/terminal/index.ts +7 -0
  816. package/src/client/theme.ts +27 -0
  817. package/src/client/tombstone/agent-presence-chip.ts +17 -0
  818. package/src/client/tombstone/editor.ts +265 -0
  819. package/src/client/tombstone/live-cursor-overlay.ts +21 -0
  820. package/src/client/tombstone/presence-bar.ts +17 -0
  821. package/src/client/tombstone/recent-edit-highlights.ts +17 -0
  822. package/src/client/tombstone/remote-selection-rings.ts +21 -0
  823. package/src/client/tombstone/rich-markdown-editor.ts +265 -0
  824. package/src/client/tombstone/ui-dialog.ts +49 -0
  825. package/src/client/tombstone/ui-dropdown-menu.ts +69 -0
  826. package/src/client/tombstone/ui-hover-card.ts +21 -0
  827. package/src/client/tombstone/ui-popover.ts +25 -0
  828. package/src/client/tombstone/ui-sheet.ts +49 -0
  829. package/src/client/tombstone/ui-tooltip.ts +29 -0
  830. package/src/client/tombstone/visual-style-controls.ts +61 -0
  831. package/src/client/tool-cells/BashCell.tsx +164 -0
  832. package/src/client/tool-cells/EditCell.tsx +303 -0
  833. package/src/client/tool-cells/FilesChangedSummary.tsx +208 -0
  834. package/src/client/tool-cells/WriteCell.tsx +145 -0
  835. package/src/client/tool-cells/index.ts +7 -0
  836. package/src/client/tool-display.ts +41 -0
  837. package/src/client/track.ts +57 -0
  838. package/src/client/transcription/BuilderTranscriptionCta.tsx +120 -0
  839. package/src/client/transcription/use-live-transcription.ts +292 -0
  840. package/src/client/ui/index.ts +55 -0
  841. package/src/client/uploads/index.ts +6 -0
  842. package/src/client/uploads/upload-editor-image.ts +79 -0
  843. package/src/client/uploads/use-upload-resource.ts +24 -0
  844. package/src/client/url-scrub.ts +51 -0
  845. package/src/client/use-action.ts +766 -0
  846. package/src/client/use-agent-chat-context.ts +63 -0
  847. package/src/client/use-agent-chat-home-handoff.ts +207 -0
  848. package/src/client/use-agent-chat.ts +53 -0
  849. package/src/client/use-agent-engine-configured.ts +211 -0
  850. package/src/client/use-avatar.ts +127 -0
  851. package/src/client/use-change-version.ts +150 -0
  852. package/src/client/use-chat-models.ts +252 -0
  853. package/src/client/use-chat-threads.ts +1262 -0
  854. package/src/client/use-db-sync.ts +1112 -0
  855. package/src/client/use-demo-mode-status.ts +30 -0
  856. package/src/client/use-dev-mode.ts +177 -0
  857. package/src/client/use-external-value.ts +40 -0
  858. package/src/client/use-pausing-interval.ts +65 -0
  859. package/src/client/use-pinch-zoom.ts +202 -0
  860. package/src/client/use-run-stuck-detection.ts +427 -0
  861. package/src/client/use-send-to-agent-chat.ts +85 -0
  862. package/src/client/use-session.ts +114 -0
  863. package/src/client/utils.ts +7 -0
  864. package/src/client/visual-style-controls.tsx +1 -0
  865. package/src/client/vite-dev-recovery-script.ts +226 -0
  866. package/src/client/voice-provider-status.ts +44 -0
  867. package/src/client/widgets/index.ts +41 -0
  868. package/src/code-agents/background-controller.ts +398 -0
  869. package/src/code-agents/background-run.ts +156 -0
  870. package/src/code-agents/index.ts +95 -0
  871. package/src/code-agents/prompt-attachments.ts +39 -0
  872. package/src/code-agents/transcript-normalizer.ts +750 -0
  873. package/src/code-agents/transcript-order.ts +73 -0
  874. package/src/coding-tools/index.ts +742 -0
  875. package/src/coding-tools/run-code.ts +1662 -0
  876. package/src/coding-tools/sandbox/adapter.ts +82 -0
  877. package/src/coding-tools/sandbox/background.ts +406 -0
  878. package/src/coding-tools/sandbox/executions-store.ts +517 -0
  879. package/src/coding-tools/sandbox/index.ts +132 -0
  880. package/src/coding-tools/sandbox/local-child-process-adapter.ts +217 -0
  881. package/src/collab/agent-identity.ts +5 -0
  882. package/src/collab/agent-presence.ts +341 -0
  883. package/src/collab/awareness-store.ts +196 -0
  884. package/src/collab/awareness.ts +317 -0
  885. package/src/collab/client-struct.ts +366 -0
  886. package/src/collab/client.ts +1205 -0
  887. package/src/collab/emitter.ts +31 -0
  888. package/src/collab/follow-mode.ts +107 -0
  889. package/src/collab/index.ts +151 -0
  890. package/src/collab/json-to-yjs.ts +504 -0
  891. package/src/collab/presence.ts +207 -0
  892. package/src/collab/recent-edits.ts +215 -0
  893. package/src/collab/routes.ts +191 -0
  894. package/src/collab/storage.ts +228 -0
  895. package/src/collab/struct-routes.ts +133 -0
  896. package/src/collab/text-to-yjs.ts +79 -0
  897. package/src/collab/undo.ts +432 -0
  898. package/src/collab/xml-ops.ts +68 -0
  899. package/src/collab/ydoc-manager.ts +544 -0
  900. package/src/command-navigation/actions.ts +100 -0
  901. package/src/command-navigation/index.ts +12 -0
  902. package/src/comments-review/index.ts +1 -0
  903. package/src/connections/catalog.ts +451 -0
  904. package/src/connections/index.ts +48 -0
  905. package/src/connections/reader.ts +911 -0
  906. package/src/connections/runtime.ts +1 -0
  907. package/src/credentials/index.ts +187 -0
  908. package/src/data-programs/actions.ts +335 -0
  909. package/src/data-programs/contract.ts +284 -0
  910. package/src/data-programs/execute.ts +693 -0
  911. package/src/data-programs/index.ts +72 -0
  912. package/src/data-programs/schema.ts +148 -0
  913. package/src/data-programs/store.ts +755 -0
  914. package/src/data-widgets/index.ts +376 -0
  915. package/src/db/client.ts +1627 -0
  916. package/src/db/create-get-db.ts +579 -0
  917. package/src/db/ddl-guard.ts +302 -0
  918. package/src/db/drizzle-config.ts +190 -0
  919. package/src/db/ensure-additive-columns.ts +430 -0
  920. package/src/db/index.ts +57 -0
  921. package/src/db/migrations.ts +691 -0
  922. package/src/db/request-telemetry.ts +154 -0
  923. package/src/db/runtime-diagnostics.ts +331 -0
  924. package/src/db/schema.ts +121 -0
  925. package/src/db/widen-columns.ts +75 -0
  926. package/src/db-admin/agent-tools.ts +202 -0
  927. package/src/db-admin/index.ts +26 -0
  928. package/src/db-admin/operations.ts +803 -0
  929. package/src/db-admin/routes.ts +174 -0
  930. package/src/db-admin/types.ts +110 -0
  931. package/src/demo/browser-state.ts +47 -0
  932. package/src/demo/fetch-interceptor.ts +175 -0
  933. package/src/demo/redact.ts +575 -0
  934. package/src/deploy/build.ts +3935 -0
  935. package/src/deploy/immutable-assets.ts +65 -0
  936. package/src/deploy/route-discovery.ts +458 -0
  937. package/src/deploy/workspace-core.ts +282 -0
  938. package/src/deploy/workspace-deploy.ts +1488 -0
  939. package/src/eject/provider-api-definitions.ts +193 -0
  940. package/src/eject/remote-mcp-presets.ts +137 -0
  941. package/src/eject/setup-readiness-ui.tsx +29 -0
  942. package/src/eject/workspace-connections.ts +92 -0
  943. package/src/embedding/agent.ts +159 -0
  944. package/src/embedding/bridge.ts +297 -0
  945. package/src/embedding/index.ts +43 -0
  946. package/src/embedding/protocol.ts +185 -0
  947. package/src/embedding/react.tsx +319 -0
  948. package/src/embeddings/index.ts +233 -0
  949. package/src/eval/agent-runner.ts +210 -0
  950. package/src/eval/define-eval.ts +55 -0
  951. package/src/eval/index.ts +49 -0
  952. package/src/eval/report.ts +75 -0
  953. package/src/eval/runner.ts +369 -0
  954. package/src/eval/scorer.ts +244 -0
  955. package/src/eval/types.ts +188 -0
  956. package/src/event-bus/bus.ts +140 -0
  957. package/src/event-bus/index.ts +3 -0
  958. package/src/event-bus/registry.ts +79 -0
  959. package/src/event-bus/types.ts +29 -0
  960. package/src/extensions/actions.ts +1636 -0
  961. package/src/extensions/change-marker.ts +54 -0
  962. package/src/extensions/content-patch.ts +468 -0
  963. package/src/extensions/fetch-tool.ts +461 -0
  964. package/src/extensions/html-shell.ts +803 -0
  965. package/src/extensions/local.ts +462 -0
  966. package/src/extensions/path.ts +40 -0
  967. package/src/extensions/proxy-security.ts +171 -0
  968. package/src/extensions/routes.ts +1080 -0
  969. package/src/extensions/schema.ts +269 -0
  970. package/src/extensions/session-replay-iframe.ts +131 -0
  971. package/src/extensions/slots/routes.ts +126 -0
  972. package/src/extensions/slots/schema.ts +87 -0
  973. package/src/extensions/slots/store.ts +450 -0
  974. package/src/extensions/store.ts +1347 -0
  975. package/src/extensions/theme.ts +114 -0
  976. package/src/extensions/url-safety.ts +316 -0
  977. package/src/extensions/web-content.ts +663 -0
  978. package/src/extensions/web-search-tool.ts +437 -0
  979. package/src/feature-flags/a2a-action-route.ts +56 -0
  980. package/src/feature-flags/actions/get-feature-flags.ts +24 -0
  981. package/src/feature-flags/actions/list-feature-flags.ts +69 -0
  982. package/src/feature-flags/actions/set-feature-flag.ts +89 -0
  983. package/src/feature-flags/index.ts +21 -0
  984. package/src/feature-flags/permissions.ts +53 -0
  985. package/src/feature-flags/plugin.ts +44 -0
  986. package/src/feature-flags/registry.ts +96 -0
  987. package/src/feature-flags/store.ts +176 -0
  988. package/src/file-upload/actions/upload-image.ts +213 -0
  989. package/src/file-upload/builder.ts +431 -0
  990. package/src/file-upload/index.ts +24 -0
  991. package/src/file-upload/pre-upload-attachments.ts +305 -0
  992. package/src/file-upload/registry.ts +125 -0
  993. package/src/file-upload/types.ts +89 -0
  994. package/src/guards/db-tool-scoping.ts +129 -0
  995. package/src/guards/index.ts +23 -0
  996. package/src/guards/no-drizzle-push.ts +116 -0
  997. package/src/guards/no-env-credentials.ts +229 -0
  998. package/src/guards/no-env-mutation.ts +114 -0
  999. package/src/guards/no-localhost-fallback.ts +124 -0
  1000. package/src/guards/no-unscoped-credentials.ts +227 -0
  1001. package/src/guards/no-unscoped-queries.ts +834 -0
  1002. package/src/guards/scan-utils.ts +105 -0
  1003. package/src/guards/types.ts +30 -0
  1004. package/src/history/actions/create-resource-version.ts +92 -0
  1005. package/src/history/actions/get-resource-version.ts +60 -0
  1006. package/src/history/actions/list-resource-history.ts +53 -0
  1007. package/src/history/actions/list-resource-versions.ts +43 -0
  1008. package/src/history/actions/restore-resource-version.ts +96 -0
  1009. package/src/history/index.ts +26 -0
  1010. package/src/history/registry.ts +111 -0
  1011. package/src/history/store.ts +379 -0
  1012. package/src/history/types.ts +69 -0
  1013. package/src/index.browser.ts +155 -0
  1014. package/src/index.ts +408 -0
  1015. package/src/ingestion/docx.ts +158 -0
  1016. package/src/ingestion/figma-node-to-html.ts +1860 -0
  1017. package/src/ingestion/figma.ts +548 -0
  1018. package/src/ingestion/index.ts +107 -0
  1019. package/src/ingestion/media.ts +264 -0
  1020. package/src/ingestion/notion.ts +121 -0
  1021. package/src/ingestion/office.ts +213 -0
  1022. package/src/ingestion/orchestration.ts +160 -0
  1023. package/src/ingestion/pptx.ts +331 -0
  1024. package/src/ingestion/selection.ts +81 -0
  1025. package/src/ingestion/website.ts +151 -0
  1026. package/src/integrations/a2a-continuation-marker.ts +2 -0
  1027. package/src/integrations/a2a-continuation-processor.ts +888 -0
  1028. package/src/integrations/a2a-continuations-store.ts +643 -0
  1029. package/src/integrations/adapter-overrides.ts +12 -0
  1030. package/src/integrations/adapters/discord.ts +364 -0
  1031. package/src/integrations/adapters/email.ts +1164 -0
  1032. package/src/integrations/adapters/google-docs.ts +371 -0
  1033. package/src/integrations/adapters/index.ts +29 -0
  1034. package/src/integrations/adapters/microsoft-teams.ts +413 -0
  1035. package/src/integrations/adapters/slack.ts +1914 -0
  1036. package/src/integrations/adapters/telegram.ts +386 -0
  1037. package/src/integrations/adapters/whatsapp.ts +358 -0
  1038. package/src/integrations/awaiting-input-store.ts +130 -0
  1039. package/src/integrations/catalog.ts +571 -0
  1040. package/src/integrations/computer-supervision-store.ts +423 -0
  1041. package/src/integrations/computer-supervision.ts +213 -0
  1042. package/src/integrations/config-store.ts +131 -0
  1043. package/src/integrations/controls-store.ts +187 -0
  1044. package/src/integrations/eject/messaging-adapters.ts +85 -0
  1045. package/src/integrations/google-docs-poller.ts +671 -0
  1046. package/src/integrations/identity-links-store.ts +210 -0
  1047. package/src/integrations/identity.ts +196 -0
  1048. package/src/integrations/index.ts +220 -0
  1049. package/src/integrations/installations-store.ts +677 -0
  1050. package/src/integrations/integration-memory.ts +187 -0
  1051. package/src/integrations/internal-token.ts +106 -0
  1052. package/src/integrations/pending-tasks-retry-job.ts +286 -0
  1053. package/src/integrations/pending-tasks-store.ts +468 -0
  1054. package/src/integrations/plugin.ts +3048 -0
  1055. package/src/integrations/remote-commands-store.ts +690 -0
  1056. package/src/integrations/remote-devices-store.ts +492 -0
  1057. package/src/integrations/remote-json-safety.ts +127 -0
  1058. package/src/integrations/remote-push-delivery-job.ts +48 -0
  1059. package/src/integrations/remote-push-delivery.ts +370 -0
  1060. package/src/integrations/remote-push-store.ts +664 -0
  1061. package/src/integrations/remote-retry-job.ts +55 -0
  1062. package/src/integrations/remote-run-events-store.ts +222 -0
  1063. package/src/integrations/remote-types.ts +186 -0
  1064. package/src/integrations/runtime.ts +1 -0
  1065. package/src/integrations/scope-store.ts +498 -0
  1066. package/src/integrations/slack-manifest.ts +78 -0
  1067. package/src/integrations/slack-oauth.ts +344 -0
  1068. package/src/integrations/task-queue-stats.ts +144 -0
  1069. package/src/integrations/thread-mapping-store.ts +134 -0
  1070. package/src/integrations/types.ts +485 -0
  1071. package/src/integrations/usage-budget-store.ts +906 -0
  1072. package/src/integrations/webhook-delivery.ts +55 -0
  1073. package/src/integrations/webhook-handler.ts +1939 -0
  1074. package/src/jobs/actions/list-recurring-jobs.ts +105 -0
  1075. package/src/jobs/actions/manage-recurring-job.ts +85 -0
  1076. package/src/jobs/cron.ts +126 -0
  1077. package/src/jobs/index.ts +9 -0
  1078. package/src/jobs/scheduler.ts +703 -0
  1079. package/src/jobs/tools.ts +367 -0
  1080. package/src/local-artifacts/index.ts +1372 -0
  1081. package/src/localization/actions/get-localization-preference.ts +24 -0
  1082. package/src/localization/actions/set-localization-preference.ts +32 -0
  1083. package/src/localization/default-messages.ts +1009 -0
  1084. package/src/localization/index.ts +2 -0
  1085. package/src/localization/server.ts +131 -0
  1086. package/src/localization/shared.ts +208 -0
  1087. package/src/mcp/actions/call-mcp-tool.ts +19 -0
  1088. package/src/mcp/actions/create-org-service-token.ts +87 -0
  1089. package/src/mcp/actions/list-mcp-tools.ts +18 -0
  1090. package/src/mcp/actions/list-org-service-tokens.ts +44 -0
  1091. package/src/mcp/actions/revoke-org-service-token.ts +30 -0
  1092. package/src/mcp/actions/service-token-access.ts +121 -0
  1093. package/src/mcp/ask-app-inline-tasks.ts +125 -0
  1094. package/src/mcp/build-server.ts +2356 -0
  1095. package/src/mcp/builtin-tools.ts +1506 -0
  1096. package/src/mcp/connect-route.ts +1460 -0
  1097. package/src/mcp/connect-store.ts +680 -0
  1098. package/src/mcp/embed-app.ts +2051 -0
  1099. package/src/mcp/embed-route.ts +84 -0
  1100. package/src/mcp/external-agent-policy.ts +18 -0
  1101. package/src/mcp/index.ts +47 -0
  1102. package/src/mcp/oauth-route.ts +1016 -0
  1103. package/src/mcp/oauth-store.ts +581 -0
  1104. package/src/mcp/oauth-token.ts +227 -0
  1105. package/src/mcp/org-directory.ts +343 -0
  1106. package/src/mcp/route-paths.ts +34 -0
  1107. package/src/mcp/screen-memory-stdio.ts +290 -0
  1108. package/src/mcp/server.ts +507 -0
  1109. package/src/mcp/stdio.ts +328 -0
  1110. package/src/mcp/workspace-resolve.ts +261 -0
  1111. package/src/mcp-client/app-api.ts +136 -0
  1112. package/src/mcp-client/app-result.ts +64 -0
  1113. package/src/mcp-client/builtin-capabilities.ts +146 -0
  1114. package/src/mcp-client/builtin-store.ts +93 -0
  1115. package/src/mcp-client/config.ts +281 -0
  1116. package/src/mcp-client/errors.ts +54 -0
  1117. package/src/mcp-client/hub-client.ts +172 -0
  1118. package/src/mcp-client/hub-routes.ts +198 -0
  1119. package/src/mcp-client/index.ts +504 -0
  1120. package/src/mcp-client/manager.ts +968 -0
  1121. package/src/mcp-client/oauth-client.ts +462 -0
  1122. package/src/mcp-client/oauth-routes.ts +307 -0
  1123. package/src/mcp-client/remote-store.ts +674 -0
  1124. package/src/mcp-client/remote-url.ts +97 -0
  1125. package/src/mcp-client/routes.ts +1093 -0
  1126. package/src/mcp-client/screen-memory-local.ts +461 -0
  1127. package/src/mcp-client/tool-policy.ts +182 -0
  1128. package/src/mcp-client/visibility.ts +56 -0
  1129. package/src/mcp-client/workspace-servers.ts +379 -0
  1130. package/src/navigation/index.ts +170 -0
  1131. package/src/notifications/actions.ts +151 -0
  1132. package/src/notifications/channels.ts +410 -0
  1133. package/src/notifications/index.ts +22 -0
  1134. package/src/notifications/registry.ts +216 -0
  1135. package/src/notifications/routes.ts +106 -0
  1136. package/src/notifications/store.ts +236 -0
  1137. package/src/notifications/types.ts +52 -0
  1138. package/src/oauth-tokens/google-refresh.ts +175 -0
  1139. package/src/oauth-tokens/index.ts +15 -0
  1140. package/src/oauth-tokens/store.ts +384 -0
  1141. package/src/observability/cleanup-job.ts +124 -0
  1142. package/src/observability/evals.ts +507 -0
  1143. package/src/observability/experiments.ts +359 -0
  1144. package/src/observability/feedback.ts +332 -0
  1145. package/src/observability/hosted-model-experiment.ts +118 -0
  1146. package/src/observability/index.ts +62 -0
  1147. package/src/observability/plugin.ts +31 -0
  1148. package/src/observability/routes.ts +431 -0
  1149. package/src/observability/sentiment.ts +261 -0
  1150. package/src/observability/store.ts +1347 -0
  1151. package/src/observability/traces.ts +789 -0
  1152. package/src/observability/tracing.ts +157 -0
  1153. package/src/observability/tracking-identity.ts +55 -0
  1154. package/src/observability/types.ts +211 -0
  1155. package/src/onboarding/default-steps.ts +428 -0
  1156. package/src/onboarding/index.ts +22 -0
  1157. package/src/onboarding/plugin.ts +262 -0
  1158. package/src/onboarding/registry.ts +47 -0
  1159. package/src/onboarding/types.ts +96 -0
  1160. package/src/org/accept-pending.ts +98 -0
  1161. package/src/org/auto-join-domain.ts +113 -0
  1162. package/src/org/context.ts +614 -0
  1163. package/src/org/free-email-providers.ts +124 -0
  1164. package/src/org/handlers.ts +1153 -0
  1165. package/src/org/index.ts +63 -0
  1166. package/src/org/migrations.ts +105 -0
  1167. package/src/org/permissions.ts +30 -0
  1168. package/src/org/plugin.ts +255 -0
  1169. package/src/org/schema.ts +28 -0
  1170. package/src/org/types.ts +57 -0
  1171. package/src/org-team/index.ts +1 -0
  1172. package/src/package-lifecycle/deprecated-imports.ts +179 -0
  1173. package/src/package-lifecycle/eject-manifest.ts +345 -0
  1174. package/src/package-lifecycle/index.ts +7 -0
  1175. package/src/package-lifecycle/manifest.ts +120 -0
  1176. package/src/package-lifecycle/migration-manifest.ts +159 -0
  1177. package/src/package-lifecycle/migration-message.ts +6 -0
  1178. package/src/package-lifecycle/tombstone.ts +58 -0
  1179. package/src/package-lifecycle/upgrade-error.ts +37 -0
  1180. package/src/private-blob/index.ts +18 -0
  1181. package/src/private-blob/registry.ts +242 -0
  1182. package/src/private-blob/types.ts +55 -0
  1183. package/src/progress/actions.ts +179 -0
  1184. package/src/progress/index.ts +16 -0
  1185. package/src/progress/registry.ts +127 -0
  1186. package/src/progress/routes.ts +89 -0
  1187. package/src/progress/store.ts +353 -0
  1188. package/src/progress/types.ts +59 -0
  1189. package/src/provider-api/actions/custom-provider-registration.ts +197 -0
  1190. package/src/provider-api/actions/delete-staged-dataset.ts +5 -0
  1191. package/src/provider-api/actions/github-repo-files.ts +266 -0
  1192. package/src/provider-api/actions/list-staged-datasets.ts +5 -0
  1193. package/src/provider-api/actions/provider-api-audit.ts +88 -0
  1194. package/src/provider-api/actions/provider-api.ts +460 -0
  1195. package/src/provider-api/actions/query-staged-dataset.ts +5 -0
  1196. package/src/provider-api/actions/staged-datasets.ts +359 -0
  1197. package/src/provider-api/corpus-jobs-store.ts +522 -0
  1198. package/src/provider-api/corpus-jobs.ts +1817 -0
  1199. package/src/provider-api/custom-registry.ts +456 -0
  1200. package/src/provider-api/github-repo.ts +675 -0
  1201. package/src/provider-api/gong.ts +108 -0
  1202. package/src/provider-api/index.ts +5444 -0
  1203. package/src/provider-api/quota-governor.ts +562 -0
  1204. package/src/provider-api/runtime.ts +1 -0
  1205. package/src/provider-api/staged-datasets-aggregate.ts +304 -0
  1206. package/src/provider-api/staged-datasets-store.ts +441 -0
  1207. package/src/provider-api/staging.ts +483 -0
  1208. package/src/resources/agents.ts +65 -0
  1209. package/src/resources/emitter.ts +54 -0
  1210. package/src/resources/handlers.ts +728 -0
  1211. package/src/resources/index.ts +4 -0
  1212. package/src/resources/metadata.ts +252 -0
  1213. package/src/resources/script-helpers.ts +160 -0
  1214. package/src/resources/store.ts +1618 -0
  1215. package/src/review/actions/consume-review-feedback.ts +48 -0
  1216. package/src/review/actions/create-review-comment.ts +108 -0
  1217. package/src/review/actions/delete-review-comment.ts +67 -0
  1218. package/src/review/actions/get-review-feedback.ts +49 -0
  1219. package/src/review/actions/list-review-comments.ts +87 -0
  1220. package/src/review/actions/reply-review-comment.ts +118 -0
  1221. package/src/review/actions/resolve-review-thread.ts +94 -0
  1222. package/src/review/actions/send-review-thread-to-agent.ts +62 -0
  1223. package/src/review/actions/set-review-status.ts +58 -0
  1224. package/src/review/identity.ts +101 -0
  1225. package/src/review/index.ts +45 -0
  1226. package/src/review/mentions.ts +44 -0
  1227. package/src/review/registry.ts +111 -0
  1228. package/src/review/store.ts +997 -0
  1229. package/src/review/types.ts +101 -0
  1230. package/src/router/index.ts +24 -0
  1231. package/src/scripts/agent-engines/list-agent-engines.ts +130 -0
  1232. package/src/scripts/agent-engines/manage-agent-engine.ts +197 -0
  1233. package/src/scripts/agent-engines/set-agent-engine.ts +84 -0
  1234. package/src/scripts/agent-engines/test-agent-engine.ts +174 -0
  1235. package/src/scripts/call-agent.ts +792 -0
  1236. package/src/scripts/chat/index.ts +8 -0
  1237. package/src/scripts/chat/open-chat.ts +56 -0
  1238. package/src/scripts/chat/search-chats.ts +121 -0
  1239. package/src/scripts/core-scripts.ts +22 -0
  1240. package/src/scripts/db/check-scoping.ts +212 -0
  1241. package/src/scripts/db/exec.ts +791 -0
  1242. package/src/scripts/db/index.ts +19 -0
  1243. package/src/scripts/db/migrate-encrypt-credentials.ts +223 -0
  1244. package/src/scripts/db/migrate-encrypt-oauth-tokens.ts +213 -0
  1245. package/src/scripts/db/migrate-user-api-keys.ts +257 -0
  1246. package/src/scripts/db/patch.ts +820 -0
  1247. package/src/scripts/db/query.ts +299 -0
  1248. package/src/scripts/db/reset-dev-owner.ts +285 -0
  1249. package/src/scripts/db/safety.ts +286 -0
  1250. package/src/scripts/db/schema.ts +364 -0
  1251. package/src/scripts/db/scoping.ts +341 -0
  1252. package/src/scripts/db/sqlite-client.ts +78 -0
  1253. package/src/scripts/db/tool-mode.ts +23 -0
  1254. package/src/scripts/db/tool-schemas.ts +31 -0
  1255. package/src/scripts/db/wipe-leaked-builder-keys.ts +201 -0
  1256. package/src/scripts/dev/index.ts +296 -0
  1257. package/src/scripts/dev/list-files.ts +119 -0
  1258. package/src/scripts/dev/read-file.ts +77 -0
  1259. package/src/scripts/dev/search-files.ts +151 -0
  1260. package/src/scripts/dev/shell.ts +111 -0
  1261. package/src/scripts/dev/write-file.ts +56 -0
  1262. package/src/scripts/dev-session.ts +96 -0
  1263. package/src/scripts/docs/index.ts +8 -0
  1264. package/src/scripts/docs/search.ts +274 -0
  1265. package/src/scripts/docs/source-search.ts +363 -0
  1266. package/src/scripts/index.ts +15 -0
  1267. package/src/scripts/manage-agent-loop-settings.ts +84 -0
  1268. package/src/scripts/parse-args.ts +46 -0
  1269. package/src/scripts/resources/delete-memory.ts +67 -0
  1270. package/src/scripts/resources/delete.ts +69 -0
  1271. package/src/scripts/resources/effective.ts +71 -0
  1272. package/src/scripts/resources/index.ts +18 -0
  1273. package/src/scripts/resources/list.ts +103 -0
  1274. package/src/scripts/resources/migrate-learnings.ts +40 -0
  1275. package/src/scripts/resources/read.ts +107 -0
  1276. package/src/scripts/resources/save-memory.ts +99 -0
  1277. package/src/scripts/resources/write.ts +158 -0
  1278. package/src/scripts/runner.ts +339 -0
  1279. package/src/scripts/utils.ts +130 -0
  1280. package/src/search/index.ts +413 -0
  1281. package/src/search-utils/index.ts +112 -0
  1282. package/src/secrets/crypto.ts +228 -0
  1283. package/src/secrets/index.ts +65 -0
  1284. package/src/secrets/onboarding.ts +101 -0
  1285. package/src/secrets/register-framework-secrets.ts +228 -0
  1286. package/src/secrets/register.ts +129 -0
  1287. package/src/secrets/routes.ts +697 -0
  1288. package/src/secrets/schema.ts +52 -0
  1289. package/src/secrets/storage.ts +561 -0
  1290. package/src/secrets/substitution.ts +305 -0
  1291. package/src/server/__snapshots__/agent-chat-plugin.surface.spec.ts.snap +150 -0
  1292. package/src/server/action-change.ts +66 -0
  1293. package/src/server/action-discovery.ts +774 -0
  1294. package/src/server/action-routes.ts +608 -0
  1295. package/src/server/agent-access.ts +72 -0
  1296. package/src/server/agent-chat/action-filters-a2a.ts +339 -0
  1297. package/src/server/agent-chat/browser-team-tools.ts +510 -0
  1298. package/src/server/agent-chat/context-tools.ts +599 -0
  1299. package/src/server/agent-chat/framework-prompts.ts +467 -0
  1300. package/src/server/agent-chat/lazy-fs.ts +11 -0
  1301. package/src/server/agent-chat/mcp-glue.ts +91 -0
  1302. package/src/server/agent-chat/plugin-options.ts +379 -0
  1303. package/src/server/agent-chat/process-run-failure.ts +62 -0
  1304. package/src/server/agent-chat/prompt-resources.ts +1004 -0
  1305. package/src/server/agent-chat/recurring-jobs-runtime.ts +80 -0
  1306. package/src/server/agent-chat/request-surface.ts +71 -0
  1307. package/src/server/agent-chat/run-code-tools.ts +67 -0
  1308. package/src/server/agent-chat/script-entries.ts +796 -0
  1309. package/src/server/agent-chat/shared-thread.ts +301 -0
  1310. package/src/server/agent-chat/skill-frontmatter.ts +41 -0
  1311. package/src/server/agent-chat-plugin.ts +5930 -0
  1312. package/src/server/agent-discovery.ts +706 -0
  1313. package/src/server/agent-engine-api-key-route.ts +236 -0
  1314. package/src/server/agent-run-context.ts +204 -0
  1315. package/src/server/agent-teams-run-queue.ts +344 -0
  1316. package/src/server/agent-teams.ts +2257 -0
  1317. package/src/server/agents-bundle.ts +464 -0
  1318. package/src/server/analytics.ts +105 -0
  1319. package/src/server/app-base-path.ts +79 -0
  1320. package/src/server/app-name.ts +53 -0
  1321. package/src/server/app-url.ts +184 -0
  1322. package/src/server/attachment-actions.ts +276 -0
  1323. package/src/server/attribution.ts +214 -0
  1324. package/src/server/auth-marketing.ts +260 -0
  1325. package/src/server/auth-plugin.ts +27 -0
  1326. package/src/server/auth.ts +3803 -0
  1327. package/src/server/better-auth-instance.ts +1197 -0
  1328. package/src/server/builder-browser.ts +1701 -0
  1329. package/src/server/builder-design-systems.ts +668 -0
  1330. package/src/server/builder-space.ts +176 -0
  1331. package/src/server/captcha.ts +63 -0
  1332. package/src/server/capture-error.ts +64 -0
  1333. package/src/server/cli-capture.ts +141 -0
  1334. package/src/server/collab-plugin.ts +391 -0
  1335. package/src/server/complete-text.ts +231 -0
  1336. package/src/server/cookie-namespace.ts +159 -0
  1337. package/src/server/core-routes-plugin.ts +4122 -0
  1338. package/src/server/cors-origins.ts +60 -0
  1339. package/src/server/create-server.ts +268 -0
  1340. package/src/server/credential-provider.ts +1202 -0
  1341. package/src/server/csrf.ts +229 -0
  1342. package/src/server/date-utils.ts +42 -0
  1343. package/src/server/deep-link.ts +119 -0
  1344. package/src/server/derived-secret.ts +32 -0
  1345. package/src/server/design-token-utils.ts +1032 -0
  1346. package/src/server/desktop-sso.ts +83 -0
  1347. package/src/server/edge.ts +69 -0
  1348. package/src/server/email-actions.ts +218 -0
  1349. package/src/server/email-template.ts +192 -0
  1350. package/src/server/email-templates.ts +148 -0
  1351. package/src/server/email.ts +219 -0
  1352. package/src/server/embed-route.ts +283 -0
  1353. package/src/server/embed-session.ts +805 -0
  1354. package/src/server/embedded.ts +252 -0
  1355. package/src/server/entry-server.tsx +154 -0
  1356. package/src/server/framework-request-handler.ts +848 -0
  1357. package/src/server/fusion-app.ts +447 -0
  1358. package/src/server/google-auth-mode.ts +34 -0
  1359. package/src/server/google-auth-plugin.ts +496 -0
  1360. package/src/server/google-oauth-credentials.ts +128 -0
  1361. package/src/server/google-oauth.ts +972 -0
  1362. package/src/server/google-realtime-session.ts +182 -0
  1363. package/src/server/h3-helpers.ts +174 -0
  1364. package/src/server/http-response-telemetry.ts +396 -0
  1365. package/src/server/identity-sso-store.ts +304 -0
  1366. package/src/server/identity-sso.ts +514 -0
  1367. package/src/server/index.ts +593 -0
  1368. package/src/server/missing-key.ts +36 -0
  1369. package/src/server/oauth-helpers.ts +44 -0
  1370. package/src/server/oauth-public-origin.ts +44 -0
  1371. package/src/server/oauth-return-url.ts +120 -0
  1372. package/src/server/og-fonts-data.ts +15 -0
  1373. package/src/server/og-fonts.ts +75 -0
  1374. package/src/server/onboarding-html.ts +4040 -0
  1375. package/src/server/open-route.ts +311 -0
  1376. package/src/server/org-admin.ts +28 -0
  1377. package/src/server/poll-events.ts +88 -0
  1378. package/src/server/poll.ts +1224 -0
  1379. package/src/server/prompts/framework-core-compact.ts +107 -0
  1380. package/src/server/prompts/framework-core.ts +143 -0
  1381. package/src/server/prompts/index.ts +16 -0
  1382. package/src/server/prompts/model-overlays.ts +54 -0
  1383. package/src/server/prompts/shared-rules.ts +104 -0
  1384. package/src/server/realtime-voice.ts +967 -0
  1385. package/src/server/recap-image-route.ts +242 -0
  1386. package/src/server/recap-image-store.ts +185 -0
  1387. package/src/server/request-context.ts +416 -0
  1388. package/src/server/request-origin.ts +67 -0
  1389. package/src/server/resources-plugin.ts +119 -0
  1390. package/src/server/schema-prompt.ts +429 -0
  1391. package/src/server/scoped-key-storage.ts +223 -0
  1392. package/src/server/security-headers.ts +170 -0
  1393. package/src/server/self-dispatch.ts +224 -0
  1394. package/src/server/sentry-config.ts +73 -0
  1395. package/src/server/sentry-plugin.ts +138 -0
  1396. package/src/server/sentry.ts +444 -0
  1397. package/src/server/short-lived-token.ts +170 -0
  1398. package/src/server/social-og-image.ts +454 -0
  1399. package/src/server/sse.ts +102 -0
  1400. package/src/server/ssr-handler.ts +466 -0
  1401. package/src/server/transcribe-voice.ts +1076 -0
  1402. package/src/server/voice-providers-status.ts +118 -0
  1403. package/src/server/workspace-oauth.ts +31 -0
  1404. package/src/server/workspace-provider-oauth.ts +970 -0
  1405. package/src/session-replay-iframe-protocol.ts +58 -0
  1406. package/src/settings/handlers.ts +44 -0
  1407. package/src/settings/index.ts +34 -0
  1408. package/src/settings/org-settings.ts +83 -0
  1409. package/src/settings/script-helpers.ts +24 -0
  1410. package/src/settings/store.ts +247 -0
  1411. package/src/settings/user-settings.ts +47 -0
  1412. package/src/setup-connections/index.ts +90 -0
  1413. package/src/setup-connections/onboarding.ts +47 -0
  1414. package/src/shared/agent-access.ts +112 -0
  1415. package/src/shared/agent-chat.ts +124 -0
  1416. package/src/shared/agent-env.ts +46 -0
  1417. package/src/shared/agent-readable-resource.ts +111 -0
  1418. package/src/shared/agent-sidebar-url.ts +39 -0
  1419. package/src/shared/cache-control.ts +25 -0
  1420. package/src/shared/cloudflare-globals.d.ts +34 -0
  1421. package/src/shared/context-xray.ts +172 -0
  1422. package/src/shared/embed-auth.ts +6 -0
  1423. package/src/shared/index.ts +83 -0
  1424. package/src/shared/llm-connection.ts +42 -0
  1425. package/src/shared/markdown-block-split.ts +129 -0
  1426. package/src/shared/mcp-connect-content.ts +130 -0
  1427. package/src/shared/mcp-embed-headers.ts +175 -0
  1428. package/src/shared/mcp-integration-config.ts +101 -0
  1429. package/src/shared/merge-by-id.ts +14 -0
  1430. package/src/shared/oauth-state.ts +39 -0
  1431. package/src/shared/reasoning-effort.ts +228 -0
  1432. package/src/shared/route-warmup-config.ts +128 -0
  1433. package/src/shared/runtime.ts +31 -0
  1434. package/src/shared/social-meta.ts +82 -0
  1435. package/src/shared/streaming-text-smoothing.ts +184 -0
  1436. package/src/shared/truncate.ts +13 -0
  1437. package/src/shared/workspace-app-audience.ts +177 -0
  1438. package/src/shared/workspace-app-id.ts +54 -0
  1439. package/src/sharing/access.ts +585 -0
  1440. package/src/sharing/actions/create-agent-resource-link.ts +91 -0
  1441. package/src/sharing/actions/extension-change.ts +22 -0
  1442. package/src/sharing/actions/list-resource-shares.ts +93 -0
  1443. package/src/sharing/actions/set-resource-visibility.ts +91 -0
  1444. package/src/sharing/actions/share-resource.ts +322 -0
  1445. package/src/sharing/actions/unshare-resource.ts +72 -0
  1446. package/src/sharing/index.ts +35 -0
  1447. package/src/sharing/registry.ts +201 -0
  1448. package/src/sharing/schema.ts +99 -0
  1449. package/src/styles/agent-conversation.css +766 -0
  1450. package/src/styles/agent-native.css +938 -0
  1451. package/src/styles/blocks.css +1717 -0
  1452. package/src/styles/chat-history-list.css +1 -0
  1453. package/src/styles/rich-markdown-editor.css +1 -0
  1454. package/src/tailwind.preset.ts +140 -0
  1455. package/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +15 -13
  1456. package/src/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  1457. package/src/templates/chat/.agents/skills/self-modifying-code/SKILL.md +9 -6
  1458. package/src/templates/chat/AGENTS.md +3 -2
  1459. package/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +15 -13
  1460. package/src/templates/default/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  1461. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +9 -6
  1462. package/src/templates/default/AGENTS.md +6 -5
  1463. package/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +15 -13
  1464. package/src/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  1465. package/src/templates/headless/AGENTS.md +5 -4
  1466. package/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +15 -13
  1467. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +3 -2
  1468. package/src/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  1469. package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +9 -6
  1470. package/src/templates/workspace-core/AGENTS.md +4 -4
  1471. package/src/templates/workspace-root/AGENTS.md +4 -4
  1472. package/src/terminal/cli-registry.ts +57 -0
  1473. package/src/terminal/index.ts +22 -0
  1474. package/src/terminal/pty-server.ts +389 -0
  1475. package/src/terminal/terminal-plugin.ts +265 -0
  1476. package/src/testing.ts +23 -0
  1477. package/src/tracking/index.ts +10 -0
  1478. package/src/tracking/providers.ts +422 -0
  1479. package/src/tracking/registry.ts +102 -0
  1480. package/src/tracking/route.ts +108 -0
  1481. package/src/tracking/types.ts +16 -0
  1482. package/src/transcription/builder-transcription.ts +118 -0
  1483. package/src/triggers/actions/list-automations.ts +99 -0
  1484. package/src/triggers/actions/manage-automation.ts +78 -0
  1485. package/src/triggers/actions.ts +333 -0
  1486. package/src/triggers/condition-evaluator.ts +163 -0
  1487. package/src/triggers/dispatcher.ts +536 -0
  1488. package/src/triggers/index.ts +13 -0
  1489. package/src/triggers/routes.ts +322 -0
  1490. package/src/triggers/types.ts +42 -0
  1491. package/src/types/pglite.d.ts +26 -0
  1492. package/src/usage/store.ts +698 -0
  1493. package/src/vite/action-types-plugin.ts +481 -0
  1494. package/src/vite/agent-web-plugin.ts +78 -0
  1495. package/src/vite/agents-bundle-plugin.ts +185 -0
  1496. package/src/vite/client.ts +3039 -0
  1497. package/src/vite/index.ts +21 -0
  1498. package/src/voice/index.ts +18 -0
  1499. package/src/voice/voice-cleanup-prompt.ts +38 -0
  1500. package/src/voice/voice-context.ts +217 -0
  1501. package/src/voice/voice-replacements.ts +75 -0
  1502. package/src/workspace-connections/credentials.ts +603 -0
  1503. package/src/workspace-connections/index.ts +72 -0
  1504. package/src/workspace-connections/lifecycle.ts +43 -0
  1505. package/src/workspace-connections/store.ts +1943 -0
  1506. package/src/workspace-files/index.ts +18 -0
  1507. package/src/workspace-files/store.ts +339 -0
  1508. package/src/workspace-files/tool.ts +266 -0
@@ -0,0 +1,2601 @@
1
+ /**
2
+ * SQL persistence for agent runs and events.
3
+ * Enables cross-isolate access on Cloudflare Workers and
4
+ * reliable reconnection after page refreshes.
5
+ */
6
+ import type { DbExec } from "../db/client.js";
7
+ import { getDbExec, intType, isPostgres } from "../db/client.js";
8
+ import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
9
+ import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
10
+ import { captureError } from "../server/capture-error.js";
11
+ import {
12
+ LLM_MISSING_CREDENTIALS_ERROR_CODE,
13
+ LLM_MISSING_CREDENTIALS_MESSAGE,
14
+ } from "./engine/credential-errors.js";
15
+ import type { AgentChatEvent } from "./types.js";
16
+
17
+ let _initPromise: Promise<void> | undefined;
18
+
19
+ /**
20
+ * Max time without a heartbeat before a "running" run is considered dead.
21
+ * The run-manager heartbeats every 1.5s, so 15s tolerates ~9 missed writes.
22
+ * Widened from 6s to absorb real-world DB latency spikes and GC pauses that
23
+ * caused false-positive reaps: a live run whose heartbeat lagged 6s+ would be
24
+ * reaped and a zombie would keep running, eventually clobbering the new row.
25
+ */
26
+ export const RUN_STALE_MS = 15_000;
27
+
28
+ /**
29
+ * Stale window for runs dispatched into a Netlify background function
30
+ * (`dispatch_mode = 'background'`). The design doc flags the 15s reaper vs a
31
+ * background cold-start as the #1 false-failure risk: the foreground POST
32
+ * inserts the `running` row, then `fireInternalDispatch` returns 202 and the
33
+ * background function may take >15s to cold-start and emit its first heartbeat.
34
+ * With the normal 15s window the reaper would falsely kill that freshly-
35
+ * inserted-but-not-yet-heartbeaten row. 90s tolerates a slow background
36
+ * cold-start while still reaping a genuinely dead background worker promptly.
37
+ * Claimed background workers heartbeat during long work; the stale watchdog is a
38
+ * liveness timeout, not the Netlify background-function execution budget.
39
+ *
40
+ * Only applied to rows explicitly marked background-dispatched; ordinary
41
+ * foreground runs keep the tight 15s window unchanged.
42
+ */
43
+ export const BACKGROUND_RUN_STALE_MS = 90_000;
44
+
45
+ /**
46
+ * A row is `background` only while the platform may still be cold-starting the
47
+ * worker, so it needs the full 90s handoff allowance above. Once that worker
48
+ * atomically claims the row (`background-processing`), it has already proved
49
+ * it started and should be reaped sooner if both heartbeat and real progress
50
+ * stop. This keeps a silent post-claim worker death from holding the client
51
+ * for the entire cold-start window before the durable successor is created.
52
+ *
53
+ * A real long-running tool or nested agent call still sets `in_flight_since`,
54
+ * which grants the bounded `IN_FLIGHT_RUN_STALE_GRACE_MS` below. Healthy model
55
+ * work keeps the normal heartbeat moving every 1.5s, so this is only a faster
56
+ * recovery path for a worker that has genuinely gone silent.
57
+ */
58
+ export const BACKGROUND_PROCESSING_RUN_STALE_MS = 45_000;
59
+
60
+ export const STALE_RUN_ERROR_EVENT = {
61
+ type: "error",
62
+ error:
63
+ "The agent stopped before it could finish. It may have hit a server timeout or the worker may have been interrupted.",
64
+ errorCode: "stale_run",
65
+ recoverable: true,
66
+ details:
67
+ "The run heartbeat stopped while the run was still marked running. Partial output and tool calls were preserved when available.",
68
+ } as const;
69
+
70
+ /**
71
+ * Terminal error for a background-dispatched run whose worker NEVER claimed it
72
+ * (the foreground fired the self-dispatch, Netlify acked it async with a 202,
73
+ * but the `_process-run` worker never ran far enough to flip
74
+ * `dispatch_mode background → background-processing`). Distinct errorCode so the
75
+ * client (and prod triage) can tell "the worker died silently" apart from "a
76
+ * claimed worker's heartbeat went stale". Recoverable so the client surfaces a
77
+ * retry affordance and re-drives the turn. See `reapUnclaimedBackgroundRun`.
78
+ */
79
+ export const UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT = {
80
+ type: "error",
81
+ error:
82
+ "The agent run was handed off to a background worker that never started. It was recovered so you can try again.",
83
+ errorCode: "background_worker_never_started",
84
+ recoverable: true,
85
+ details:
86
+ "A background-dispatched run was acknowledged (HTTP 202) but its worker never claimed the run, so no progress was produced. The run was reaped early (it had no live worker to protect) so the turn can be retried.",
87
+ } as const;
88
+
89
+ /**
90
+ * Terminal error for a background worker that DID claim the run, then failed
91
+ * during route/handler setup before `startRun` could emit its own error event.
92
+ * Claimed runs are no longer eligible for foreground inline recovery, so the
93
+ * route boundary must fail them loudly instead of leaving subscribers to wait
94
+ * for stale-run recovery.
95
+ */
96
+ export const CLAIMED_BACKGROUND_WORKER_FAILED_ERROR_EVENT = {
97
+ type: "error",
98
+ error:
99
+ "The background agent worker stopped before it could start the turn. You can retry from the preserved chat context.",
100
+ errorCode: "background_worker_failed",
101
+ recoverable: true,
102
+ details:
103
+ "The durable background worker claimed the run but threw during setup before it could emit agent events.",
104
+ } as const;
105
+
106
+ /**
107
+ * Grace period before a never-claimed background run (dispatch_mode still
108
+ * 'background', no worker claim) is treated as a dead handoff and reaped.
109
+ *
110
+ * This is intentionally tighter than `BACKGROUND_RUN_STALE_MS`. That wider
111
+ * window protects cold-starting or temporarily delayed background dispatches,
112
+ * while claimed workers stay alive by heartbeat/progress updates. A run that is
113
+ * still `dispatch_mode = 'background'` has, by definition, NO worker — nothing
114
+ * to protect — so once a Netlify
115
+ * background function has had a reasonable cold-start window to claim it and
116
+ * hasn't, the handoff is dead and should surface promptly instead of leaving
117
+ * the user staring at a spinner for the durable-worker window. 25s comfortably exceeds a normal
118
+ * Netlify Lambda cold start while still failing fast on a silent worker death.
119
+ */
120
+ export const UNCLAIMED_BACKGROUND_RUN_GRACE_MS = 25_000;
121
+
122
+ /**
123
+ * Backstop ceiling — measured from the row's ORIGINAL `started_at`, which never
124
+ * changes — after which the unclaimed-background-run sweep stops attempting to
125
+ * redispatch a lost handoff and instead reaps it via `reapUnclaimedBackgroundRun`
126
+ * (loud, attributable `errored`). This is what keeps redispatch recoverable
127
+ * WITHOUT becoming a silent hang: a handoff that cannot be delivered within this
128
+ * window (a genuinely dead platform, not a transient blip) still fails loudly,
129
+ * it just gets a few sweep-cycle chances first. 5 minutes comfortably allows
130
+ * multiple 2-minute sweep ticks (see `agent-chat-plugin.ts`'s
131
+ * "Unclaimed background-run sweep") while staying well inside both the 40s
132
+ * foreground chunk clamp and the ~13min background soft-timeout ceiling that
133
+ * bound how long a real user turn is worth waiting on before failing loud.
134
+ */
135
+ export const UNCLAIMED_BACKGROUND_RUN_REDISPATCH_BOUND_MS = 5 * 60_000;
136
+
137
+ /**
138
+ * Tick interval for the DEDICATED fast redispatch sweep in
139
+ * agent-chat-plugin.ts (distinct from that file's general-purpose 2-minute
140
+ * orphan/reap sweep). Only attempts redispatch for rows still inside
141
+ * `UNCLAIMED_BACKGROUND_RUN_REDISPATCH_BOUND_MS` — it never reaps, so it
142
+ * cannot race the loud-failure fallback onto an earlier trigger.
143
+ *
144
+ * This constant exists because the general sweep's 2-minute cadence puts the
145
+ * FIRST redispatch attempt uncomfortably close to (and on a slow tick, past)
146
+ * `BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS` (150s, agent-chat-adapter.ts) — the
147
+ * client following a deferred successor would give up and report a fatal
148
+ * error for a turn the server was silently about to recover. The whole
149
+ * budget is a derived chain, each bound following from the one before it:
150
+ *
151
+ * UNCLAIMED_BACKGROUND_RUN_GRACE_MS (25s) row must look abandoned
152
+ * + UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS (20s) worst-case tick latency
153
+ * = ~45s worst-case time-to-first-redispatch-attempt, ~65s to a second
154
+ * attempt if the first fails — both comfortably under the client's 150s
155
+ * idle timeout, which additionally no longer counts a known-deferred row
156
+ * against its idle window at all (see `awaitingRedispatch` surfaced by
157
+ * `/runs/active` and consumed by the client follow loop).
158
+ * < BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS (150s) client's own backstop
159
+ * < UNCLAIMED_BACKGROUND_RUN_REDISPATCH_BOUND_MS (300s) hard, unresettable
160
+ * ceiling — untouched by this constant — past which the slow sweep's
161
+ * existing loud reap (`background_worker_never_started`) still fires.
162
+ */
163
+ export const UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS = 20_000;
164
+
165
+ /**
166
+ * FIX 3 (durable-background incident) per-turn run-count ceiling for
167
+ * stale-run recovery — mirrors `chainServerDrivenContinuation`'s own ledger
168
+ * guard in production-agent.ts (`MAX_BACKGROUND_RUN_CONTINUATIONS + 5` = 25).
169
+ * Duplicated as a literal rather than imported: production-agent.ts already
170
+ * imports run-manager.ts, which imports this file, so a runtime import back
171
+ * from here would be circular. Keep this numerically in sync if that
172
+ * constant ever changes.
173
+ */
174
+ const STALE_RUN_RECOVERY_MAX_TURN_RUNS = 25;
175
+
176
+ /**
177
+ * Maximum time the stale reapers (`reapIfStale`, `reapAllStaleRuns`,
178
+ * `cleanupOldRuns`'s heartbeat-stale pass) will suspend reaping a "running"
179
+ * row that is marked in-flight (`in_flight_since`, see `setRunInFlightMarker`)
180
+ * even though its heartbeat/progress liveness basis
181
+ * (`livenessBasisSql`/`backgroundAwareStaleCutoffSql`) has gone stale.
182
+ *
183
+ * WHY a marker column at all: `inFlightWorkCount` in run-manager.ts (the
184
+ * no-progress backstop's guard) is in-memory, per-isolate — but all three
185
+ * reapers above can run in a DIFFERENT isolate than the one holding the
186
+ * producing run (a client's SQL-subscription poll, a sibling isolate's
187
+ * opportunistic `cleanupOldRuns` after ITS OWN run completes, or a fresh
188
+ * boot's `reapAllStaleRuns`). None of them can read another isolate's
189
+ * in-memory counter, so the counter's 0->1 / 1->0 transitions are mirrored
190
+ * into this column (`setRunInFlightMarker`, called from
191
+ * run-manager.ts's `trackInFlightWork`) so it is observable from SQL. This is
192
+ * exactly the gap that let a demonstrably-alive run holding a long tool call
193
+ * or A2A `call-agent` delegation get reaped: the heartbeat WRITE can fail
194
+ * silently (Neon pooler saturation) for the whole `BACKGROUND_RUN_STALE_MS`
195
+ * window while the run is provably still doing work.
196
+ *
197
+ * BOUNDED, not a silent hang — derived from two independent ceilings already
198
+ * in the codebase, not picked by feel:
199
+ * - `DEFAULT_TOOL_TIMEOUT_MS` (12 min, production-agent.ts) is the longest
200
+ * any SINGLE tool call or `agent_call` (A2A delegation) may legitimately
201
+ * stay in flight — past that its own `AbortSignal.timeout` forces a
202
+ * tool_done/error and clears the marker.
203
+ * - `BACKGROUND_SOFT_TIMEOUT_CEILING_MS` (13 min, run-manager.ts) is the
204
+ * background chunk's OWN soft-timeout ceiling. Unlike the no-progress
205
+ * backstop, this timer is NOT gated on in-flight work (see the "secondary"
206
+ * hazard documented next to the soft-timeout timer in run-manager.ts) —
207
+ * it fires unconditionally and checkpoints/continues the run, so by 13
208
+ * minutes the row leaves status='running' via that path regardless of
209
+ * what the marker says.
210
+ * This grace is the LARGER of the two (13 min) plus one `BACKGROUND_RUN_STALE_MS`
211
+ * (90s) buffer for that checkpoint's own completion write to land under the
212
+ * same DB pressure that could have caused the heartbeat to lapse in the first
213
+ * place: 780_000 + 90_000 = 870_000ms (14.5 min). Past that, a "running" row
214
+ * that still shows in-flight work AND a stale liveness basis is not a slow
215
+ * producer anymore — every backstop that should have ended it has ALSO failed
216
+ * to write, and it is reaped loud like any other stale run.
217
+ *
218
+ * Never applied when a caller passes an explicit `maxStaleMs` override to
219
+ * `reapIfStale` — that escape hatch is an exact, caller-chosen window and
220
+ * stays exact. Never weakens the no-in-flight case: a row with no marker set
221
+ * evaluates this grace clause to a no-op and is reaped at the original
222
+ * `BACKGROUND_RUN_STALE_MS` / `RUN_STALE_MS` exactly as before.
223
+ */
224
+ export const IN_FLIGHT_RUN_STALE_GRACE_MS = 14.5 * 60_000; // 870_000
225
+
226
+ async function ensureRunTables(): Promise<void> {
227
+ if (!_initPromise) {
228
+ _initPromise = (async () => {
229
+ const client = getDbExec();
230
+
231
+ // Shared CREATE SQL strings — referenced by both the Postgres and SQLite
232
+ // branches so the column definitions stay in one place.
233
+ const agentRunsCreateSql = `
234
+ CREATE TABLE IF NOT EXISTS agent_runs (
235
+ id TEXT PRIMARY KEY,
236
+ thread_id TEXT NOT NULL,
237
+ status TEXT NOT NULL DEFAULT 'running',
238
+ abort_reason TEXT,
239
+ started_at ${intType()} NOT NULL,
240
+ completed_at ${intType()},
241
+ heartbeat_at ${intType()},
242
+ last_progress_at ${intType()},
243
+ turn_id TEXT,
244
+ error_code TEXT,
245
+ error_detail TEXT,
246
+ terminal_reason TEXT,
247
+ dispatch_mode TEXT,
248
+ diag_stage TEXT,
249
+ dispatch_payload TEXT
250
+ )
251
+ `;
252
+ const agentRunEventsCreateSql = `
253
+ CREATE TABLE IF NOT EXISTS agent_run_events (
254
+ run_id TEXT NOT NULL,
255
+ seq ${intType()} NOT NULL,
256
+ event_at ${intType()},
257
+ event_data TEXT NOT NULL,
258
+ PRIMARY KEY (run_id, seq)
259
+ )
260
+ `;
261
+ // Tool-call result ledger: persists the outcome of write tool calls that
262
+ // completed AFTER their chunk was abandoned (zombie completions). A
263
+ // resumed continuation can recover the real result by matching
264
+ // thread_id + tool_key (name:stableInputHash) instead of re-executing
265
+ // the side effect. Entries are scoped to the thread and expire with it.
266
+ const agentToolLedgerCreateSql = `
267
+ CREATE TABLE IF NOT EXISTS agent_tool_ledger (
268
+ thread_id TEXT NOT NULL,
269
+ tool_key TEXT NOT NULL,
270
+ result_summary TEXT NOT NULL,
271
+ completed_at ${intType()} NOT NULL,
272
+ PRIMARY KEY (thread_id, tool_key)
273
+ )
274
+ `;
275
+
276
+ if (isPostgres()) {
277
+ // Hot path: in production the tables and all additive columns are
278
+ // virtually always already present. Issuing `CREATE TABLE`/`ALTER TABLE
279
+ // ADD COLUMN` still takes an ACCESS EXCLUSIVE lock — which, in a fresh
280
+ // background-worker process behind a concurrent connection on the shared
281
+ // Neon DB, can block ~indefinitely. So check `information_schema` first
282
+ // (plain reads, no lock) and run DDL ONLY for what is actually missing.
283
+ // The `ensureTableExists` / `ensureColumnExists` wrappers probe →
284
+ // guarded-DDL (bounded `lock_timeout`) → re-probe, and THROW if the
285
+ // schema is still missing after a swallowed lock-timeout so a poisoned
286
+ // init never memoizes success against absent schema (the `_initPromise`
287
+ // rejects and the next call retries).
288
+ await ensureTableExists("agent_runs", agentRunsCreateSql);
289
+ // Additive columns — all listed in the CREATE TABLE above, so on a
290
+ // fresh DB they already exist after the CREATE and these checks are
291
+ // instant short-circuits. On an older deployment that predates a
292
+ // column, the wrapper issues one bounded ALTER.
293
+ //
294
+ // Backfill heartbeat_at on older deployments.
295
+ // heartbeat_at = "the producer process is alive" (bumped on a timer).
296
+ // last_progress_at = "the agent is actually emitting events" (bumped on
297
+ // each emit). The gap between them is the stuck-detector signal.
298
+ for (const [col, colType] of [
299
+ ["heartbeat_at", intType()],
300
+ ["abort_reason", "TEXT"],
301
+ ["last_progress_at", intType()],
302
+ // Backfill turn_id / error_code / error_detail.
303
+ // turn_id = stable identity for one logical assistant turn that may
304
+ // span several continuation runs, so the durable record
305
+ // can be folded across runs instead of dropped per-run.
306
+ // error_code / error_detail = terminal failure classification captured
307
+ // at completion so errored/cut-off runs are queryable for
308
+ // pattern analysis (see listErroredRuns).
309
+ // dispatch_mode marks how a run was started: NULL/"foreground" for the
310
+ // normal client-continued synchronous path, "foreground-self-chain" for
311
+ // a foreground run whose continuation boundary is server-driven, and
312
+ // "background" for a run dispatched into a Netlify background function.
313
+ // The reaper/claim widen the stale window for background rows so a slow
314
+ // cold-start isn't falsely reaped.
315
+ // diag_stage records the last reached pipeline stage (+ any error) for a
316
+ // background-dispatched run so a silent worker death is DIAGNOSABLE from
317
+ // the client (/runs/active surfaces it) without reading the unreadable
318
+ // Netlify background-function logs. See recordRunDiagnostic.
319
+ ["turn_id", "TEXT"],
320
+ ["error_code", "TEXT"],
321
+ ["error_detail", "TEXT"],
322
+ ["terminal_reason", "TEXT"],
323
+ ["dispatch_mode", "TEXT"],
324
+ ["diag_stage", "TEXT"],
325
+ ["worker_stage", "TEXT"],
326
+ // dispatch_payload holds the JSON request body for a background
327
+ // dispatch so the self-POST to the Netlify background function can
328
+ // stay tiny (Netlify caps background-function request bodies at
329
+ // 256KB — a large chat history silently exceeded it). The worker
330
+ // rehydrates the body from this column via the marker's payloadRef.
331
+ // Cleared on terminal status writes.
332
+ ["dispatch_payload", "TEXT"],
333
+ // in_flight_since = ms epoch when run-manager's in-memory
334
+ // `inFlightWorkCount` last transitioned 0->1 (a tool call or nested
335
+ // `agent_call`/A2A delegation started), NULL once it drops back to 0.
336
+ // Lets the cross-isolate stale reapers grant a bounded grace to a
337
+ // demonstrably-alive run even when the SAME-isolate heartbeat write
338
+ // has failed. See `IN_FLIGHT_RUN_STALE_GRACE_MS` and
339
+ // `setRunInFlightMarker`.
340
+ ["in_flight_since", intType()],
341
+ ] as const) {
342
+ await ensureColumnExists(
343
+ "agent_runs",
344
+ col,
345
+ `ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS ${col} ${colType}`,
346
+ );
347
+ }
348
+ await ensureTableExists("agent_run_events", agentRunEventsCreateSql);
349
+ await ensureColumnExists(
350
+ "agent_run_events",
351
+ "event_at",
352
+ `ALTER TABLE agent_run_events ADD COLUMN IF NOT EXISTS event_at ${intType()}`,
353
+ );
354
+ await ensureTableExists("agent_tool_ledger", agentToolLedgerCreateSql);
355
+ // Widen millisecond-timestamp columns that older deployments created as
356
+ // 32-bit `INTEGER`. `insertRun()` writes `Date.now()` into `started_at`
357
+ // on every turn, so an int4 column makes every agent prompt fail on
358
+ // Postgres with "value … is out of range for type integer". No-op once
359
+ // widened (and on fresh DBs that already use BIGINT). See
360
+ // widenIntColumnsToBigInt.
361
+ await widenIntColumnsToBigInt("agent_runs", [
362
+ "started_at",
363
+ "completed_at",
364
+ "heartbeat_at",
365
+ "last_progress_at",
366
+ "in_flight_since",
367
+ ]);
368
+ await widenIntColumnsToBigInt("agent_run_events", ["event_at"]);
369
+ await widenIntColumnsToBigInt("agent_tool_ledger", ["completed_at"]);
370
+ return;
371
+ }
372
+
373
+ // SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep the
374
+ // original create-then-additive-alter behaviour. SQLite has no
375
+ // `ADD COLUMN IF NOT EXISTS`, so the ALTERs stay wrapped in try/catch.
376
+ await client.execute(agentRunsCreateSql);
377
+ // Backfill heartbeat_at on older deployments.
378
+ try {
379
+ await client.execute(
380
+ `ALTER TABLE agent_runs ADD COLUMN heartbeat_at ${intType()}`,
381
+ );
382
+ } catch {
383
+ // Column already exists — ignore
384
+ }
385
+ try {
386
+ await client.execute(
387
+ `ALTER TABLE agent_runs ADD COLUMN abort_reason TEXT`,
388
+ );
389
+ } catch {
390
+ // Column already exists — ignore
391
+ }
392
+ // Backfill last_progress_at — this is distinct from heartbeat_at.
393
+ // heartbeat_at = "the producer process is alive" (bumped on a timer).
394
+ // last_progress_at = "the agent is actually emitting events" (bumped on
395
+ // each emit). The gap between them is the stuck-detector signal.
396
+ try {
397
+ await client.execute(
398
+ `ALTER TABLE agent_runs ADD COLUMN last_progress_at ${intType()}`,
399
+ );
400
+ } catch {
401
+ // Column already exists — ignore
402
+ }
403
+ // Backfill in_flight_since — ms epoch when run-manager's in-memory
404
+ // `inFlightWorkCount` last transitioned 0->1, NULL once back to 0. Lets
405
+ // the cross-isolate stale reapers grant a bounded grace to a
406
+ // demonstrably-alive run even when the heartbeat write itself has
407
+ // failed. See `IN_FLIGHT_RUN_STALE_GRACE_MS` and `setRunInFlightMarker`.
408
+ try {
409
+ await client.execute(
410
+ `ALTER TABLE agent_runs ADD COLUMN in_flight_since ${intType()}`,
411
+ );
412
+ } catch {
413
+ // Column already exists — ignore
414
+ }
415
+ // Backfill turn_id / error_code / error_detail.
416
+ // turn_id = stable identity for one logical assistant turn that may
417
+ // span several continuation runs, so the durable record
418
+ // can be folded across runs instead of dropped per-run.
419
+ // error_code / error_detail = terminal failure classification captured
420
+ // at completion so errored/cut-off runs are queryable for
421
+ // pattern analysis (see listErroredRuns).
422
+ // dispatch_mode marks how a run was started: NULL/"foreground" for the
423
+ // normal client-continued synchronous path, "foreground-self-chain" for
424
+ // a foreground run whose continuation boundary is server-driven, and
425
+ // "background" for a run dispatched into a Netlify background function.
426
+ // The reaper/claim widen the stale window for background rows so a slow
427
+ // cold-start isn't falsely reaped.
428
+ // diag_stage records the last reached pipeline stage (+ any error) for a
429
+ // background-dispatched run so a silent worker death is DIAGNOSABLE from
430
+ // the client (/runs/active surfaces it) without reading the unreadable
431
+ // Netlify background-function logs. See recordRunDiagnostic.
432
+ for (const col of [
433
+ "turn_id",
434
+ "error_code",
435
+ "error_detail",
436
+ "terminal_reason",
437
+ "dispatch_mode",
438
+ "diag_stage",
439
+ "worker_stage",
440
+ "dispatch_payload",
441
+ ] as const) {
442
+ try {
443
+ await client.execute(`ALTER TABLE agent_runs ADD COLUMN ${col} TEXT`);
444
+ } catch {
445
+ // Column already exists — ignore
446
+ }
447
+ }
448
+ await client.execute(agentRunEventsCreateSql);
449
+ try {
450
+ await client.execute(
451
+ `ALTER TABLE agent_run_events ADD COLUMN event_at ${intType()}`,
452
+ );
453
+ } catch {
454
+ // Column already exists — ignore
455
+ }
456
+ await client.execute(agentToolLedgerCreateSql);
457
+ // Widen millisecond-timestamp columns that older deployments created as
458
+ // 32-bit `INTEGER`. `insertRun()` writes `Date.now()` into `started_at`
459
+ // on every turn, so an int4 column makes every agent prompt fail on
460
+ // Postgres with "value … is out of range for type integer". No-op once
461
+ // widened (and on fresh DBs that already use BIGINT). See
462
+ // widenIntColumnsToBigInt.
463
+ await widenIntColumnsToBigInt("agent_runs", [
464
+ "started_at",
465
+ "completed_at",
466
+ "heartbeat_at",
467
+ "last_progress_at",
468
+ "in_flight_since",
469
+ ]);
470
+ await widenIntColumnsToBigInt("agent_run_events", ["event_at"]);
471
+ await widenIntColumnsToBigInt("agent_tool_ledger", ["completed_at"]);
472
+ })().catch((err) => {
473
+ // Retry init on the next call after a failed startup.
474
+ _initPromise = undefined;
475
+ throw err;
476
+ });
477
+ }
478
+ return _initPromise;
479
+ }
480
+
481
+ // ─── Tool-call result ledger ─────────────────────────────────────────────────
482
+ //
483
+ // When the run-level abort signal fires (soft timeout / user cancel) while a
484
+ // write tool is in-flight, `Promise.race` abandons the call — but the action's
485
+ // Promise continues running in the background (a "zombie"). If the zombie
486
+ // resolves before the continuation's next tool dispatch, we record the result
487
+ // here so the continuation can recover it without re-executing the side effect.
488
+ //
489
+ // Keyed by (thread_id, tool_key) where tool_key = "<toolName>:<stableJsonHash>".
490
+ // The write is fire-and-forget from the hot path; reads are synchronous look-
491
+ // ups at the start of each write-tool dispatch in the continuation.
492
+
493
+ /** Max length for a persisted result summary (8 KB). */
494
+ const LEDGER_RESULT_MAX_CHARS = 8_000;
495
+
496
+ /**
497
+ * Persist a zombie tool-call completion to the ledger. Called by the detached
498
+ * promise continuation after `Promise.race` abandons it. Best-effort — never
499
+ * throws so a ledger write failure doesn't break any caller.
500
+ */
501
+ export async function writeLedgerEntry(
502
+ threadId: string,
503
+ toolKey: string,
504
+ resultSummary: string,
505
+ ): Promise<void> {
506
+ try {
507
+ await ensureRunTables();
508
+ const client = getDbExec();
509
+ const capped =
510
+ resultSummary.length > LEDGER_RESULT_MAX_CHARS
511
+ ? resultSummary.slice(0, LEDGER_RESULT_MAX_CHARS) +
512
+ `\n...[ledger truncated at ${LEDGER_RESULT_MAX_CHARS} chars]`
513
+ : resultSummary;
514
+ await client.execute({
515
+ sql: `INSERT INTO agent_tool_ledger (thread_id, tool_key, result_summary, completed_at)
516
+ VALUES (?, ?, ?, ?)
517
+ ON CONFLICT (thread_id, tool_key) DO UPDATE SET
518
+ result_summary = excluded.result_summary,
519
+ completed_at = excluded.completed_at`,
520
+ args: [threadId, toolKey, capped, Date.now()],
521
+ });
522
+ } catch {
523
+ // Ledger is best-effort; never surface failures to the caller.
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Look up a prior zombie completion for this thread + tool key. Returns the
529
+ * persisted result summary, or `null` when no entry exists.
530
+ */
531
+ export async function readLedgerEntry(
532
+ threadId: string,
533
+ toolKey: string,
534
+ ): Promise<string | null> {
535
+ try {
536
+ await ensureRunTables();
537
+ const client = getDbExec();
538
+ const { rows } = await client.execute({
539
+ sql: `SELECT result_summary FROM agent_tool_ledger WHERE thread_id = ? AND tool_key = ?`,
540
+ args: [threadId, toolKey],
541
+ });
542
+ if (rows.length === 0) return null;
543
+ const row = rows[0] as { result_summary: string };
544
+ return row.result_summary;
545
+ } catch {
546
+ return null;
547
+ }
548
+ }
549
+
550
+ /**
551
+ * Delete ledger entries for a thread. Called after a turn fully completes so
552
+ * old entries don't bleed into the next turn's disambiguation.
553
+ * Best-effort — never throws.
554
+ */
555
+ export async function clearLedgerForThread(threadId: string): Promise<void> {
556
+ try {
557
+ await ensureRunTables();
558
+ const client = getDbExec();
559
+ await client.execute({
560
+ sql: `DELETE FROM agent_tool_ledger WHERE thread_id = ?`,
561
+ args: [threadId],
562
+ });
563
+ } catch {
564
+ // Best-effort.
565
+ }
566
+ }
567
+
568
+ export async function insertRun(
569
+ id: string,
570
+ threadId: string,
571
+ turnId?: string,
572
+ options?: {
573
+ dispatchMode?: "foreground" | "foreground-self-chain" | "background";
574
+ /**
575
+ * JSON-serialized request body for a background dispatch. Persisted on the
576
+ * run row so the self-POST to the background function carries only the
577
+ * tiny `__backgroundRun` marker (Netlify caps background-function request
578
+ * bodies at 256KB); the worker rehydrates the body from this column.
579
+ */
580
+ dispatchPayload?: string;
581
+ },
582
+ ): Promise<void> {
583
+ await ensureRunTables();
584
+ const client = getDbExec();
585
+ const now = Date.now();
586
+ await client.execute({
587
+ sql: `INSERT INTO agent_runs (id, thread_id, status, started_at, heartbeat_at, last_progress_at, turn_id, dispatch_mode, dispatch_payload) VALUES (?, ?, 'running', ?, ?, ?, ?, ?, ?) ON CONFLICT (id) DO NOTHING`,
588
+ args: [
589
+ id,
590
+ threadId,
591
+ now,
592
+ now,
593
+ now,
594
+ turnId ?? id,
595
+ options?.dispatchMode ?? null,
596
+ options?.dispatchPayload ?? null,
597
+ ],
598
+ });
599
+ }
600
+
601
+ /**
602
+ * SQL fragment that resolves the per-row stale cutoff. Background-dispatched
603
+ * runs (`dispatch_mode` starting with `background`) tolerate a much longer gap
604
+ * without a heartbeat (slow Netlify background-function cold-start) before being
605
+ * reaped; every other run keeps the tight 15s window. The bound `now` is
606
+ * subtracted by the resolved window so the comparison is
607
+ * `COALESCE(heartbeat_at, started_at) < (now - window)`.
608
+ *
609
+ * `dispatch_mode` is one of NULL/"foreground" (normal client-continued sync
610
+ * path), "foreground-self-chain" (server-driven continuation at the foreground
611
+ * chunk boundary), "background" (foreground inserted the row for a background
612
+ * dispatch), or "background-processing" (the background worker claimed it). Both
613
+ * background states get the wider window via a LIKE-prefix match.
614
+ */
615
+ function backgroundAwareStaleCutoffSql(): string {
616
+ // `CAST(? AS BIGINT)` is required: without it Postgres infers the param as
617
+ // int4 from the int4 window literals, so the bound `Date.now()` ms epoch
618
+ // overflows int4. The cast keeps the subtraction 64-bit; a no-op on SQLite.
619
+ return `(CAST(? AS BIGINT) - CASE WHEN dispatch_mode = 'background-processing' THEN ${BACKGROUND_PROCESSING_RUN_STALE_MS} WHEN dispatch_mode LIKE 'background%' THEN ${BACKGROUND_RUN_STALE_MS} ELSE ${RUN_STALE_MS} END)`;
620
+ }
621
+
622
+ function terminalRunEventExclusionSql(runIdColumn = "id"): string {
623
+ return `NOT EXISTS (
624
+ SELECT 1 FROM agent_run_events terminal_events
625
+ WHERE terminal_events.run_id = agent_runs.${runIdColumn}
626
+ AND (
627
+ terminal_events.event_data LIKE '{"type":"done"%'
628
+ OR terminal_events.event_data LIKE '{"type":"error"%'
629
+ OR terminal_events.event_data LIKE '{"type":"missing_api_key"%'
630
+ OR terminal_events.event_data LIKE '{"type":"loop_limit"%'
631
+ OR terminal_events.event_data LIKE '{"type":"auto_continue"%'
632
+ )
633
+ )`;
634
+ }
635
+
636
+ /**
637
+ * Liveness basis for the stale reapers: the MOST RECENT of `heartbeat_at`
638
+ * ("process is up", bumped on a 1.5s timer) and `last_progress_at` ("real work
639
+ * is happening", bumped whenever the agent emits an event — including a
640
+ * long-running tool's periodic activity heartbeats, e.g. image generation every
641
+ * 8s), falling back to `started_at`.
642
+ *
643
+ * The reapers previously keyed liveness on `heartbeat_at` alone, so a run that
644
+ * was demonstrably progressing got reaped ('running' → 'errored') the moment the
645
+ * process-liveness write lagged (DB latency, a brief event-loop stall). The
646
+ * producing isolate's SQL-abort check then self-aborted the in-flight action
647
+ * with "Run aborted"; on the durable-background self-chaining path this re-drove
648
+ * the turn in a loop. Honoring progress means a run doing real work is never
649
+ * reaped mid-tool. It can only make reaping MORE conservative — a genuinely dead
650
+ * producer emits neither signal — so a truly-dead run is still reaped.
651
+ *
652
+ * Portable across SQLite and Postgres (CASE + COALESCE only; no GREATEST or
653
+ * scalar MAX, which differ between engines).
654
+ */
655
+ function livenessBasisSql(): string {
656
+ return `(CASE WHEN COALESCE(last_progress_at, started_at) > COALESCE(heartbeat_at, started_at) THEN COALESCE(last_progress_at, started_at) ELSE COALESCE(heartbeat_at, started_at) END)`;
657
+ }
658
+
659
+ /**
660
+ * Additive grace clause for the default (no explicit `maxStaleMs` override)
661
+ * heartbeat/liveness-based stale reap conditions — TRUE (row remains eligible
662
+ * for the surrounding staleness check) unless `in_flight_since` is set AND
663
+ * still inside `IN_FLIGHT_RUN_STALE_GRACE_MS`. A row with no marker set
664
+ * (`in_flight_since IS NULL`, the common case and every pre-existing row
665
+ * before this migration) always evaluates TRUE here, so this can only make
666
+ * reaping MORE conservative — the no-in-flight `BACKGROUND_RUN_STALE_MS` /
667
+ * `RUN_STALE_MS` behavior is unchanged. See `IN_FLIGHT_RUN_STALE_GRACE_MS`'s
668
+ * doc comment for why this is sound and bounded.
669
+ *
670
+ * Binds one param: the same `now` value the surrounding cutoff clause binds.
671
+ */
672
+ function inFlightGraceSql(): string {
673
+ return `(in_flight_since IS NULL OR in_flight_since <= (CAST(? AS BIGINT) - ${IN_FLIGHT_RUN_STALE_GRACE_MS}))`;
674
+ }
675
+
676
+ /**
677
+ * Atomically claim a background-dispatched run for processing. The foreground
678
+ * POST inserts the run row with `dispatch_mode = 'background'`; the FIRST
679
+ * delivery of the background dispatch flips it to `background-processing` and
680
+ * wins the claim. A duplicate Netlify delivery (background functions can be
681
+ * retried) sees `background-processing` and loses, so it no-ops — mirroring
682
+ * `claimAgentTeamRun` returning null. Returns true when this caller won.
683
+ *
684
+ * Idempotent and conditional: the WHERE clause only matches the unclaimed
685
+ * `background` state AND a still-running row, so a reaped/terminal row can't be
686
+ * re-claimed.
687
+ */
688
+ export async function claimBackgroundRun(runId: string): Promise<boolean> {
689
+ await ensureRunTables();
690
+ const client = getDbExec();
691
+ const { rowsAffected } = await client.execute({
692
+ sql: `UPDATE agent_runs
693
+ SET dispatch_mode = 'background-processing'
694
+ WHERE id = ?
695
+ AND status = 'running'
696
+ AND dispatch_mode = 'background'`,
697
+ args: [runId],
698
+ });
699
+ return (rowsAffected ?? 0) > 0;
700
+ }
701
+
702
+ /**
703
+ * Read the claim/lifecycle state of a single run by id — for the foreground
704
+ * circuit-breaker that confirms a background worker actually CLAIMED a run that
705
+ * was dispatched with a Netlify async 202. A 202 only means the invocation was
706
+ * ENQUEUED; if the generated background-function wrapper fails to import/hand off
707
+ * to the route it never reaches `claimBackgroundRun`, leaving the row stuck at
708
+ * `dispatch_mode = 'background'`. `'background-processing'` means a worker won
709
+ * the claim; a terminal `status` means the run already resolved. Returns null if
710
+ * the row is missing.
711
+ */
712
+ export async function readBackgroundRunClaim(runId: string): Promise<{
713
+ dispatchMode: string | null;
714
+ status: string | null;
715
+ diagStage: string | null;
716
+ workerStage: string | null;
717
+ lastLivenessAt: number | null;
718
+ } | null> {
719
+ await ensureRunTables();
720
+ const client = getDbExec();
721
+ const { rows } = await client.execute({
722
+ sql: `SELECT dispatch_mode, status, diag_stage, worker_stage, started_at, heartbeat_at FROM agent_runs WHERE id = ? LIMIT 1`,
723
+ args: [runId],
724
+ });
725
+ const row = rows?.[0] as
726
+ | {
727
+ dispatch_mode?: string | null;
728
+ status?: string | null;
729
+ diag_stage?: string | null;
730
+ worker_stage?: string | null;
731
+ started_at?: number | null;
732
+ heartbeat_at?: number | null;
733
+ }
734
+ | undefined;
735
+ if (!row) return null;
736
+ return {
737
+ dispatchMode: row.dispatch_mode ?? null,
738
+ status: row.status ?? null,
739
+ diagStage: row.diag_stage ?? null,
740
+ workerStage: row.worker_stage ?? null,
741
+ // Same liveness basis the unclaimed-reaper uses (COALESCE(heartbeat_at,
742
+ // started_at)), so the foreground can decide to recover BEFORE the reaper.
743
+ lastLivenessAt: row.heartbeat_at ?? row.started_at ?? null,
744
+ };
745
+ }
746
+
747
+ /**
748
+ * Read the persisted dispatch payload for a background-dispatched run. The
749
+ * worker rehydrates its request body from this column when the dispatch marker
750
+ * carries `payloadRef: true` (the self-POST itself stays under Netlify's 256KB
751
+ * background-function body cap). Returns null when the row is missing or the
752
+ * payload was already cleared (terminal run).
753
+ */
754
+ export async function readRunDispatchPayload(
755
+ runId: string,
756
+ ): Promise<string | null> {
757
+ await ensureRunTables();
758
+ const client = getDbExec();
759
+ const { rows } = await client.execute({
760
+ sql: `SELECT dispatch_payload FROM agent_runs WHERE id = ? LIMIT 1`,
761
+ args: [runId],
762
+ });
763
+ const row = rows?.[0] as { dispatch_payload?: string | null } | undefined;
764
+ if (!row) return null;
765
+ const payload = row.dispatch_payload;
766
+ return typeof payload === "string" && payload.length > 0 ? payload : null;
767
+ }
768
+
769
+ /**
770
+ * Clear a run's persisted dispatch payload once the worker has claimed and
771
+ * rehydrated it — the payload can be large (full chat history) and has no use
772
+ * after the handoff. Best-effort; terminal status writes also clear it.
773
+ */
774
+ export async function clearRunDispatchPayload(runId: string): Promise<void> {
775
+ await ensureRunTables();
776
+ const client = getDbExec();
777
+ await client.execute({
778
+ sql: `UPDATE agent_runs SET dispatch_payload = NULL WHERE id = ?`,
779
+ args: [runId],
780
+ });
781
+ }
782
+
783
+ /**
784
+ * List background-dispatched runs that were never claimed by a worker within
785
+ * the unclaimed grace window. These are handoffs that were lost in flight —
786
+ * the async 202 (or the dispatching worker) died before any worker reached
787
+ * `claimBackgroundRun`. The periodic sweeper reaps them via
788
+ * `reapUnclaimedBackgroundRun` so a lost handoff becomes a loud, attributable
789
+ * error instead of a silent forever-hang. The foreground circuit-breaker
790
+ * already covers initial dispatches while the client is connected; this sweep
791
+ * exists for server-chained continuation handoffs, which have no foreground
792
+ * watching them.
793
+ */
794
+ export async function listUnclaimedBackgroundRunIds(): Promise<string[]> {
795
+ await ensureRunTables();
796
+ const client = getDbExec();
797
+ const { rows } = await client.execute({
798
+ // CAST keeps the ms-epoch param 64-bit on Postgres (see
799
+ // backgroundAwareStaleCutoffSql for the int4-inference failure mode).
800
+ sql: `SELECT id FROM agent_runs
801
+ WHERE status = 'running'
802
+ AND dispatch_mode = 'background'
803
+ AND COALESCE(heartbeat_at, started_at) < (CAST(? AS BIGINT) - ${UNCLAIMED_BACKGROUND_RUN_GRACE_MS})`,
804
+ args: [Date.now()],
805
+ });
806
+ const ids: string[] = [];
807
+ for (const row of rows ?? []) {
808
+ const id = (row as { id?: unknown }).id;
809
+ if (typeof id === "string" && id) ids.push(id);
810
+ }
811
+ return ids;
812
+ }
813
+
814
+ /** A row returned by `listUnclaimedBackgroundRunRows`. */
815
+ export interface UnclaimedBackgroundRunRow {
816
+ id: string;
817
+ /** The row's ORIGINAL `started_at` (never bumped by heartbeats), so a
818
+ * caller can measure total elapsed time since the handoff was first
819
+ * pre-inserted — independent of any liveness bump a redispatch attempt
820
+ * makes along the way. */
821
+ startedAt: number;
822
+ }
823
+
824
+ /**
825
+ * Same eligibility as `listUnclaimedBackgroundRunIds`, but also returns each
826
+ * row's original `started_at` so a caller can bound total redispatch time
827
+ * (see `UNCLAIMED_BACKGROUND_RUN_REDISPATCH_BOUND_MS`) independent of the
828
+ * liveness bumps a redispatch attempt makes along the way. Used by the
829
+ * unclaimed-background-run sweep's redispatch pass; `listUnclaimedBackgroundRunIds`
830
+ * is kept as the simpler, pre-existing surface for callers that only need ids.
831
+ */
832
+ export async function listUnclaimedBackgroundRunRows(): Promise<
833
+ UnclaimedBackgroundRunRow[]
834
+ > {
835
+ await ensureRunTables();
836
+ const client = getDbExec();
837
+ const { rows } = await client.execute({
838
+ // CAST keeps the ms-epoch param 64-bit on Postgres (see
839
+ // backgroundAwareStaleCutoffSql for the int4-inference failure mode).
840
+ sql: `SELECT id, started_at FROM agent_runs
841
+ WHERE status = 'running'
842
+ AND dispatch_mode = 'background'
843
+ AND COALESCE(heartbeat_at, started_at) < (CAST(? AS BIGINT) - ${UNCLAIMED_BACKGROUND_RUN_GRACE_MS})`,
844
+ args: [Date.now()],
845
+ });
846
+ const result: UnclaimedBackgroundRunRow[] = [];
847
+ for (const row of rows ?? []) {
848
+ const id = (row as { id?: unknown }).id;
849
+ const startedAt = (row as { started_at?: unknown }).started_at;
850
+ if (typeof id === "string" && id) {
851
+ result.push({
852
+ id,
853
+ startedAt:
854
+ typeof startedAt === "number" ? startedAt : Number(startedAt) || 0,
855
+ });
856
+ }
857
+ }
858
+ return result;
859
+ }
860
+
861
+ /**
862
+ * Pure decision for the unclaimed-background-run sweep: should THIS row get
863
+ * another redispatch attempt, or has it exceeded
864
+ * `UNCLAIMED_BACKGROUND_RUN_REDISPATCH_BOUND_MS` and must fall back to the
865
+ * loud reap (`reapUnclaimedBackgroundRun`)? Measured from the row's ORIGINAL
866
+ * `started_at` (never bumped by a redispatch's heartbeat write), so this is
867
+ * the total-elapsed-time backstop that keeps recovery bounded — a handoff
868
+ * that cannot be delivered within the window is not spinning forever, it
869
+ * fails loud. Exported as a pure function (no DB access) so the bound is unit
870
+ * -testable independent of the sweep's setInterval wiring.
871
+ */
872
+ export function shouldRedispatchUnclaimedBackgroundRun(
873
+ row: { startedAt: number },
874
+ now: number = Date.now(),
875
+ ): boolean {
876
+ return now - row.startedAt < UNCLAIMED_BACKGROUND_RUN_REDISPATCH_BOUND_MS;
877
+ }
878
+
879
+ /**
880
+ * Count how many runs (chunks) a logical turn has consumed so far. This is the
881
+ * durable per-turn recovery ledger: unlike the in-marker `continuationCount`
882
+ * (which resets whenever a fresh client POST starts a new chain for the same
883
+ * turn), the SQL count survives every recovery path, so it bounds pathological
884
+ * turn loops regardless of which layer initiated each chunk.
885
+ */
886
+ export async function countRunsForTurn(
887
+ threadId: string,
888
+ turnId: string,
889
+ ): Promise<number> {
890
+ await ensureRunTables();
891
+ const client = getDbExec();
892
+ const { rows } = await client.execute({
893
+ sql: `SELECT COUNT(*) AS run_count FROM agent_runs WHERE thread_id = ? AND turn_id = ?`,
894
+ args: [threadId, turnId],
895
+ });
896
+ const raw = (rows?.[0] as { run_count?: unknown } | undefined)?.run_count;
897
+ const count = Number(raw);
898
+ return Number.isFinite(count) ? count : 0;
899
+ }
900
+
901
+ /**
902
+ * Resolve the authenticated owner email for a run by joining it to its chat
903
+ * thread. The durable background worker's self-dispatch is cookieless
904
+ * (HMAC-only — see `AGENT_CHAT_PROCESS_RUN_PATH`), so it has no session for the
905
+ * normal owner resolution and would otherwise be treated as unauthenticated.
906
+ * The thread's `owner_email` was written by the authenticated foreground when it
907
+ * created the thread, so it is a trusted, non-forgeable owner source: only the
908
+ * HMAC-signed `runId` selects the row, and the caller cannot influence which
909
+ * owner that row maps to. Returns null when the run (or its thread) is missing.
910
+ */
911
+ export async function getRunOwnerEmail(runId: string): Promise<string | null> {
912
+ await ensureRunTables();
913
+ const client = getDbExec();
914
+ const { rows } = await client.execute({
915
+ sql: `SELECT t.owner_email AS owner_email FROM agent_runs r JOIN chat_threads t ON r.thread_id = t.id WHERE r.id = ? LIMIT 1`,
916
+ args: [runId],
917
+ });
918
+ const row = rows?.[0] as { owner_email?: string | null } | undefined;
919
+ return row?.owner_email ?? null;
920
+ }
921
+
922
+ /**
923
+ * Atomically acquire a run lease for a thread. Succeeds (returns true) only
924
+ * when no other run for the same thread is currently status='running' with a
925
+ * fresh heartbeat. Works for both Postgres and SQLite: the stale-cutoff
926
+ * comparison lets a dead producer's run be replaced without waiting for the
927
+ * reaper, mirroring the logic in `reapIfStale`.
928
+ *
929
+ * Callers that win the claim then insert the run row normally; callers that
930
+ * lose skip the run and return the existing active runId to the caller.
931
+ */
932
+ export async function tryClaimRunSlot(
933
+ threadId: string,
934
+ maxStaleMs?: number,
935
+ ): Promise<{ claimed: boolean; activeRunId: string | null }> {
936
+ await ensureRunTables();
937
+ const client = getDbExec();
938
+ const now = Date.now();
939
+ // Default: per-row background-aware window so a live background run (which can
940
+ // legitimately go >15s between heartbeats during a cold-start) isn't seen as
941
+ // "free" and double-claimed by a racing foreground POST. An explicit
942
+ // `maxStaleMs` override keeps a flat window for callers that want one.
943
+ if (typeof maxStaleMs === "number") {
944
+ const heartbeatCutoff = now - maxStaleMs;
945
+ const { rows } = await client.execute({
946
+ sql: `SELECT id FROM agent_runs
947
+ WHERE thread_id = ?
948
+ AND status = 'running'
949
+ AND ${terminalRunEventExclusionSql()}
950
+ AND ${livenessBasisSql()} >= ?
951
+ ORDER BY started_at DESC LIMIT 1`,
952
+ args: [threadId, heartbeatCutoff],
953
+ });
954
+ if (rows.length > 0) {
955
+ return { claimed: false, activeRunId: (rows[0] as { id: string }).id };
956
+ }
957
+ return { claimed: true, activeRunId: null };
958
+ }
959
+ const { rows } = await client.execute({
960
+ sql: `SELECT id FROM agent_runs
961
+ WHERE thread_id = ?
962
+ AND status = 'running'
963
+ AND ${terminalRunEventExclusionSql()}
964
+ AND ${livenessBasisSql()} >= ${backgroundAwareStaleCutoffSql()}
965
+ ORDER BY started_at DESC LIMIT 1`,
966
+ args: [threadId, now],
967
+ });
968
+ if (rows.length > 0) {
969
+ const row = rows[0] as { id: string };
970
+ return { claimed: false, activeRunId: row.id };
971
+ }
972
+ return { claimed: true, activeRunId: null };
973
+ }
974
+
975
+ /**
976
+ * Record terminal failure classification for a run so cut-off / errored runs
977
+ * can be surfaced for pattern analysis (see listErroredRuns). Best-effort —
978
+ * never throws, since it runs on the completion path that must not fail the run.
979
+ */
980
+ export async function setRunError(
981
+ runId: string,
982
+ errorCode: string | undefined,
983
+ errorDetail: string | undefined,
984
+ ): Promise<void> {
985
+ if (!errorCode && !errorDetail) return;
986
+ try {
987
+ await ensureRunTables();
988
+ const client = getDbExec();
989
+ await client.execute({
990
+ sql: `UPDATE agent_runs SET error_code = ?, error_detail = ? WHERE id = ?`,
991
+ args: [
992
+ errorCode ?? null,
993
+ errorDetail ? errorDetail.slice(0, 2000) : null,
994
+ runId,
995
+ ],
996
+ });
997
+ } catch {
998
+ // Diagnostics are best-effort; never let them break completion.
999
+ }
1000
+ }
1001
+
1002
+ /**
1003
+ * Record why a run reached its terminal status. Unlike error_code/error_detail,
1004
+ * this is set for successful checkpoint boundaries too (for example
1005
+ * status='completed' + terminal_reason='run_timeout').
1006
+ */
1007
+ export async function setRunTerminalReason(
1008
+ runId: string,
1009
+ terminalReason: string | undefined,
1010
+ ): Promise<void> {
1011
+ if (!terminalReason) return;
1012
+ try {
1013
+ await ensureRunTables();
1014
+ const client = getDbExec();
1015
+ await client.execute({
1016
+ sql: `UPDATE agent_runs SET terminal_reason = ? WHERE id = ?`,
1017
+ args: [terminalReason.slice(0, 200), runId],
1018
+ });
1019
+ } catch {
1020
+ // Diagnostics are best-effort; never let them break completion.
1021
+ }
1022
+ }
1023
+
1024
+ function terminalStatusForEvent(
1025
+ event: AgentChatEvent,
1026
+ ): "completed" | "errored" | null {
1027
+ if (event.type === "error") return "errored";
1028
+ if (event.type === "missing_api_key") return "errored";
1029
+ if (
1030
+ event.type === "done" ||
1031
+ event.type === "loop_limit" ||
1032
+ event.type === "auto_continue"
1033
+ ) {
1034
+ return "completed";
1035
+ }
1036
+ return null;
1037
+ }
1038
+
1039
+ function terminalReasonForEvent(event: AgentChatEvent): string | null {
1040
+ if (event.type === "auto_continue") return event.reason || "auto_continue";
1041
+ if (event.type === "loop_limit") return "loop_limit";
1042
+ if (event.type === "missing_api_key") return "missing_api_key";
1043
+ if (event.type === "error") return `error:${event.errorCode || "unknown"}`;
1044
+ if (event.type === "done") return "done";
1045
+ return null;
1046
+ }
1047
+
1048
+ function isRealFailureTerminalEvent(event: AgentChatEvent): boolean {
1049
+ if (event.type === "missing_api_key") return true;
1050
+ if (event.type !== "error") return false;
1051
+ return event.errorCode !== STALE_RUN_ERROR_EVENT.errorCode;
1052
+ }
1053
+
1054
+ const RUN_RECONCILIATION_TERMINAL_EVENT_LIMIT = 100;
1055
+
1056
+ async function getRunEventForReconciliation(runId: string): Promise<{
1057
+ event: AgentChatEvent;
1058
+ eventAt: number | null;
1059
+ } | null> {
1060
+ const client = getDbExec();
1061
+ const { rows } = await client.execute({
1062
+ sql: `SELECT seq, event_data, event_at
1063
+ FROM agent_run_events
1064
+ WHERE run_id = ?
1065
+ AND (
1066
+ event_data LIKE '{"type":"done"%'
1067
+ OR event_data LIKE '{"type":"error"%'
1068
+ OR event_data LIKE '{"type":"missing_api_key"%'
1069
+ OR event_data LIKE '{"type":"loop_limit"%'
1070
+ OR event_data LIKE '{"type":"auto_continue"%'
1071
+ )
1072
+ ORDER BY seq DESC
1073
+ LIMIT ?`,
1074
+ args: [runId, RUN_RECONCILIATION_TERMINAL_EVENT_LIMIT],
1075
+ });
1076
+ let latestTerminal: {
1077
+ event: AgentChatEvent;
1078
+ eventAt: number | null;
1079
+ } | null = null;
1080
+ for (const row of rows as Array<{
1081
+ event_at?: number | string | null;
1082
+ event_data?: string;
1083
+ }>) {
1084
+ const raw = row.event_data;
1085
+ if (!raw) continue;
1086
+ try {
1087
+ const event = JSON.parse(raw) as AgentChatEvent;
1088
+ if (!terminalStatusForEvent(event) || !terminalReasonForEvent(event)) {
1089
+ continue;
1090
+ }
1091
+ const rawEventAt = row.event_at == null ? NaN : Number(row.event_at);
1092
+ const parsed = {
1093
+ event,
1094
+ eventAt:
1095
+ Number.isFinite(rawEventAt) && rawEventAt > 0 ? rawEventAt : null,
1096
+ };
1097
+ if (!latestTerminal) latestTerminal = parsed;
1098
+ // A real stream failure must not be laundered into success by a later
1099
+ // done/continuation boundary. Keep the synthetic stale-run error special:
1100
+ // that repair marker may be superseded by a later durable done event.
1101
+ if (isRealFailureTerminalEvent(event)) return parsed;
1102
+ } catch {
1103
+ continue;
1104
+ }
1105
+ }
1106
+ return latestTerminal;
1107
+ }
1108
+
1109
+ function errorCodeForTerminalEvent(event: AgentChatEvent): string | null {
1110
+ if (event.type === "missing_api_key")
1111
+ return LLM_MISSING_CREDENTIALS_ERROR_CODE;
1112
+ if (event.type === "error") return event.errorCode ?? null;
1113
+ return null;
1114
+ }
1115
+
1116
+ function errorDetailForTerminalEvent(event: AgentChatEvent): string | null {
1117
+ if (event.type === "missing_api_key") return LLM_MISSING_CREDENTIALS_MESSAGE;
1118
+ if (event.type !== "error") return null;
1119
+ return (event.details || event.error || "").slice(0, 2000) || null;
1120
+ }
1121
+
1122
+ /**
1123
+ * Repair a run whose terminal event was durably appended but whose final
1124
+ * `agent_runs.status` write lost a race with reconnect/reaper code.
1125
+ *
1126
+ * The event ledger is the durable transcript users see. If its latest event is
1127
+ * terminal, the run is no longer alive and must not be converted into a stale
1128
+ * error later. This keeps `agent_runs` and `agent_run_events` from telling two
1129
+ * different stories after delayed DB writes or background function teardown.
1130
+ */
1131
+ export async function reconcileTerminalRunFromEvents(
1132
+ runId: string,
1133
+ ): Promise<boolean> {
1134
+ await ensureRunTables();
1135
+ const latest = await getRunEventForReconciliation(runId);
1136
+ if (!latest) return false;
1137
+ const status = terminalStatusForEvent(latest.event);
1138
+ const terminalReason = terminalReasonForEvent(latest.event);
1139
+ if (!status || !terminalReason) return false;
1140
+
1141
+ const client = getDbExec();
1142
+ const errorCode = errorCodeForTerminalEvent(latest.event);
1143
+ const errorDetail = errorDetailForTerminalEvent(latest.event);
1144
+ const { rowsAffected } = await client.execute({
1145
+ sql: `UPDATE agent_runs
1146
+ SET status = ?,
1147
+ completed_at = COALESCE(completed_at, ?, ${livenessBasisSql()}),
1148
+ error_code = ?,
1149
+ error_detail = ?,
1150
+ terminal_reason = ?
1151
+ WHERE id = ?
1152
+ AND (
1153
+ status = 'running'
1154
+ OR (status = 'errored' AND error_code = ?)
1155
+ )`,
1156
+ args: [
1157
+ status,
1158
+ latest.eventAt,
1159
+ errorCode,
1160
+ errorDetail,
1161
+ terminalReason,
1162
+ runId,
1163
+ STALE_RUN_ERROR_EVENT.errorCode,
1164
+ ],
1165
+ });
1166
+ return (rowsAffected ?? 0) > 0;
1167
+ }
1168
+
1169
+ /**
1170
+ * Diagnostic stage names recorded onto a background run as it moves through the
1171
+ * `_process-run` worker pipeline. Each value is the LAST stage successfully
1172
+ * reached, so a stuck run's `diag_stage` reveals exactly where it died. Ordered
1173
+ * roughly by execution; the literal strings are the client-readable contract.
1174
+ */
1175
+ export const RUN_DIAG_STAGE = {
1176
+ /** The `_process-run` route handler was entered (the request reached Nitro). */
1177
+ routeEntered: "route_entered",
1178
+ /** HMAC auth + body validation in prepareProcessRunRequest FAILED. */
1179
+ authFailed: "auth_failed",
1180
+ /** HMAC auth + body validation PASSED; about to invoke the worker handler. */
1181
+ authPassed: "auth_passed",
1182
+ /** The re-entered agent-chat handler recognized itself as the bg worker. */
1183
+ workerEntered: "worker_entered",
1184
+ /** The worker won the atomic claim (it owns the run). */
1185
+ workerClaimed: "worker_claimed",
1186
+ /** The worker LOST the claim (a duplicate delivery already owns the run). */
1187
+ workerClaimLost: "worker_claim_lost",
1188
+ /** The agent loop started (startRun fired). */
1189
+ workerStarted: "worker_started",
1190
+ /** Last worker setup stage reached before startRun (progressive hang localizer). */
1191
+ workerSetupStep: "worker_setup_step",
1192
+ /** Pre-claim setup timing breakdown (diagnostic). */
1193
+ setupTimings: "setup_timings",
1194
+ /** The worker threw before/while running the loop (message carried in detail). */
1195
+ workerThrew: "worker_threw",
1196
+ /** The route handler caught an error from the worker invocation. */
1197
+ routeThrew: "route_threw",
1198
+ /**
1199
+ * The foreground circuit-breaker fired: a Netlify async 202 was returned but
1200
+ * no background worker CLAIMED the run within the foreground grace window
1201
+ * (the generated function wrapper never reached the route), so the foreground
1202
+ * recovered by running the turn inline. The run still completes for the user.
1203
+ */
1204
+ foregroundInlineRecovery: "foreground_inline_recovery",
1205
+ /**
1206
+ * FIX 3 (durable-background incident): a stale-run reaper (`reapIfStale` /
1207
+ * `reapAllStaleRuns`) found this background chat-turn run dead (heartbeat
1208
+ * stale, no terminal event) and attempted server-owned recovery — detail
1209
+ * carries the outcome (a recovered successor's runId, or why recovery was
1210
+ * declined: not eligible, payload missing, a newer run already exists, or
1211
+ * the per-turn budget is exhausted). See `attemptStaleRunRecovery`.
1212
+ */
1213
+ staleRunRecoveryAttempted: "stale_run_recovery_attempted",
1214
+ } as const;
1215
+
1216
+ export type RunDiagStage = (typeof RUN_DIAG_STAGE)[keyof typeof RUN_DIAG_STAGE];
1217
+
1218
+ /**
1219
+ * Record the last reached pipeline stage (+ optional short detail) for a run.
1220
+ *
1221
+ * PURPOSE: a Netlify background function's logs are not readable from the build
1222
+ * tooling, so when its worker dies silently the run just times out with no clue
1223
+ * WHY. This writes the failure stage straight onto the `agent_runs` row, which
1224
+ * `/runs/active` and `listRunsForThread` surface to the client — so the next
1225
+ * prod run's death cause is readable WITHOUT bg-fn logs. Cheap, additive, and
1226
+ * best-effort: it must never throw or perturb the run (it is called on the auth
1227
+ * path BEFORE a 401 is returned, and around the worker body).
1228
+ *
1229
+ * The stored value is a compact JSON `{ stage, detail?, at }` capped to 2 KB so
1230
+ * a long stack can't bloat the row.
1231
+ */
1232
+ export async function recordRunDiagnostic(
1233
+ runId: string,
1234
+ stage: RunDiagStage,
1235
+ detail?: string,
1236
+ ): Promise<void> {
1237
+ if (!runId) return;
1238
+ try {
1239
+ await ensureRunTables();
1240
+ const client = getDbExec();
1241
+ const payload = JSON.stringify({
1242
+ stage,
1243
+ ...(detail ? { detail: detail.slice(0, 1500) } : {}),
1244
+ at: Date.now(),
1245
+ }).slice(0, 2000);
1246
+ // Worker-setup stages ALSO land in `worker_stage`, a column the foreground's
1247
+ // inline-recovery `setup_timings` write never touches. `/runs/active` only
1248
+ // surfaces a run after it is claimed, and the foreground then overwrites
1249
+ // `diag_stage` — so without this the durable worker's pre-claim progression
1250
+ // (where it stalled before claiming) is unrecoverable. `worker_stage`
1251
+ // preserves the last worker stage reached for post-hoc diagnosis.
1252
+ const isWorkerStage =
1253
+ stage === RUN_DIAG_STAGE.workerSetupStep ||
1254
+ stage === RUN_DIAG_STAGE.workerStarted;
1255
+ if (isWorkerStage) {
1256
+ await client.execute({
1257
+ sql: `UPDATE agent_runs SET diag_stage = ?, worker_stage = ? WHERE id = ?`,
1258
+ args: [payload, payload, runId],
1259
+ });
1260
+ } else {
1261
+ await client.execute({
1262
+ sql: `UPDATE agent_runs SET diag_stage = ? WHERE id = ?`,
1263
+ args: [payload, runId],
1264
+ });
1265
+ }
1266
+ } catch {
1267
+ // Diagnostics are best-effort; never let them break the run or the route.
1268
+ }
1269
+ }
1270
+
1271
+ /** Update the run's liveness heartbeat. Called periodically by run-manager. */
1272
+ export async function updateRunHeartbeat(runId: string): Promise<void> {
1273
+ await ensureRunTables();
1274
+ const client = getDbExec();
1275
+ // Only bump liveness while the row is still running. Zombie producers that
1276
+ // keep their setInterval after status flips to errored/completed used to
1277
+ // rewrite heartbeat_at for minutes after the turn died (seen on slides
1278
+ // prod: heartbeat continued ~400s past completed_at), which confuses
1279
+ // triage and can keep /runs/active looking "fresh" after failure.
1280
+ await client.execute({
1281
+ sql: `UPDATE agent_runs SET heartbeat_at = ? WHERE id = ? AND status = 'running'`,
1282
+ args: [Date.now(), runId],
1283
+ });
1284
+ }
1285
+
1286
+ /**
1287
+ * Bump `last_progress_at` — call this whenever the agent actually emits an
1288
+ * event (token, tool call, message). Distinct from `heartbeat_at` so the
1289
+ * stuck-detector can tell "process alive but nothing happening" from
1290
+ * "process dead." Callers should throttle (run-manager debounces to ~1/s).
1291
+ */
1292
+ export async function bumpRunProgress(runId: string): Promise<void> {
1293
+ await ensureRunTables();
1294
+ const client = getDbExec();
1295
+ const now = Date.now();
1296
+ await client.execute({
1297
+ // Multiple event-persistence paths and serverless isolates can bump the
1298
+ // same run concurrently. A slower, older write must never land after a
1299
+ // newer one and move the user-visible no-progress clock backwards.
1300
+ // CASE keeps this portable across SQLite and Postgres.
1301
+ sql: `UPDATE agent_runs SET last_progress_at = CASE WHEN last_progress_at IS NULL OR last_progress_at < ? THEN ? ELSE last_progress_at END WHERE id = ? AND status = 'running'`,
1302
+ args: [now, now, runId],
1303
+ });
1304
+ }
1305
+
1306
+ /**
1307
+ * Mirror run-manager's in-memory `inFlightWorkCount` 0<->N transitions into
1308
+ * SQL so a stale reaper running in a DIFFERENT isolate can tell a
1309
+ * demonstrably-alive run (holding a tool call or A2A `agent_call` delegation)
1310
+ * apart from a genuinely dead one — see `IN_FLIGHT_RUN_STALE_GRACE_MS`'s doc
1311
+ * comment for the full reasoning.
1312
+ *
1313
+ * `inFlight: true` only writes when the row is still `NULL` — a defense-in-
1314
+ * depth belt-and-suspenders against a nested 1->2 transition clobbering the
1315
+ * ORIGINAL start time with a later one (the caller's own counter already
1316
+ * dedupes 0->1 transitions; this WHERE just makes the write itself
1317
+ * idempotent/order-independent too). `inFlight: false` always clears
1318
+ * unconditionally — if it races a fresh 0->1 write from a *different* tool
1319
+ * finishing/starting back to back, worst case is losing a few seconds of
1320
+ * grace, never gaining an incorrect one.
1321
+ *
1322
+ * Best-effort: callers fire-and-forget (`.catch(() => {})`) so a write
1323
+ * failure here never blocks event emission or aborts the run. If this write
1324
+ * itself fails (the same DB pressure that could be starving the heartbeat),
1325
+ * the row simply gets no grace — never worse than today's behavior.
1326
+ */
1327
+ export async function setRunInFlightMarker(
1328
+ runId: string,
1329
+ inFlight: boolean,
1330
+ ): Promise<void> {
1331
+ await ensureRunTables();
1332
+ const client = getDbExec();
1333
+ if (inFlight) {
1334
+ await client.execute({
1335
+ sql: `UPDATE agent_runs SET in_flight_since = ? WHERE id = ? AND status = 'running' AND in_flight_since IS NULL`,
1336
+ args: [Date.now(), runId],
1337
+ });
1338
+ } else {
1339
+ await client.execute({
1340
+ sql: `UPDATE agent_runs SET in_flight_since = NULL WHERE id = ?`,
1341
+ args: [runId],
1342
+ });
1343
+ }
1344
+ }
1345
+
1346
+ /** A recovery successor row created by `attemptStaleRunRecovery`. */
1347
+ interface StaleRunRecoverySuccessor {
1348
+ successorRunId: string;
1349
+ threadId: string;
1350
+ turnId: string;
1351
+ }
1352
+
1353
+ /**
1354
+ * FIX 3 (durable-background incident) discriminated outcome of a recovery
1355
+ * attempt — recorded as a diag stage (except `not_background`, the
1356
+ * overwhelmingly common case for every ordinary foreground reap) so a
1357
+ * silently-died background worker's fate is diagnosable without bg-fn logs.
1358
+ */
1359
+ type StaleRunRecoveryOutcome =
1360
+ | ({ outcome: "recovered" } & StaleRunRecoverySuccessor)
1361
+ | { outcome: "not_background" }
1362
+ | { outcome: "payload_missing" }
1363
+ | { outcome: "newer_run_exists" }
1364
+ | { outcome: "budget_exhausted" };
1365
+
1366
+ /**
1367
+ * Mirrors `production-agent.ts`'s `generateRunId` — duplicated (not
1368
+ * imported) to avoid a run-store.ts <-> production-agent.ts import cycle
1369
+ * (production-agent.ts already imports run-manager.ts, which imports this
1370
+ * file). Keep the format in sync if that one changes.
1371
+ */
1372
+ function generateRecoveryRunId(): string {
1373
+ return `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1374
+ }
1375
+
1376
+ function staleRecoveryDispatchPayload(payload: string): string {
1377
+ try {
1378
+ const parsed = JSON.parse(payload);
1379
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1380
+ return payload;
1381
+ }
1382
+ return JSON.stringify({
1383
+ ...(parsed as Record<string, unknown>),
1384
+ internalContinuation: true,
1385
+ });
1386
+ } catch {
1387
+ return payload;
1388
+ }
1389
+ }
1390
+
1391
+ /**
1392
+ * FIX 3 (durable-background incident): when a stale-run reaper is about to
1393
+ * flip a BACKGROUND chat-turn run to errored/stale_run, attempt to keep the
1394
+ * TURN alive instead of leaving it dead — a background worker has no
1395
+ * connected client watching it, unlike a foreground run, so nothing else
1396
+ * would ever recover it. Inserts an UNCLAIMED successor row (same turnId,
1397
+ * `dispatch_payload` carried over from the dying run) that the existing
1398
+ * unclaimed-background-run sweep (`agent-chat-plugin.ts`, ~20s fast sweep)
1399
+ * picks up and redispatches automatically — this function never dispatches
1400
+ * anything itself beyond the best-effort immediate attempt its caller fires.
1401
+ *
1402
+ * `db` is threaded through (rather than calling `getDbExec()` internally)
1403
+ * so the caller can run this INSIDE the same transaction as the reap-to-
1404
+ * errored write — see `reapSingleStaleRun` for why that matters.
1405
+ *
1406
+ * Eligibility (ALL must hold, or this is a documented no-op — the caller's
1407
+ * normal loud stale_run failure proceeds unchanged):
1408
+ * - the row is a background chat-turn dispatch (`dispatch_mode` starting
1409
+ * with "background") — a foreground/foreground-self-chain run has a
1410
+ * connected client to recover it via its own `auto_continue` re-POST.
1411
+ * - its `dispatch_payload` is still present — without it there is nothing
1412
+ * to rehydrate the successor's request body from. Read HERE, before the
1413
+ * caller's terminal write (which NULLs `dispatch_payload` on every other
1414
+ * path), so it survives long enough to carry over.
1415
+ * - no newer run already exists for the same turn — avoids stacking a
1416
+ * second successor onto a turn a previous recovery (or a normal
1417
+ * `chainServerDrivenContinuation`) already continued. Combined with the
1418
+ * caller's own atomic "did I win the reap" gate, this guarantees AT MOST
1419
+ * ONE recovery successor per reaped run even under concurrent reapers.
1420
+ * - the per-turn run ledger (`countRunsForTurn`'s underlying query) has
1421
+ * room (`STALE_RUN_RECOVERY_MAX_TURN_RUNS`) — mirrors
1422
+ * `chainServerDrivenContinuation`'s own budget guard so a pathological
1423
+ * turn can't loop forever through reaper-driven recovery either.
1424
+ */
1425
+ async function attemptStaleRunRecovery(
1426
+ db: DbExec,
1427
+ runId: string,
1428
+ ): Promise<StaleRunRecoveryOutcome> {
1429
+ const { rows } = await db.execute({
1430
+ sql: `SELECT thread_id, turn_id, dispatch_mode, dispatch_payload, started_at FROM agent_runs WHERE id = ? LIMIT 1`,
1431
+ args: [runId],
1432
+ });
1433
+ const row = rows?.[0] as
1434
+ | {
1435
+ thread_id?: string | null;
1436
+ turn_id?: string | null;
1437
+ dispatch_mode?: string | null;
1438
+ dispatch_payload?: string | null;
1439
+ started_at?: number | string | null;
1440
+ }
1441
+ | undefined;
1442
+ const dispatchMode = row?.dispatch_mode ?? "";
1443
+ if (!row?.thread_id || !dispatchMode.startsWith("background")) {
1444
+ return { outcome: "not_background" };
1445
+ }
1446
+ const payload = row.dispatch_payload;
1447
+ if (typeof payload !== "string" || payload.length === 0) {
1448
+ return { outcome: "payload_missing" };
1449
+ }
1450
+ const threadId = row.thread_id;
1451
+ const turnId = row.turn_id ?? runId;
1452
+ const startedAt = Number(row.started_at) || 0;
1453
+
1454
+ const { rows: newerRows } = await db.execute({
1455
+ sql: `SELECT id FROM agent_runs WHERE turn_id = ? AND id != ? AND started_at > ? LIMIT 1`,
1456
+ args: [turnId, runId, startedAt],
1457
+ });
1458
+ if ((newerRows?.length ?? 0) > 0) {
1459
+ return { outcome: "newer_run_exists" };
1460
+ }
1461
+
1462
+ // Inline COUNT rather than the exported `countRunsForTurn` (which opens
1463
+ // its own `getDbExec()` connection) — this must read through the SAME `db`
1464
+ // handle as everything else here so it participates in the caller's
1465
+ // transaction when one is active.
1466
+ const { rows: countRows } = await db.execute({
1467
+ sql: `SELECT COUNT(*) AS run_count FROM agent_runs WHERE thread_id = ? AND turn_id = ?`,
1468
+ args: [threadId, turnId],
1469
+ });
1470
+ const turnRunCount = Number(
1471
+ (countRows?.[0] as { run_count?: unknown } | undefined)?.run_count,
1472
+ );
1473
+ if (
1474
+ Number.isFinite(turnRunCount) &&
1475
+ turnRunCount > STALE_RUN_RECOVERY_MAX_TURN_RUNS
1476
+ ) {
1477
+ return { outcome: "budget_exhausted" };
1478
+ }
1479
+
1480
+ const successorRunId = generateRecoveryRunId();
1481
+ const now = Date.now();
1482
+ await db.execute({
1483
+ sql: `INSERT INTO agent_runs (id, thread_id, status, started_at, heartbeat_at, last_progress_at, turn_id, dispatch_mode, dispatch_payload) VALUES (?, ?, 'running', ?, ?, ?, ?, 'background', ?) ON CONFLICT (id) DO NOTHING`,
1484
+ args: [
1485
+ successorRunId,
1486
+ threadId,
1487
+ now,
1488
+ now,
1489
+ now,
1490
+ turnId,
1491
+ staleRecoveryDispatchPayload(payload),
1492
+ ],
1493
+ });
1494
+ return { outcome: "recovered", successorRunId, threadId, turnId };
1495
+ }
1496
+
1497
+ /**
1498
+ * FIX 3: best-effort immediate redispatch for a stale-run recovery
1499
+ * successor, mirroring the "Unclaimed background-run sweep" redispatch
1500
+ * marker in `agent-chat-plugin.ts` (deliberately omits `continuationCount`
1501
+ * — see that file's comment — so a reaper-recovered chunk starts a fresh
1502
+ * nested-dispatch segment at depth 0). Fire-and-forget and never awaited by
1503
+ * callers: the successor row is already durably persisted (unclaimed,
1504
+ * `dispatch_payload` set) regardless of whether this dispatch lands, so a
1505
+ * failure here just means the existing fast unclaimed-background-run sweep
1506
+ * (`UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS`, ~20s) picks it up instead — the
1507
+ * row is left claimable, never marked errored, by construction (this
1508
+ * function never writes to `agent_runs`).
1509
+ */
1510
+ function attemptStaleRunRecoveryDispatch(successorRunId: string): void {
1511
+ void (async () => {
1512
+ try {
1513
+ const [
1514
+ {
1515
+ AGENT_CHAT_BACKGROUND_RUN_FIELD,
1516
+ resolveAgentChatProcessRunDispatchPath,
1517
+ },
1518
+ { fireInternalDispatch },
1519
+ ] = await Promise.all([
1520
+ import("./durable-background.js"),
1521
+ import("../server/self-dispatch.js"),
1522
+ ]);
1523
+ await fireInternalDispatch({
1524
+ path: resolveAgentChatProcessRunDispatchPath(),
1525
+ taskId: successorRunId,
1526
+ body: {
1527
+ internalContinuation: true,
1528
+ [AGENT_CHAT_BACKGROUND_RUN_FIELD]: {
1529
+ runId: successorRunId,
1530
+ payloadRef: true,
1531
+ },
1532
+ },
1533
+ });
1534
+ } catch (err) {
1535
+ console.error(
1536
+ "[run-store] stale-run recovery redispatch attempt failed (leaving successor claimable for the sweep):",
1537
+ successorRunId,
1538
+ err instanceof Error ? err.message : err,
1539
+ );
1540
+ }
1541
+ })();
1542
+ }
1543
+
1544
+ /**
1545
+ * Shared reap-to-stale-error implementation for a SINGLE run, used by both
1546
+ * `reapIfStale` (per-row, hot read path) and `reapAllStaleRuns` (per-row loop
1547
+ * over a stale-row snapshot — a row's own staleness is re-checked at UPDATE
1548
+ * time exactly as the prior bulk UPDATE did, so a heartbeat that lands
1549
+ * between the snapshot SELECT and this call naturally excludes the row).
1550
+ *
1551
+ * FIX 3: wraps the reap-to-errored write together with the recovery-
1552
+ * successor insert (`attemptStaleRunRecovery`) in ONE transaction so a
1553
+ * client polling `/runs/active` mid-recovery can never observe "errored, no
1554
+ * successor" — both writes commit together or not at all. A recovery
1555
+ * failure (thrown inside the transaction callback) is caught locally and
1556
+ * never rolls back the reap itself: the reap is the critical path, recovery
1557
+ * is strictly additive.
1558
+ */
1559
+ async function reapSingleStaleRun(
1560
+ runId: string,
1561
+ maxStaleMs?: number,
1562
+ ): Promise<boolean> {
1563
+ const completedAt = Date.now();
1564
+ // Background-dispatched runs get the wider stale window so a slow cold-start
1565
+ // isn't reaped; foreground runs keep the tight 15s window. An explicit
1566
+ // override forces a flat window for callers that want one — and, like that
1567
+ // override already bypasses the background-aware widening, it also bypasses
1568
+ // the in-flight grace below: an explicit `maxStaleMs` is an exact,
1569
+ // caller-chosen window and stays exact.
1570
+ const staleClause =
1571
+ typeof maxStaleMs === "number"
1572
+ ? `${livenessBasisSql()} < ?`
1573
+ : `${livenessBasisSql()} < ${backgroundAwareStaleCutoffSql()} AND ${inFlightGraceSql()}`;
1574
+ const staleArgs =
1575
+ typeof maxStaleMs === "number"
1576
+ ? [completedAt - maxStaleMs]
1577
+ : // First `?` is backgroundAwareStaleCutoffSql's CAST param, second is
1578
+ // inFlightGraceSql's — both bound to the same "now".
1579
+ [completedAt, completedAt];
1580
+ const updateSql = `UPDATE agent_runs
1581
+ SET status = 'errored',
1582
+ completed_at = ?,
1583
+ error_code = ?,
1584
+ error_detail = ?,
1585
+ terminal_reason = ?
1586
+ WHERE id = ?
1587
+ AND status = 'running'
1588
+ AND ${terminalRunEventExclusionSql()}
1589
+ AND ${staleClause}`;
1590
+ const updateArgs = [
1591
+ completedAt,
1592
+ STALE_RUN_ERROR_EVENT.errorCode,
1593
+ STALE_RUN_ERROR_EVENT.details,
1594
+ STALE_RUN_ERROR_EVENT.errorCode,
1595
+ runId,
1596
+ ...staleArgs,
1597
+ ];
1598
+
1599
+ const client = getDbExec();
1600
+ let reaped = false;
1601
+ let outcome: StaleRunRecoveryOutcome | null = null;
1602
+ if (client.transaction) {
1603
+ await client.transaction(async (tx) => {
1604
+ const { rowsAffected } = await tx.execute({
1605
+ sql: updateSql,
1606
+ args: updateArgs,
1607
+ });
1608
+ reaped = (rowsAffected ?? 0) > 0;
1609
+ if (reaped) {
1610
+ outcome = await attemptStaleRunRecovery(tx, runId).catch(() => null);
1611
+ }
1612
+ });
1613
+ } else {
1614
+ // No transaction primitive on this DbExec (every current implementation
1615
+ // provides one — see db/client.ts — so this is a defensive fallback,
1616
+ // not an expected path). Ordering the recovery design explicitly
1617
+ // tolerates: insert the successor FIRST, then flip the old row terminal
1618
+ // — a still-"running" old row plus an unclaimed successor is a safe
1619
+ // intermediate state; the reverse (errored with no successor briefly
1620
+ // visible) is not. Narrow, accepted gap versus the transactional path:
1621
+ // two concurrent reapers racing this exact fallback on the exact same
1622
+ // run could each pass the "no newer run" check before either inserts,
1623
+ // producing two successors for one turn.
1624
+ outcome = await attemptStaleRunRecovery(client, runId).catch(() => null);
1625
+ const { rowsAffected } = await client.execute({
1626
+ sql: updateSql,
1627
+ args: updateArgs,
1628
+ });
1629
+ reaped = (rowsAffected ?? 0) > 0;
1630
+ }
1631
+
1632
+ if (reaped && outcome && outcome.outcome !== "not_background") {
1633
+ const detail =
1634
+ outcome.outcome === "recovered"
1635
+ ? `recovered successorRunId=${outcome.successorRunId}`
1636
+ : `declined reason=${outcome.outcome}`;
1637
+ await recordRunDiagnostic(
1638
+ runId,
1639
+ RUN_DIAG_STAGE.staleRunRecoveryAttempted,
1640
+ detail,
1641
+ ).catch(() => {});
1642
+ if (outcome.outcome === "recovered") {
1643
+ attemptStaleRunRecoveryDispatch(outcome.successorRunId);
1644
+ }
1645
+ }
1646
+ return reaped;
1647
+ }
1648
+
1649
+ /**
1650
+ * If the given run is marked "running" in SQL but its heartbeat is stale
1651
+ * (producer likely crashed), flip it to "errored" so watchers stop waiting.
1652
+ * Returns true if the row was reaped.
1653
+ */
1654
+ export async function reapIfStale(
1655
+ runId: string,
1656
+ maxStaleMs?: number,
1657
+ ): Promise<boolean> {
1658
+ await ensureRunTables();
1659
+ if (await reconcileTerminalRunFromEvents(runId)) return false;
1660
+ const reaped = await reapSingleStaleRun(runId, maxStaleMs);
1661
+ if (!reaped && (await reconcileTerminalRunFromEvents(runId))) return false;
1662
+ if (reaped) {
1663
+ await safeAppendTerminalRunEvent(
1664
+ runId,
1665
+ STALE_RUN_ERROR_EVENT,
1666
+ "reap-if-stale",
1667
+ );
1668
+ }
1669
+ return reaped;
1670
+ }
1671
+
1672
+ /**
1673
+ * FALLBACK HARDENING for the "dispatched with 202 but the worker never started"
1674
+ * case. A background-dispatched run sits in `dispatch_mode = 'background'` until
1675
+ * the worker wins `claimBackgroundRun` (which flips it to
1676
+ * `background-processing`). If the worker silently dies (e.g. the bg-fn 401s
1677
+ * before it can claim), the row stays `background`, never heartbeats again, and
1678
+ * — because dispatch returned 202 — the foreground already returned the SSE
1679
+ * stream, so the existing fast-fail inline fallback never engaged. The run would
1680
+ * otherwise hang for the full durable background window and then error opaquely.
1681
+ *
1682
+ * This reaps such a run EARLY and DISTINCTLY: a row that is still unclaimed
1683
+ * (`dispatch_mode = 'background'`) past the tight `UNCLAIMED_BACKGROUND_RUN_GRACE_MS`
1684
+ * grace is a dead handoff — there is no live worker to protect with the wide
1685
+ * window — so we flip it to `errored` with the recoverable
1686
+ * `background_worker_never_started` code. The client's existing recoverable-error
1687
+ * path then lets the user (or auto-recovery) re-drive the turn. Idempotent and
1688
+ * conditional: only an unclaimed, still-running, grace-exceeded row matches, so a
1689
+ * claimed worker, a fresh dispatch, or a terminal row is never touched.
1690
+ *
1691
+ * Returns true when this call reaped the run.
1692
+ */
1693
+ export async function reapUnclaimedBackgroundRun(
1694
+ runId: string,
1695
+ ): Promise<boolean> {
1696
+ await ensureRunTables();
1697
+ const client = getDbExec();
1698
+ const completedAt = Date.now();
1699
+ const cutoff = completedAt - UNCLAIMED_BACKGROUND_RUN_GRACE_MS;
1700
+ const { rowsAffected } = await client.execute({
1701
+ sql: `UPDATE agent_runs
1702
+ SET status = 'errored',
1703
+ completed_at = ?,
1704
+ error_code = ?,
1705
+ error_detail = ?,
1706
+ terminal_reason = ?
1707
+ WHERE id = ?
1708
+ AND status = 'running'
1709
+ AND dispatch_mode = 'background'
1710
+ AND COALESCE(heartbeat_at, started_at) < ?`,
1711
+ args: [
1712
+ completedAt,
1713
+ UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT.errorCode,
1714
+ UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT.details,
1715
+ UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT.errorCode,
1716
+ runId,
1717
+ cutoff,
1718
+ ],
1719
+ });
1720
+ const reaped = (rowsAffected ?? 0) > 0;
1721
+ if (reaped) {
1722
+ await recordRunDiagnostic(
1723
+ runId,
1724
+ RUN_DIAG_STAGE.workerThrew,
1725
+ "unclaimed background dispatch reaped (worker never claimed the run)",
1726
+ );
1727
+ await safeAppendTerminalRunEvent(
1728
+ runId,
1729
+ UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT,
1730
+ "reap-unclaimed-background",
1731
+ );
1732
+ }
1733
+ return reaped;
1734
+ }
1735
+
1736
+ export async function updateRunStatus(
1737
+ runId: string,
1738
+ status: "completed" | "errored" | "aborted",
1739
+ ): Promise<void> {
1740
+ await ensureRunTables();
1741
+ const client = getDbExec();
1742
+ await client.execute({
1743
+ // Terminal writes also drop the (potentially large) dispatch payload —
1744
+ // it only exists to rehydrate a not-yet-claimed background worker.
1745
+ sql: `UPDATE agent_runs SET status = ?, completed_at = ?, dispatch_payload = NULL WHERE id = ?`,
1746
+ args: [status, Date.now(), runId],
1747
+ });
1748
+ }
1749
+
1750
+ /**
1751
+ * Conditional terminal status write: only updates if the row still belongs to
1752
+ * this run AND is still status='running'. Returns true when the update landed.
1753
+ *
1754
+ * This is the safe variant used by the producer's finally block so a zombie run
1755
+ * (reaped while executing) can never clobber the status written by the reaper
1756
+ * or a replacement run.
1757
+ */
1758
+ export async function updateRunStatusIfRunning(
1759
+ runId: string,
1760
+ status: "completed" | "errored" | "aborted",
1761
+ ): Promise<boolean> {
1762
+ await ensureRunTables();
1763
+ const client = getDbExec();
1764
+ const { rowsAffected } = await client.execute({
1765
+ // Terminal writes also drop the (potentially large) dispatch payload —
1766
+ // it only exists to rehydrate a not-yet-claimed background worker.
1767
+ sql: `UPDATE agent_runs SET status = ?, completed_at = ?, dispatch_payload = NULL WHERE id = ? AND status = 'running'`,
1768
+ args: [status, Date.now(), runId],
1769
+ });
1770
+ return (rowsAffected ?? 0) > 0;
1771
+ }
1772
+
1773
+ /** Read the current status of a run row. Returns null when the row is missing. */
1774
+ export async function getRunStatus(runId: string): Promise<string | null> {
1775
+ await ensureRunTables();
1776
+ const client = getDbExec();
1777
+ const { rows } = await client.execute({
1778
+ sql: `SELECT status FROM agent_runs WHERE id = ?`,
1779
+ args: [runId],
1780
+ });
1781
+ if (rows.length === 0) return null;
1782
+ return String((rows[0] as { status: string }).status);
1783
+ }
1784
+
1785
+ export async function markRunAborted(
1786
+ runId: string,
1787
+ reason?: string,
1788
+ ): Promise<void> {
1789
+ await ensureRunTables();
1790
+ const client = getDbExec();
1791
+ const { rowsAffected } = await client.execute({
1792
+ sql: `UPDATE agent_runs SET status = 'aborted', abort_reason = ?, completed_at = ?, terminal_reason = ? WHERE id = ? AND status = 'running'`,
1793
+ args: [reason ?? "user", Date.now(), `aborted:${reason ?? "user"}`, runId],
1794
+ });
1795
+ if ((rowsAffected ?? 0) > 0) {
1796
+ await safeAppendTerminalRunEvent(runId, { type: "done" }, "mark-aborted");
1797
+ }
1798
+ }
1799
+
1800
+ function turnAbortMarkerRunId(turnId: string): string {
1801
+ return `turn-abort-${turnId}`;
1802
+ }
1803
+
1804
+ /** Records Stop before a foreground request has created its real run row. */
1805
+ export async function markTurnAborted(
1806
+ threadId: string,
1807
+ turnId: string,
1808
+ reason: string = "user",
1809
+ ): Promise<void> {
1810
+ await ensureRunTables();
1811
+ const now = Date.now();
1812
+ const client = getDbExec();
1813
+ await client.execute({
1814
+ sql: `INSERT INTO agent_runs (id, thread_id, status, abort_reason, started_at, completed_at, heartbeat_at, last_progress_at, turn_id, terminal_reason, dispatch_mode) VALUES (?, ?, 'aborted', ?, ?, ?, ?, ?, ?, ?, 'turn-abort') ON CONFLICT (id) DO NOTHING`,
1815
+ args: [
1816
+ turnAbortMarkerRunId(turnId),
1817
+ threadId,
1818
+ reason,
1819
+ now,
1820
+ now,
1821
+ now,
1822
+ now,
1823
+ turnId,
1824
+ `aborted:${reason}`,
1825
+ ],
1826
+ });
1827
+ const { rows } = await client.execute({
1828
+ sql: `SELECT id FROM agent_runs WHERE thread_id = ? AND turn_id = ? AND status = 'running'`,
1829
+ args: [threadId, turnId],
1830
+ });
1831
+ const runIds = rows
1832
+ .map((row) => String((row as { id?: unknown }).id ?? ""))
1833
+ .filter(Boolean);
1834
+ if (runIds.length === 0) return;
1835
+ await client.execute({
1836
+ sql: `UPDATE agent_runs SET status = 'aborted', abort_reason = ?, completed_at = ?, terminal_reason = ? WHERE thread_id = ? AND turn_id = ? AND status = 'running'`,
1837
+ args: [reason, Date.now(), `aborted:${reason}`, threadId, turnId],
1838
+ });
1839
+ await Promise.all(
1840
+ runIds.map((runId) =>
1841
+ safeAppendTerminalRunEvent(runId, { type: "done" }, "mark-turn-aborted"),
1842
+ ),
1843
+ );
1844
+ }
1845
+
1846
+ export async function isTurnAborted(
1847
+ threadId: string,
1848
+ turnId: string,
1849
+ ): Promise<boolean> {
1850
+ await ensureRunTables();
1851
+ const { rows } = await getDbExec().execute({
1852
+ sql: `SELECT id FROM agent_runs WHERE id = ? AND thread_id = ? AND status = 'aborted' LIMIT 1`,
1853
+ args: [turnAbortMarkerRunId(turnId), threadId],
1854
+ });
1855
+ return rows.length > 0;
1856
+ }
1857
+
1858
+ export async function isRunAborted(runId: string): Promise<boolean> {
1859
+ return (await getRunAbortState(runId)).aborted;
1860
+ }
1861
+
1862
+ export async function getRunAbortState(
1863
+ runId: string,
1864
+ ): Promise<{ aborted: boolean; reason?: string }> {
1865
+ await ensureRunTables();
1866
+ const client = getDbExec();
1867
+ const { rows } = await client.execute({
1868
+ sql: `SELECT status, abort_reason FROM agent_runs WHERE id = ?`,
1869
+ args: [runId],
1870
+ });
1871
+ if (rows.length === 0) return { aborted: false };
1872
+ const row = rows[0] as { status: string; abort_reason?: string | null };
1873
+ if (row.status !== "aborted") return { aborted: false };
1874
+ return {
1875
+ aborted: true,
1876
+ ...(row.abort_reason ? { reason: row.abort_reason } : {}),
1877
+ };
1878
+ }
1879
+
1880
+ export async function insertRunEvent(
1881
+ runId: string,
1882
+ seq: number,
1883
+ eventData: string,
1884
+ ): Promise<void> {
1885
+ await ensureRunTables();
1886
+ const client = getDbExec();
1887
+ // ON CONFLICT DO NOTHING: a (runId, seq) collision can happen on the
1888
+ // soft-timeout / terminal-event path where `pendingTerminalEvent` was
1889
+ // assigned a seq that later gets reused by an event pushed after it.
1890
+ // It can also race with `appendTerminalRunEvent` (max-seq + 1) when a
1891
+ // run aborts at the same time the producer emits its final event.
1892
+ // Treat the second write as a no-op so the run completes cleanly.
1893
+ // A producer can also outlive a stale-run reap or explicit abort. Reject
1894
+ // those zombie writes atomically once the run row is terminal. Allowing a
1895
+ // missing run row preserves the existing startRun race where the producer
1896
+ // may emit before the non-blocking run-row insert has landed.
1897
+ await client.execute({
1898
+ sql: `INSERT INTO agent_run_events (run_id, seq, event_at, event_data)
1899
+ SELECT ?, ?, ?, ?
1900
+ WHERE NOT EXISTS (
1901
+ SELECT 1 FROM agent_runs
1902
+ WHERE id = ? AND status <> 'running'
1903
+ )
1904
+ ON CONFLICT (run_id, seq) DO NOTHING`,
1905
+ args: [runId, seq, Date.now(), eventData, runId],
1906
+ });
1907
+ }
1908
+
1909
+ export async function getRunEventsSince(
1910
+ runId: string,
1911
+ fromSeq: number,
1912
+ ): Promise<Array<{ seq: number; eventData: string }>> {
1913
+ await ensureRunTables();
1914
+ const client = getDbExec();
1915
+ const { rows } = await client.execute({
1916
+ sql: `SELECT seq, event_data FROM agent_run_events WHERE run_id = ? AND seq >= ? ORDER BY seq ASC`,
1917
+ args: [runId, fromSeq],
1918
+ });
1919
+ return rows.map((r) => {
1920
+ const row = r as { seq: number | string; event_data: string };
1921
+ return { seq: Number(row.seq), eventData: row.event_data };
1922
+ });
1923
+ }
1924
+
1925
+ export async function getRunById(runId: string): Promise<{
1926
+ id: string;
1927
+ threadId: string;
1928
+ status: string;
1929
+ startedAt: number;
1930
+ errorCode: string | null;
1931
+ errorDetail: string | null;
1932
+ } | null> {
1933
+ await ensureRunTables();
1934
+ const client = getDbExec();
1935
+ const { rows } = await client.execute({
1936
+ sql: `SELECT id, thread_id, status, started_at, error_code, error_detail FROM agent_runs WHERE id = ?`,
1937
+ args: [runId],
1938
+ });
1939
+ if (rows.length === 0) return null;
1940
+ const r = rows[0] as {
1941
+ id: string;
1942
+ thread_id: string;
1943
+ status: string;
1944
+ started_at: number | string;
1945
+ error_code?: string | null;
1946
+ error_detail?: string | null;
1947
+ };
1948
+ return {
1949
+ id: r.id,
1950
+ threadId: r.thread_id,
1951
+ status: r.status,
1952
+ startedAt: Number(r.started_at),
1953
+ errorCode: r.error_code ?? null,
1954
+ errorDetail: r.error_detail ?? null,
1955
+ };
1956
+ }
1957
+
1958
+ /**
1959
+ * Read the latest terminal event already persisted for a run, if any.
1960
+ * Used by SSE reconnect when the client cursor is already past that event
1961
+ * (so `getRunEventsSince` returns empty) but the row is terminal — we must
1962
+ * replay the REAL error instead of inventing a stale_run card.
1963
+ */
1964
+ export async function getLastTerminalRunEvent(
1965
+ runId: string,
1966
+ ): Promise<{ seq: number; event: Record<string, unknown> } | null> {
1967
+ await ensureRunTables();
1968
+ const client = getDbExec();
1969
+ const { rows } = await client.execute({
1970
+ sql: `SELECT seq, event_data FROM agent_run_events WHERE run_id = ? ORDER BY seq DESC LIMIT 1`,
1971
+ args: [runId],
1972
+ });
1973
+ const last = rows[0] as
1974
+ | { seq?: number | string; event_data?: string }
1975
+ | undefined;
1976
+ if (!last?.event_data) return null;
1977
+ try {
1978
+ const parsed = JSON.parse(last.event_data) as Record<string, unknown>;
1979
+ if (
1980
+ parsed?.type === "done" ||
1981
+ parsed?.type === "error" ||
1982
+ parsed?.type === "missing_api_key" ||
1983
+ parsed?.type === "loop_limit" ||
1984
+ parsed?.type === "auto_continue"
1985
+ ) {
1986
+ return { seq: Number(last.seq ?? 0), event: parsed };
1987
+ }
1988
+ } catch {
1989
+ return null;
1990
+ }
1991
+ return null;
1992
+ }
1993
+
1994
+ /**
1995
+ * Build the terminal error payload to stream when an `errored` run has no
1996
+ * in-cursor terminal event. Prefer the real last terminal event, then the
1997
+ * row's error_code/error_detail, and only then the generic stale_run card.
1998
+ */
1999
+ export function resolveErroredRunTerminalEvent(run: {
2000
+ errorCode?: string | null;
2001
+ errorDetail?: string | null;
2002
+ }): {
2003
+ event: Record<string, unknown>;
2004
+ shouldPersist: boolean;
2005
+ } {
2006
+ const code = typeof run.errorCode === "string" ? run.errorCode.trim() : "";
2007
+ const detail =
2008
+ typeof run.errorDetail === "string" ? run.errorDetail.trim() : "";
2009
+ if (code === STALE_RUN_ERROR_EVENT.errorCode) {
2010
+ return { event: { ...STALE_RUN_ERROR_EVENT }, shouldPersist: true };
2011
+ }
2012
+ if (detail || (code && code !== "unknown")) {
2013
+ return {
2014
+ event: {
2015
+ type: "error",
2016
+ error: detail || "The agent run failed.",
2017
+ ...(code && code !== "unknown" ? { errorCode: code } : {}),
2018
+ recoverable: true,
2019
+ },
2020
+ shouldPersist: true,
2021
+ };
2022
+ }
2023
+ return { event: { ...STALE_RUN_ERROR_EVENT }, shouldPersist: true };
2024
+ }
2025
+
2026
+ export async function getRunByThread(
2027
+ threadId: string,
2028
+ options?: { includeTerminal?: boolean },
2029
+ ): Promise<{
2030
+ id: string;
2031
+ threadId: string;
2032
+ turnId?: string | null;
2033
+ status: string;
2034
+ startedAt: number;
2035
+ heartbeatAt: number | null;
2036
+ completedAt: number | null;
2037
+ lastProgressAt: number | null;
2038
+ dispatchMode: string | null;
2039
+ terminalReason: string | null;
2040
+ diagStage: string | null;
2041
+ /**
2042
+ * Raw `in_flight_since` marker (see `setRunInFlightMarker`) — non-null
2043
+ * exactly when run-manager's in-memory `inFlightWorkCount` was last known
2044
+ * (from THIS row's own producer) to be > 0: a tool call or A2A `agent_call`
2045
+ * delegation is open and has not yet resolved. Callers that want the
2046
+ * authoritative "does this run currently hold live work" signal (e.g. the
2047
+ * `hasInFlightWork` wire field on `/runs/active`) should test this for
2048
+ * non-null, not re-derive their own notion of in-flight — see
2049
+ * `getActiveRunForThreadAsync` in run-manager.ts.
2050
+ */
2051
+ inFlightSince: number | null;
2052
+ } | null> {
2053
+ await ensureRunTables();
2054
+ const client = getDbExec();
2055
+ const sql = options?.includeTerminal
2056
+ ? `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, dispatch_mode, terminal_reason, diag_stage, error_code, in_flight_since FROM agent_runs WHERE thread_id = ? ORDER BY started_at DESC LIMIT 1`
2057
+ : `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, dispatch_mode, terminal_reason, diag_stage, error_code, in_flight_since FROM agent_runs WHERE thread_id = ? AND status = 'running' ORDER BY started_at DESC LIMIT 1`;
2058
+ const { rows } = await client.execute({ sql, args: [threadId] });
2059
+ if (rows.length === 0) return null;
2060
+ const r = rows[0] as {
2061
+ id: string;
2062
+ thread_id: string;
2063
+ turn_id?: string | null;
2064
+ status: string;
2065
+ started_at: number | string;
2066
+ heartbeat_at: number | string | null;
2067
+ completed_at: number | string | null;
2068
+ last_progress_at: number | string | null;
2069
+ dispatch_mode?: string | null;
2070
+ terminal_reason?: string | null;
2071
+ diag_stage?: string | null;
2072
+ error_code?: string | null;
2073
+ in_flight_since?: number | string | null;
2074
+ };
2075
+ const canReconcileFromEvents =
2076
+ r.status === "running" ||
2077
+ (r.status === "errored" &&
2078
+ r.error_code === STALE_RUN_ERROR_EVENT.errorCode);
2079
+ if (canReconcileFromEvents && (await reconcileTerminalRunFromEvents(r.id))) {
2080
+ return getRunByThread(threadId, options);
2081
+ }
2082
+ return {
2083
+ id: r.id,
2084
+ threadId: r.thread_id,
2085
+ turnId: r.turn_id ?? null,
2086
+ status: r.status,
2087
+ startedAt: Number(r.started_at),
2088
+ heartbeatAt: r.heartbeat_at == null ? null : Number(r.heartbeat_at),
2089
+ completedAt: r.completed_at == null ? null : Number(r.completed_at),
2090
+ lastProgressAt:
2091
+ r.last_progress_at == null ? null : Number(r.last_progress_at),
2092
+ dispatchMode: r.dispatch_mode ?? null,
2093
+ terminalReason: r.terminal_reason ?? null,
2094
+ diagStage: r.diag_stage ?? null,
2095
+ inFlightSince: r.in_flight_since == null ? null : Number(r.in_flight_since),
2096
+ };
2097
+ }
2098
+
2099
+ export interface AgentRunSummary {
2100
+ id: string;
2101
+ threadId: string;
2102
+ turnId: string | null;
2103
+ status: string;
2104
+ startedAt: number;
2105
+ heartbeatAt: number | null;
2106
+ completedAt: number | null;
2107
+ lastProgressAt: number | null;
2108
+ errorCode: string | null;
2109
+ abortReason: string | null;
2110
+ dispatchMode: string | null;
2111
+ terminalReason: string | null;
2112
+ /** Last reached `_process-run` worker stage (JSON `{stage,detail?,at}`). */
2113
+ diagStage: string | null;
2114
+ }
2115
+
2116
+ export async function listRunsForThread(
2117
+ threadId: string,
2118
+ options: { limit?: number } = {},
2119
+ ): Promise<AgentRunSummary[]> {
2120
+ await ensureRunTables();
2121
+ const limit = Math.min(Math.max(options.limit ?? 10, 1), 50);
2122
+ const client = getDbExec();
2123
+ let { rows } = await client.execute({
2124
+ sql: `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, error_code, abort_reason, dispatch_mode, terminal_reason, diag_stage
2125
+ FROM agent_runs
2126
+ WHERE thread_id = ?
2127
+ ORDER BY started_at DESC
2128
+ LIMIT ?`,
2129
+ args: [threadId, limit],
2130
+ });
2131
+ const reconcileCandidateIds: string[] = [];
2132
+ for (const r of rows) {
2133
+ const row = r as {
2134
+ id?: string;
2135
+ status?: string;
2136
+ error_code?: string | null;
2137
+ };
2138
+ const runId = row.id;
2139
+ if (!runId) continue;
2140
+ const canReconcileFromEvents =
2141
+ row.status === "running" ||
2142
+ (row.status === "errored" &&
2143
+ row.error_code === STALE_RUN_ERROR_EVENT.errorCode);
2144
+ if (!canReconcileFromEvents) continue;
2145
+ reconcileCandidateIds.push(runId);
2146
+ }
2147
+ // Each candidate's reconciliation is independently fenced by the UPDATE's
2148
+ // WHERE clause (status = 'running' OR errored-with-stale-code) inside
2149
+ // reconcileTerminalRunFromEvents, keyed on that run's own id — reconciling
2150
+ // several stale runs in parallel is safe and avoids N sequential
2151
+ // SELECT+UPDATE round-trip pairs on a shared-thread page load.
2152
+ const reconcileResults = await Promise.all(
2153
+ reconcileCandidateIds.map((runId) =>
2154
+ reconcileTerminalRunFromEvents(runId).catch(() => false),
2155
+ ),
2156
+ );
2157
+ const repairedTerminalRow = reconcileResults.some(Boolean);
2158
+ if (repairedTerminalRow) {
2159
+ const refreshed = await client.execute({
2160
+ sql: `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, error_code, abort_reason, dispatch_mode, terminal_reason, diag_stage
2161
+ FROM agent_runs
2162
+ WHERE thread_id = ?
2163
+ ORDER BY started_at DESC
2164
+ LIMIT ?`,
2165
+ args: [threadId, limit],
2166
+ });
2167
+ rows = refreshed.rows;
2168
+ }
2169
+ return rows.map((r) => {
2170
+ const row = r as {
2171
+ id: string;
2172
+ thread_id: string;
2173
+ turn_id?: string | null;
2174
+ status: string;
2175
+ started_at: number | string;
2176
+ heartbeat_at?: number | string | null;
2177
+ completed_at?: number | string | null;
2178
+ last_progress_at?: number | string | null;
2179
+ error_code?: string | null;
2180
+ abort_reason?: string | null;
2181
+ dispatch_mode?: string | null;
2182
+ terminal_reason?: string | null;
2183
+ diag_stage?: string | null;
2184
+ };
2185
+ return {
2186
+ id: row.id,
2187
+ threadId: row.thread_id,
2188
+ turnId: row.turn_id ?? null,
2189
+ status: row.status,
2190
+ startedAt: Number(row.started_at),
2191
+ heartbeatAt: row.heartbeat_at == null ? null : Number(row.heartbeat_at),
2192
+ completedAt: row.completed_at == null ? null : Number(row.completed_at),
2193
+ lastProgressAt:
2194
+ row.last_progress_at == null ? null : Number(row.last_progress_at),
2195
+ errorCode: row.error_code ?? null,
2196
+ abortReason: row.abort_reason ?? null,
2197
+ dispatchMode: row.dispatch_mode ?? null,
2198
+ terminalReason: row.terminal_reason ?? null,
2199
+ diagStage: row.diag_stage ?? null,
2200
+ };
2201
+ });
2202
+ }
2203
+
2204
+ /**
2205
+ * Read the current logical turn's recorded events for a thread, parsed into
2206
+ * `AgentChatEvent`s in seq order, for per-turn tool-call journal classification
2207
+ * (see `tool-call-journal.ts`). Read-only and additive — reuses the existing
2208
+ * `agent_runs` / `agent_run_events` ledger with no schema change.
2209
+ *
2210
+ * A logical turn may span several continuation runs (each chunk is its own run
2211
+ * sharing one `turn_id`), so we union the events of every run that belongs to
2212
+ * the latest turn for this thread. Events are ordered by (started_at, seq) so
2213
+ * earlier chunks come before later ones and the positional `tool_start` →
2214
+ * `tool_done` matching in the classifier stays correct across chunk boundaries.
2215
+ *
2216
+ * Returns an empty array when the thread has no run yet or no parseable events.
2217
+ * Best-effort on parse: malformed ledger rows are skipped rather than thrown.
2218
+ */
2219
+ export async function getCurrentTurnEventsForThread(
2220
+ threadId: string,
2221
+ ): Promise<AgentChatEvent[]> {
2222
+ await ensureRunTables();
2223
+ const client = getDbExec();
2224
+ // Find the latest run for this thread (terminal or running) to learn the
2225
+ // logical turn id. The journal is consulted on the resume path, where the
2226
+ // just-interrupted run is typically already terminal.
2227
+ const latest = await client.execute({
2228
+ sql: `SELECT id, turn_id FROM agent_runs WHERE thread_id = ? ORDER BY started_at DESC LIMIT 1`,
2229
+ args: [threadId],
2230
+ });
2231
+ if (latest.rows.length === 0) return [];
2232
+ const latestRow = latest.rows[0] as { id: string; turn_id: string | null };
2233
+ const turnId = latestRow.turn_id ?? latestRow.id;
2234
+ // Gather every run that belongs to this logical turn, oldest chunk first, and
2235
+ // read their events in seq order. COALESCE(turn_id, id) folds older rows that
2236
+ // predate the turn_id backfill into a turn keyed by their own run id.
2237
+ const { rows } = await client.execute({
2238
+ sql: `SELECT e.event_data AS event_data
2239
+ FROM agent_run_events e
2240
+ JOIN agent_runs r ON r.id = e.run_id
2241
+ WHERE r.thread_id = ?
2242
+ AND COALESCE(r.turn_id, r.id) = ?
2243
+ ORDER BY r.started_at ASC, e.seq ASC`,
2244
+ args: [threadId, turnId],
2245
+ });
2246
+ const events: AgentChatEvent[] = [];
2247
+ for (const r of rows) {
2248
+ const raw = (r as { event_data?: string }).event_data;
2249
+ if (!raw) continue;
2250
+ try {
2251
+ events.push(JSON.parse(raw) as AgentChatEvent);
2252
+ } catch {
2253
+ // Skip malformed ledger rows — the journal is best-effort.
2254
+ }
2255
+ }
2256
+ return events;
2257
+ }
2258
+
2259
+ /**
2260
+ * Expire any "running" rows whose heartbeat is stale — producer died.
2261
+ * Safe to call at server startup on multi-isolate deployments: only rows
2262
+ * without a fresh heartbeat get reaped, so runs owned by OTHER live
2263
+ * isolates (which keep heartbeating) are left alone.
2264
+ */
2265
+ export async function reapAllStaleRuns(): Promise<number> {
2266
+ await ensureRunTables();
2267
+ const client = getDbExec();
2268
+ const now = Date.now();
2269
+ // Background-dispatched runs use the wider window; everything else 15s. The
2270
+ // in-flight grace clause is applied identically to this SELECT and the
2271
+ // UPDATE below (both derive `stale.rows`/the terminal-event-append loop
2272
+ // from the SAME predicate) so a row the grace clause protects from the
2273
+ // UPDATE is never mistakenly given a terminal event anyway — see
2274
+ // `inFlightGraceSql` and `IN_FLIGHT_RUN_STALE_GRACE_MS`. This runs at
2275
+ // server startup across possibly-multiple isolates, so a sibling isolate's
2276
+ // still-alive, in-flight run must not be reaped just because THIS isolate
2277
+ // just booted and has no heartbeat history for it.
2278
+ const stale = await client.execute({
2279
+ sql: `SELECT id FROM agent_runs
2280
+ WHERE status = 'running'
2281
+ AND ${livenessBasisSql()} < ${backgroundAwareStaleCutoffSql()}
2282
+ AND ${inFlightGraceSql()}`,
2283
+ args: [now, now],
2284
+ });
2285
+ for (const row of stale.rows) {
2286
+ const id = (row as { id?: unknown }).id;
2287
+ if (typeof id === "string") {
2288
+ await reconcileTerminalRunFromEvents(id);
2289
+ }
2290
+ }
2291
+ // FIX 3 (durable-background incident): reap each stale row individually
2292
+ // via the shared `reapSingleStaleRun` (rather than one bulk UPDATE) so a
2293
+ // background chat-turn run can be checked for server-owned recovery
2294
+ // (`attemptStaleRunRecovery`) before it goes terminal — a single bulk
2295
+ // statement can't express "insert a successor for row A but not row B".
2296
+ // `reapSingleStaleRun` re-applies the identical staleness clause per row
2297
+ // (same as the bulk UPDATE previously did), so a row whose heartbeat
2298
+ // landed between the SELECT above and this loop is still correctly
2299
+ // excluded. This function only runs once, at process startup (see
2300
+ // agent-chat-plugin.ts) — the extra per-row round trips are
2301
+ // inconsequential.
2302
+ let reapedCount = 0;
2303
+ for (const row of stale.rows) {
2304
+ const id = (row as { id?: unknown }).id;
2305
+ if (typeof id !== "string") continue;
2306
+ const reaped = await reapSingleStaleRun(id).catch(() => false);
2307
+ if (reaped) reapedCount += 1;
2308
+ }
2309
+ for (const row of stale.rows) {
2310
+ const id = (row as { id?: unknown }).id;
2311
+ if (typeof id === "string") {
2312
+ await safeAppendTerminalRunEvent(
2313
+ id,
2314
+ STALE_RUN_ERROR_EVENT,
2315
+ "reap-all-stale",
2316
+ );
2317
+ }
2318
+ }
2319
+ return reapedCount;
2320
+ }
2321
+
2322
+ /** Delete old runs and expire stale "running" rows that haven't had activity
2323
+ * (e.g. worker crashed before updating status). Completed runs are pruned at
2324
+ * `olderThanMs`; errored/aborted runs are kept until `erroredOlderThanMs` (a
2325
+ * longer window, falling back to `olderThanMs`) so their event log survives
2326
+ * for cut-off pattern analysis via listErroredRuns. */
2327
+ export async function cleanupOldRuns(
2328
+ olderThanMs: number,
2329
+ erroredOlderThanMs?: number,
2330
+ ): Promise<void> {
2331
+ await ensureRunTables();
2332
+ const client = getDbExec();
2333
+ const cutoff = Date.now() - olderThanMs;
2334
+ const erroredCutoff =
2335
+ Date.now() - Math.max(erroredOlderThanMs ?? 0, olderThanMs);
2336
+ // Expire stale running rows on the absolute-age threshold — safety net
2337
+ // for runs that never received a heartbeat (very old deployments). The
2338
+ // SELECT covers BOTH UPDATE conditions so the terminal-event-append loop
2339
+ // below catches every row we're about to flip — a 24h-old row with a
2340
+ // somehow-fresh heartbeat would slip past a heartbeat-only SELECT.
2341
+ //
2342
+ // The in-flight grace clause is applied ONLY to the heartbeat-based branch
2343
+ // (mirroring the second UPDATE below), never the absolute-age branch:
2344
+ // nothing can legitimately hold in-flight work anywhere near `olderThanMs`
2345
+ // (default on the order of a day) — `IN_FLIGHT_RUN_STALE_GRACE_MS` bounds
2346
+ // any real grace at ~14.5 minutes — so a row that old is dead regardless of
2347
+ // its in-flight marker. This runs opportunistically after EVERY run
2348
+ // completes in ANY isolate, so a different thread's still-in-flight A2A
2349
+ // call must not be reaped as a side effect of an unrelated run finishing.
2350
+ const now = Date.now();
2351
+ const stale = await client.execute({
2352
+ sql: `SELECT id FROM agent_runs
2353
+ WHERE status = 'running'
2354
+ AND (
2355
+ (${livenessBasisSql()} < ${backgroundAwareStaleCutoffSql()} AND ${inFlightGraceSql()})
2356
+ OR started_at < ?
2357
+ )`,
2358
+ args: [now, now, cutoff],
2359
+ });
2360
+ for (const row of stale.rows) {
2361
+ const id = (row as { id?: unknown }).id;
2362
+ if (typeof id === "string") {
2363
+ await reconcileTerminalRunFromEvents(id);
2364
+ }
2365
+ }
2366
+ const completedAt = Date.now();
2367
+ await client.execute({
2368
+ sql: `UPDATE agent_runs
2369
+ SET status = 'errored',
2370
+ completed_at = ?,
2371
+ error_code = ?,
2372
+ error_detail = ?,
2373
+ terminal_reason = ?
2374
+ WHERE status = 'running'
2375
+ AND ${terminalRunEventExclusionSql()}
2376
+ AND started_at < ?`,
2377
+ args: [
2378
+ completedAt,
2379
+ STALE_RUN_ERROR_EVENT.errorCode,
2380
+ STALE_RUN_ERROR_EVENT.details,
2381
+ STALE_RUN_ERROR_EVENT.errorCode,
2382
+ cutoff,
2383
+ ],
2384
+ });
2385
+ // Also expire runs whose heartbeat is stale — producer has died. Uses the
2386
+ // background-aware window so a slow background cold-start isn't reaped
2387
+ // early, and the in-flight grace so a demonstrably-alive run holding a tool
2388
+ // call / A2A delegation survives a heartbeat write failure. Must match the
2389
+ // SELECT's heartbeat branch above exactly — the terminal-event-append loop
2390
+ // below fires for every row the SELECT returned, so a mismatch would
2391
+ // silently inject a terminal error event onto a row this UPDATE left
2392
+ // status='running'.
2393
+ await client.execute({
2394
+ sql: `UPDATE agent_runs
2395
+ SET status = 'errored',
2396
+ completed_at = ?,
2397
+ error_code = ?,
2398
+ error_detail = ?,
2399
+ terminal_reason = ?
2400
+ WHERE status = 'running'
2401
+ AND ${terminalRunEventExclusionSql()}
2402
+ AND ${livenessBasisSql()} < ${backgroundAwareStaleCutoffSql()}
2403
+ AND ${inFlightGraceSql()}`,
2404
+ args: [
2405
+ completedAt,
2406
+ STALE_RUN_ERROR_EVENT.errorCode,
2407
+ STALE_RUN_ERROR_EVENT.details,
2408
+ STALE_RUN_ERROR_EVENT.errorCode,
2409
+ completedAt,
2410
+ completedAt,
2411
+ ],
2412
+ });
2413
+ for (const row of stale.rows) {
2414
+ const id = (row as { id?: unknown }).id;
2415
+ if (typeof id === "string") {
2416
+ await safeAppendTerminalRunEvent(
2417
+ id,
2418
+ STALE_RUN_ERROR_EVENT,
2419
+ "cleanup-old-runs",
2420
+ );
2421
+ }
2422
+ }
2423
+ // Delete events for old terminal runs. Completed runs prune at `cutoff`;
2424
+ // errored/aborted runs are retained until the (longer) `erroredCutoff`.
2425
+ await client.execute({
2426
+ sql: `DELETE FROM agent_run_events WHERE run_id IN (
2427
+ SELECT id FROM agent_runs
2428
+ WHERE (status = 'completed' AND completed_at < ?)
2429
+ OR (status IN ('errored', 'aborted') AND completed_at < ?)
2430
+ )`,
2431
+ args: [cutoff, erroredCutoff],
2432
+ });
2433
+ await client.execute({
2434
+ sql: `DELETE FROM agent_runs
2435
+ WHERE (status = 'completed' AND completed_at < ?)
2436
+ OR (status IN ('errored', 'aborted') AND completed_at < ?)`,
2437
+ args: [cutoff, erroredCutoff],
2438
+ });
2439
+ }
2440
+
2441
+ /**
2442
+ * List recent errored/aborted runs for cut-off pattern analysis. Read-only,
2443
+ * bounded, and ordered newest-first. Surfaced via the list-errored-runs action
2444
+ * so the team can see why chats are failing (terminal error code, duration,
2445
+ * turn linkage) instead of discovering it ad hoc.
2446
+ */
2447
+ export async function listErroredRuns(options?: {
2448
+ limit?: number;
2449
+ sinceMs?: number;
2450
+ }): Promise<
2451
+ Array<{
2452
+ id: string;
2453
+ threadId: string;
2454
+ turnId: string | null;
2455
+ status: string;
2456
+ errorCode: string | null;
2457
+ errorDetail: string | null;
2458
+ terminalReason: string | null;
2459
+ startedAt: number;
2460
+ completedAt: number | null;
2461
+ durationMs: number | null;
2462
+ }>
2463
+ > {
2464
+ await ensureRunTables();
2465
+ const client = getDbExec();
2466
+ const limit = Math.min(Math.max(Math.floor(options?.limit ?? 100), 1), 1000);
2467
+ const since =
2468
+ options?.sinceMs && options.sinceMs > 0 ? Date.now() - options.sinceMs : 0;
2469
+ const { rows } = await client.execute({
2470
+ sql: `SELECT id, thread_id, turn_id, status, error_code, error_detail, terminal_reason, started_at, completed_at
2471
+ FROM agent_runs
2472
+ WHERE status IN ('errored', 'aborted')
2473
+ AND COALESCE(completed_at, started_at) >= ?
2474
+ ORDER BY COALESCE(completed_at, started_at) DESC
2475
+ LIMIT ${limit}`,
2476
+ args: [since],
2477
+ });
2478
+ return rows.map((r) => {
2479
+ const row = r as {
2480
+ id: string;
2481
+ thread_id: string;
2482
+ turn_id: string | null;
2483
+ status: string;
2484
+ error_code: string | null;
2485
+ error_detail: string | null;
2486
+ terminal_reason: string | null;
2487
+ started_at: number | string;
2488
+ completed_at: number | string | null;
2489
+ };
2490
+ const startedAt = Number(row.started_at);
2491
+ const completedAt =
2492
+ row.completed_at == null ? null : Number(row.completed_at);
2493
+ return {
2494
+ id: row.id,
2495
+ threadId: row.thread_id,
2496
+ turnId: row.turn_id ?? null,
2497
+ status: row.status,
2498
+ errorCode: row.error_code ?? null,
2499
+ errorDetail: row.error_detail ?? null,
2500
+ terminalReason: row.terminal_reason ?? null,
2501
+ startedAt,
2502
+ completedAt,
2503
+ durationMs: completedAt == null ? null : completedAt - startedAt,
2504
+ };
2505
+ });
2506
+ }
2507
+
2508
+ /**
2509
+ * Idempotently append a terminal event to a run's event stream. No-op if the
2510
+ * stream already ends in a terminal event. Used by reapers AND by SSE
2511
+ * reconnect paths that discover an `errored` run row with no terminal event
2512
+ * (e.g. an earlier reaper's silent `.catch(() => {})` swallowed the append).
2513
+ *
2514
+ * Persisting from the reconnect path is what keeps the system self-healing:
2515
+ * subsequent reconnects replay the proper terminal event from SQL instead of
2516
+ * synthesizing a fresh one each time.
2517
+ */
2518
+ export async function ensureTerminalRunEvent(
2519
+ runId: string,
2520
+ event: Record<string, unknown>,
2521
+ ): Promise<void> {
2522
+ return appendTerminalRunEvent(runId, event);
2523
+ }
2524
+
2525
+ /**
2526
+ * Append a terminal run event, retrying once on failure and reporting to
2527
+ * Sentry if both attempts fail. Background reaper paths can't surface errors
2528
+ * to a user, but they MUST eventually persist a terminal event — losing it
2529
+ * leaves reconnecting clients staring at a bare `status='errored'` row with
2530
+ * no payload to render. The previous `.catch(() => {})` callsites silently
2531
+ * dropped transient SQL blips and produced exactly that bug. Never throws.
2532
+ */
2533
+ async function safeAppendTerminalRunEvent(
2534
+ runId: string,
2535
+ event: Record<string, unknown>,
2536
+ source: string,
2537
+ ): Promise<void> {
2538
+ let firstError: unknown;
2539
+ try {
2540
+ await appendTerminalRunEvent(runId, event);
2541
+ return;
2542
+ } catch (err) {
2543
+ firstError = err;
2544
+ }
2545
+ // Brief backoff — most "transient" SQL failures (connection blip, lock
2546
+ // contention) clear within a couple hundred ms.
2547
+ await new Promise<void>((resolve) => setTimeout(resolve, 100));
2548
+ try {
2549
+ await appendTerminalRunEvent(runId, event);
2550
+ } catch (retryErr) {
2551
+ captureError(retryErr, {
2552
+ tags: {
2553
+ component: "agent-run-store",
2554
+ operation: "append-terminal-event",
2555
+ source,
2556
+ },
2557
+ extra: {
2558
+ runId,
2559
+ eventType: typeof event.type === "string" ? event.type : "(unknown)",
2560
+ firstError:
2561
+ firstError instanceof Error ? firstError.message : String(firstError),
2562
+ },
2563
+ });
2564
+ }
2565
+ }
2566
+
2567
+ async function appendTerminalRunEvent(
2568
+ runId: string,
2569
+ event: Record<string, unknown>,
2570
+ ): Promise<void> {
2571
+ await ensureRunTables();
2572
+ const client = getDbExec();
2573
+ const { rows } = await client.execute({
2574
+ sql: `SELECT seq, event_data FROM agent_run_events WHERE run_id = ? ORDER BY seq DESC LIMIT 1`,
2575
+ args: [runId],
2576
+ });
2577
+ const last = rows[0] as
2578
+ | { seq?: number | string; event_data?: string }
2579
+ | undefined;
2580
+ if (last?.event_data) {
2581
+ try {
2582
+ const parsed = JSON.parse(last.event_data);
2583
+ if (
2584
+ parsed?.type === "done" ||
2585
+ parsed?.type === "error" ||
2586
+ parsed?.type === "missing_api_key" ||
2587
+ parsed?.type === "loop_limit" ||
2588
+ parsed?.type === "auto_continue"
2589
+ ) {
2590
+ return;
2591
+ }
2592
+ } catch {
2593
+ // Ignore malformed rows and append the terminal event.
2594
+ }
2595
+ }
2596
+ const nextSeq = last ? Number(last.seq ?? -1) + 1 : 0;
2597
+ await client.execute({
2598
+ sql: `INSERT INTO agent_run_events (run_id, seq, event_at, event_data) VALUES (?, ?, ?, ?) ON CONFLICT (run_id, seq) DO NOTHING`,
2599
+ args: [runId, nextSeq, Date.now(), JSON.stringify(event)],
2600
+ });
2601
+ }