@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,3692 @@
1
+ import { unwrapAttachmentEnvelope } from "@agent-native/toolkit/composer/pasted-text";
2
+ import type { ChatModelAdapter, ChatModelRunResult } from "@assistant-ui/react";
3
+
4
+ import { actionPreparationContinuationNote } from "../agent/action-continuation-guidance.js";
5
+ import {
6
+ LLM_MISSING_CREDENTIALS_ERROR_CODE,
7
+ LLM_MISSING_CREDENTIALS_MESSAGE,
8
+ } from "../agent/engine/credential-errors.js";
9
+ import type {
10
+ AgentChatStructuredContentPart,
11
+ AgentChatStructuredMessage,
12
+ } from "../agent/types.js";
13
+ import type { ReasoningEffort } from "../shared/reasoning-effort.js";
14
+ import {
15
+ clearPendingTurnIfMatches,
16
+ setActiveRun,
17
+ updateActiveRunSeq,
18
+ clearActiveRun,
19
+ setPendingTurn,
20
+ } from "./active-run-state.js";
21
+ import { captureError } from "./analytics.js";
22
+ import { agentNativePath } from "./api-path.js";
23
+ import { formatChatErrorText, normalizeChatError } from "./error-format.js";
24
+ import {
25
+ AgentAutoContinueSignal,
26
+ type AgentActivityTrailEntry,
27
+ type AgentAutoContinueErrorInfo,
28
+ type ContentPart,
29
+ type PreparingActionState,
30
+ readSSEStream,
31
+ settleInterruptedToolCalls,
32
+ } from "./sse-event-processor.js";
33
+ import type { ChatThreadScope } from "./use-chat-threads.js";
34
+
35
+ export type AgentChatSurfaceKind =
36
+ /**
37
+ * Chat rendered by the app itself, including the normal AgentSidebar. This
38
+ * surface must not receive code-editing dev tools because source edits can
39
+ * reload the same React tree that is hosting the chat.
40
+ */
41
+ | "app"
42
+ /** Chat rendered by the outer local dev frame, outside the app iframe. */
43
+ | "dev-frame"
44
+ /** Chat hosted by Desktop for a local app with explicit code access. */
45
+ | "desktop";
46
+
47
+ type AdapterHistoryMessage = {
48
+ role: "user" | "assistant";
49
+ content: string;
50
+ };
51
+
52
+ type AssistantUiAttachment = {
53
+ name: string;
54
+ contentType?: string;
55
+ content: readonly Record<string, unknown>[];
56
+ };
57
+
58
+ type AgentChatAdapterAttachment = {
59
+ type: string;
60
+ name: string;
61
+ contentType?: string;
62
+ data?: string;
63
+ text?: string;
64
+ };
65
+
66
+ const TEXT_ATTACHMENT_CONTENT_TYPES = new Set([
67
+ "application/json",
68
+ "application/x-ndjson",
69
+ "image/svg+xml",
70
+ "text/csv",
71
+ "text/css",
72
+ "text/html",
73
+ "text/json",
74
+ "text/markdown",
75
+ "text/plain",
76
+ "text/xml",
77
+ ]);
78
+
79
+ const AUTO_CONTINUE_PROMPT =
80
+ "Continue from where you left off and finish the user's original request. Do not repeat completed work, do not mention internal reconnects, time limits, or step limits, and continue as if this is the same uninterrupted run.";
81
+ const AUTO_CONTINUE_COMPLETION_GUARD =
82
+ "Before doing more work, inspect the prior partial assistant output in history. If it already gives a coherent answer, summary, artifact, coverage note, or next-step recommendation, finish with at most one short closing sentence and do not call tools, scan more data, or expand the search. Continue only genuinely unfinished work.";
83
+ const MAX_RECONNECT_ATTEMPTS = 5;
84
+ const MAX_STARTUP_RECOVERY_ATTEMPTS = 8;
85
+ const MAX_QUEUED_CONFLICT_RETRIES = 120;
86
+ const MAX_STALE_RUN_CONTINUATIONS = 3;
87
+ const MAX_STALLED_TRANSIENT_CONTINUATIONS = 8;
88
+ const MAX_TOTAL_TRANSIENT_CONTINUATIONS = 32;
89
+ // How many consecutive continuations that produce NO progress (no streamed
90
+ // text, no completed/in-flight tool) we tolerate before giving up. A complex
91
+ // first turn can spend the whole soft-timeout window (~40s) "thinking" with no
92
+ // visible output; giving up after a single such window made the agent feel like
93
+ // it "craps out / stops midway" on heavier prompts (Analytics). Retrying a few
94
+ // times lets a transient slow start recover, while the cap still terminates a
95
+ // genuinely stuck turn with a clear message instead of looping forever.
96
+ const MAX_EMPTY_TRANSIENT_CONTINUATIONS = 3;
97
+ // How many consecutive continuations that re-stream the SAME narration without
98
+ // advancing (no in-flight tool, no completed tool) we tolerate before giving
99
+ // up. A model that degenerates into repeating one phrase ("I have the full
100
+ // HTML. Creating the extension now!") emits "new" text every continuation,
101
+ // which keeps resetting the stalled/empty budgets — so without this guard the
102
+ // stuck turn burns the entire MAX_TOTAL_TRANSIENT_CONTINUATIONS budget (each
103
+ // round re-sending any large pasted payload) before bailing. Catching the
104
+ // repeat ends it in a few rounds with a clear, actionable message instead.
105
+ const MAX_REPEATED_TRANSIENT_CONTINUATIONS = 3;
106
+ // How many consecutive continuations that only reach the SAME "preparing
107
+ // action" activity card we tolerate before giving up. This catches runs that
108
+ // keep timing out while assembling a large tool payload: they are not empty,
109
+ // and the narration may vary enough to bypass the text-repeat guard, but the
110
+ // real tool never starts.
111
+ const MAX_REPEATED_ACTION_PREPARATION_CONTINUATIONS = 3;
112
+ const RETRY_BASE_DELAY_MS = 500;
113
+ const RETRY_MAX_DELAY_MS = 8_000;
114
+ const MAX_HISTORY_ATTACHMENT_CHARS = 60_000;
115
+ // The attachment submitted with the CURRENT turn gets a much larger cap than
116
+ // prior-history embedding. The server threads this turn's attachments into each
117
+ // action's ActionRunContext, and `create-extension`/`update-extension` host a
118
+ // pasted file verbatim from it via `contentFromAttachment` — a feature whose
119
+ // whole point is large pastes. Truncating the outbound payload to the 60K
120
+ // history cap would silently cut a >60K HTML/Alpine file before the server ever
121
+ // reads it, hosting a broken extension. Mirror the large-input tool-arg cap
122
+ // (MAX_HISTORY_LARGE_TOOL_ARGS_CHARS) so realistic pasted files survive intact;
123
+ // the trailing truncation notice still makes a pathological multi-MB paste
124
+ // visibly (not silently) capped.
125
+ const MAX_OUTBOUND_ATTACHMENT_CHARS = 200_000;
126
+ const MAX_HISTORY_MESSAGES = 24;
127
+ const MAX_HISTORY_TOTAL_CHARS = 64_000;
128
+ const MAX_HISTORY_MESSAGE_CHARS = 12_000;
129
+ const MAX_HISTORY_TOOL_ARGS_CHARS = 8_000;
130
+ const MAX_HISTORY_TOOL_RESULT_CHARS = 12_000;
131
+ // Tools whose entire input IS the artifact being built (extension HTML, etc.).
132
+ // Lossy-truncating these to a `{ __agentNativeTruncated }` placeholder strands
133
+ // the resumed agent — it can no longer refine the artifact because it sees a
134
+ // fake input. Keep the real input for these on continuation, only collapsing
135
+ // inputs that are far larger than any realistic extension payload.
136
+ const LARGE_INPUT_TOOL_NAMES = new Set([
137
+ "create-extension",
138
+ "update-extension",
139
+ ]);
140
+ const MAX_HISTORY_LARGE_TOOL_ARGS_CHARS = 200_000;
141
+ const STARTUP_RESPONSE_TIMEOUT_MS = 45_000;
142
+
143
+ // ── Background follow mode ──────────────────────────────────────────────────
144
+ // For server-continued runs (dispatchMode starts with "background" or equals
145
+ // "foreground-self-chain") the SERVER normally chains continuation chunks
146
+ // itself (fresh runId, same turnId), pre-inserts the successor run row BEFORE
147
+ // the old chunk completes (so /runs/active shows an active run continuously
148
+ // across chunk boundaries), and a server sweep reaps lost handoffs into loud
149
+ // terminal errors. The client demotes itself to a READER of server state for
150
+ // healthy handoffs. Foreground self-chain keeps one escape hatch: if the server
151
+ // reports that the self-dispatch itself failed before a successor claimed the
152
+ // run, the existing client auto-continue POST remains the fallback.
153
+ const BACKGROUND_FOLLOW_POLL_INTERVAL_MS = 1_000;
154
+ // How long the follow loop tolerates seeing NO active run for this turn before
155
+ // treating the turn as ended. The server pre-inserts the successor row before
156
+ // the old chunk completes, so a healthy chain never shows an idle gap; allow a
157
+ // wider window here because the server's unclaimed-handoff recovery can span
158
+ // the 25s grace plus sweep/DB latency. This stays below the background
159
+ // reconnect stuck threshold, but gives the server-owned recovery brain time to
160
+ // surface the successor or terminal errored run before the client reports idle.
161
+ //
162
+ // THREE-SITE INVARIANT (keep in lockstep with run-store.ts and
163
+ // agent-chat-plugin.ts — see `UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS`'s doc
164
+ // comment in run-store.ts for the full derivation): a `chainServerDrivenContinuation`
165
+ // deferral (server/production-agent.ts) leaves a successor row `running` with
166
+ // no live worker until a sweep redispatches it. The budget is a derived
167
+ // chain, each bound comfortably inside the next:
168
+ // UNCLAIMED_BACKGROUND_RUN_GRACE_MS (25s)
169
+ // + UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS (20s)
170
+ // = ~45-65s worst-case time-to-first-redispatch-attempt
171
+ // < BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS (150s, this constant)
172
+ // < UNCLAIMED_BACKGROUND_RUN_REDISPATCH_BOUND_MS (300s, run-store.ts)
173
+ // On top of that margin, the follow loop below never counts a tick against
174
+ // this timeout at all while `/runs/active` reports `awaitingRedispatch:
175
+ // true` — a server-authoritative "known deferred, recovery in progress"
176
+ // signal, not a guess — so this timeout is a backstop for a genuinely lost
177
+ // run, not the primary mechanism racing the sweep. Do NOT raise this value to
178
+ // paper over a slow sweep; fix the sweep timing (run-store.ts /
179
+ // agent-chat-plugin.ts) instead, and keep this comment's inequality chain
180
+ // accurate if any of the four numbers change.
181
+ export const BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS = 150_000;
182
+
183
+ // A re-observed terminal run whose outcome would be an ERROR (never a
184
+ // genuine "done" success) gets a short extra grace window before the follow
185
+ // loop surfaces it: the server's dead-run recovery can reap a lost
186
+ // background run and insert a claimable successor a beat after the client
187
+ // first sees the stale terminal row. Deliberately much shorter than
188
+ // BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS — this exists to absorb that narrow
189
+ // insert-latency race, not to mask a genuinely lost run behind a long hang
190
+ // before the user sees an error.
191
+ const BACKGROUND_TERMINAL_ERROR_GRACE_POLLS = 5;
192
+
193
+ /**
194
+ * User-facing copy for the server's background terminal reasons
195
+ * (`agent_runs.terminal_reason`, surfaced by /runs/active). These runs died in
196
+ * server-side handoff machinery where no richer error event may exist, so the
197
+ * client owns the message. Keys are matched after stripping an optional
198
+ * `error:` prefix (`terminalReasonForEvent` records `error:<code>`). Keep this
199
+ * map scoped to terminal failures, including reasons that must override a
200
+ * mistakenly completed row.
201
+ */
202
+ const BACKGROUND_TERMINAL_REASON_MESSAGES: Record<string, string> = {
203
+ background_worker_never_started:
204
+ "The agent run was handed off to a background worker that never started. It was recovered so you can try again.",
205
+ background_continuation_dispatch_failed:
206
+ "The agent's background worker could not hand off the next step of this run. Retry to continue from the preserved context.",
207
+ dispatch_payload_missing:
208
+ "The agent's background run lost its saved request data and could not continue. Retry to start a fresh run.",
209
+ missing_api_key: LLM_MISSING_CREDENTIALS_MESSAGE,
210
+ missing_credentials: LLM_MISSING_CREDENTIALS_MESSAGE,
211
+ turn_continuation_budget_exhausted:
212
+ "This request needed more automatic continuations than allowed and was stopped. Try breaking it into smaller steps.",
213
+ };
214
+
215
+ /**
216
+ * `agent_runs.terminal_reason` values that mark a CHUNK boundary, not the end
217
+ * of the turn — `markBackgroundContinuationChunkTerminal` (production-agent.ts)
218
+ * writes status "completed" with one of these bare reasons whenever a
219
+ * background chunk ends at a continuation boundary (mirrors
220
+ * `AgentLoopContinuationReason` there). A run in this state is expected to be
221
+ * replaced by a server-chained successor row; it must never be read as "the
222
+ * turn is done" just because its own row says `status: "completed"`.
223
+ */
224
+ const BACKGROUND_CONTINUATION_TERMINAL_REASONS = new Set<string>([
225
+ "run_timeout",
226
+ "loop_limit",
227
+ "max_tokens",
228
+ "stream_ended",
229
+ "gateway_timeout",
230
+ "network_interrupted",
231
+ "no_progress",
232
+ ]);
233
+
234
+ /**
235
+ * True when the given `/runs/active` snapshot, if surfaced as a background
236
+ * terminal outcome right now, would render as an error rather than a
237
+ * successful completion. Mirrors `emitBackgroundTerminalOutcome`'s own
238
+ * mappedMessage/hasErrorTerminalReason/status branching (kept in sync with
239
+ * it) so callers can decide whether an error-surfacing grace window applies
240
+ * BEFORE actually emitting anything.
241
+ */
242
+ function isBackgroundTerminalErrorOutcome(
243
+ lastKnown: Record<string, unknown> | null,
244
+ ): boolean {
245
+ const status = typeof lastKnown?.status === "string" ? lastKnown.status : "";
246
+ const rawTerminalReason =
247
+ typeof lastKnown?.terminalReason === "string"
248
+ ? lastKnown.terminalReason
249
+ : "";
250
+ const hasErrorTerminalReason = rawTerminalReason.startsWith("error:");
251
+ const terminalReason = rawTerminalReason.replace(/^error:/, "");
252
+ const mappedMessage = terminalReason
253
+ ? BACKGROUND_TERMINAL_REASON_MESSAGES[terminalReason]
254
+ : undefined;
255
+ if (mappedMessage || hasErrorTerminalReason) return true;
256
+ return status !== "completed";
257
+ }
258
+
259
+ function normalizeMentions(text: string): string {
260
+ return text.replace(/@\[([^\]|]+)\|[^\]]+\]/g, "@$1");
261
+ }
262
+
263
+ function truncateForContinuation(value: string, maxChars: number): string {
264
+ if (value.length <= maxChars) return value;
265
+ return `${value.slice(0, maxChars)}\n\n...[truncated ${value.length - maxChars} chars from prior partial output]`;
266
+ }
267
+
268
+ function truncateForHistory(
269
+ value: string,
270
+ maxChars: number,
271
+ label: string,
272
+ ): string {
273
+ if (value.length <= maxChars) return value;
274
+ const omitted = value.length - maxChars;
275
+ return `${value.slice(0, maxChars)}\n\n[${label} truncated after ${maxChars.toLocaleString()} characters; ${omitted.toLocaleString()} characters omitted from prior chat history. Read the current app/resource state with tools if exact content is needed.]`;
276
+ }
277
+
278
+ function contentToContinuationHistory(content: ContentPart[]): string {
279
+ const chunks: string[] = [];
280
+ for (const part of content) {
281
+ if (part.type === "text") {
282
+ if (part.text.trim()) chunks.push(part.text.trim());
283
+ continue;
284
+ }
285
+ if (part.type === "reasoning") continue;
286
+ if (part.activity === true) continue;
287
+ const toolSummary = [
288
+ `Tool: ${part.toolName}`,
289
+ part.argsText ? `Input: ${part.argsText}` : "",
290
+ part.result
291
+ ? `Result:\n${truncateForContinuation(part.result, 8_000)}`
292
+ : "Result: interrupted before this tool returned a result",
293
+ ]
294
+ .filter(Boolean)
295
+ .join("\n");
296
+ chunks.push(toolSummary);
297
+ }
298
+ return truncateForContinuation(chunks.join("\n\n"), 40_000).trim();
299
+ }
300
+
301
+ function messageTextFromContent(
302
+ content: readonly { type: string; text?: string }[],
303
+ ): string {
304
+ return truncateForHistory(
305
+ content
306
+ .filter((p): p is { type: "text"; text: string } => p.type === "text")
307
+ .map((p) => normalizeMentions(p.text))
308
+ .join("\n"),
309
+ MAX_HISTORY_MESSAGE_CHARS,
310
+ "Message",
311
+ );
312
+ }
313
+
314
+ function truncateToolArgsForHistory(
315
+ args: unknown,
316
+ toolName?: string,
317
+ ): Record<string, unknown> {
318
+ if (!args || typeof args !== "object" || Array.isArray(args)) return {};
319
+ // Large-input tools (e.g. create-extension/update-extension) carry the
320
+ // artifact itself as their input. Preserve the real input on continuation so
321
+ // the resumed agent can keep refining it; only collapse inputs that exceed a
322
+ // far larger ceiling than any realistic payload.
323
+ const cap =
324
+ toolName && LARGE_INPUT_TOOL_NAMES.has(toolName)
325
+ ? MAX_HISTORY_LARGE_TOOL_ARGS_CHARS
326
+ : MAX_HISTORY_TOOL_ARGS_CHARS;
327
+ try {
328
+ const json = JSON.stringify(args);
329
+ if (json.length <= cap) {
330
+ return args as Record<string, unknown>;
331
+ }
332
+ return {
333
+ __agentNativeTruncated: true,
334
+ note: "Tool input was too large to resend in chat history. Use the current app/resource state as the source of truth if exact content is needed.",
335
+ preview: truncateForHistory(json, cap, "Tool input"),
336
+ };
337
+ } catch {
338
+ return {
339
+ __agentNativeTruncated: true,
340
+ note: "Tool input could not be serialized for prior chat history.",
341
+ };
342
+ }
343
+ }
344
+
345
+ function messageTextFromContentRaw(
346
+ content: readonly { type: string; text?: string }[],
347
+ ): string {
348
+ return content
349
+ .filter((p): p is { type: "text"; text: string } => p.type === "text")
350
+ .map((p) => normalizeMentions(p.text))
351
+ .join("\n");
352
+ }
353
+
354
+ function escapeAttachmentAttribute(value: string): string {
355
+ return value
356
+ .replace(/&/g, "&amp;")
357
+ .replace(/"/g, "&quot;")
358
+ .replace(/</g, "&lt;")
359
+ .replace(/>/g, "&gt;");
360
+ }
361
+
362
+ function isTextAttachmentContentType(value: string | undefined): boolean {
363
+ if (!value) return false;
364
+ const contentType = value.split(";")[0]?.trim().toLowerCase();
365
+ return (
366
+ !!contentType &&
367
+ (contentType.startsWith("text/") ||
368
+ TEXT_ATTACHMENT_CONTENT_TYPES.has(contentType))
369
+ );
370
+ }
371
+
372
+ function isSvgAttachment(args: {
373
+ name?: string;
374
+ contentType?: string;
375
+ }): boolean {
376
+ const contentType = args.contentType?.split(";")[0]?.trim().toLowerCase();
377
+ return contentType === "image/svg+xml" || /\.svg$/i.test(args.name ?? "");
378
+ }
379
+
380
+ function decodeTextDataUrl(dataUrl: string): string | null {
381
+ const match = dataUrl.match(
382
+ /^data:([^;,]+)(?:;charset=[^;,]+)?(;base64)?,(.*)$/i,
383
+ );
384
+ if (!match || !isTextAttachmentContentType(match[1])) return null;
385
+
386
+ try {
387
+ const payload = match[3] ?? "";
388
+ if (match[2]) {
389
+ if (typeof atob === "function") {
390
+ return decodeURIComponent(
391
+ Array.from(
392
+ atob(payload),
393
+ (char) => `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`,
394
+ ).join(""),
395
+ );
396
+ }
397
+ return null;
398
+ }
399
+ return decodeURIComponent(payload.replace(/\+/g, "%20"));
400
+ } catch {
401
+ return null;
402
+ }
403
+ }
404
+
405
+ function extractAttachmentsFromMessage(message: {
406
+ content?: readonly { type: string; image?: string }[];
407
+ attachments?: readonly AssistantUiAttachment[];
408
+ }): AgentChatAdapterAttachment[] {
409
+ const attachments: AgentChatAdapterAttachment[] = [];
410
+ for (const att of message.attachments ?? []) {
411
+ for (const part of att.content) {
412
+ if (part.type === "image" && typeof part.image === "string") {
413
+ attachments.push({
414
+ type: "image",
415
+ name: att.name,
416
+ contentType: att.contentType,
417
+ data: part.image,
418
+ });
419
+ } else if (part.type === "file" && typeof part.data === "string") {
420
+ const contentType =
421
+ att.contentType ??
422
+ (typeof part.mimeType === "string" ? part.mimeType : undefined);
423
+ const preserveDataUrl =
424
+ part.data.startsWith("data:") &&
425
+ shouldPreserveFileDataUrl({ name: att.name, contentType });
426
+ const decodedText = part.data.startsWith("data:")
427
+ ? decodeTextDataUrl(part.data)
428
+ : null;
429
+ attachments.push({
430
+ type: "file",
431
+ name: att.name,
432
+ contentType,
433
+ ...(preserveDataUrl
434
+ ? {
435
+ data: part.data,
436
+ ...(decodedText !== null
437
+ ? { text: truncateOutboundAttachment(decodedText) }
438
+ : {}),
439
+ }
440
+ : decodedText !== null
441
+ ? { text: truncateOutboundAttachment(decodedText) }
442
+ : part.data.startsWith("data:")
443
+ ? { data: part.data }
444
+ : { text: truncateOutboundAttachment(part.data) }),
445
+ });
446
+ } else if (part.type === "text" && typeof part.text === "string") {
447
+ attachments.push({
448
+ type: "file",
449
+ name: att.name,
450
+ contentType: att.contentType,
451
+ text: truncateOutboundAttachment(unwrapAttachmentEnvelope(part.text)),
452
+ });
453
+ }
454
+ }
455
+ }
456
+ for (const part of message.content ?? []) {
457
+ if (part.type === "image" && typeof part.image === "string") {
458
+ attachments.push({
459
+ type: "image",
460
+ name: "image",
461
+ contentType: /^data:([^;,]+)/.exec(part.image)?.[1],
462
+ data: part.image,
463
+ });
464
+ }
465
+ }
466
+ return attachments;
467
+ }
468
+
469
+ function shouldPreserveFileDataUrl(args: {
470
+ name?: string;
471
+ contentType?: string;
472
+ }): boolean {
473
+ return isSvgAttachment(args);
474
+ }
475
+
476
+ function truncateHistoryAttachment(text: string): string {
477
+ if (text.length <= MAX_HISTORY_ATTACHMENT_CHARS) return text;
478
+ const omitted = text.length - MAX_HISTORY_ATTACHMENT_CHARS;
479
+ return `${text.slice(0, MAX_HISTORY_ATTACHMENT_CHARS)}\n\n[Attachment truncated after ${MAX_HISTORY_ATTACHMENT_CHARS.toLocaleString()} characters; ${omitted.toLocaleString()} characters omitted from prior chat history.]`;
480
+ }
481
+
482
+ function truncateOutboundAttachment(text: string): string {
483
+ if (text.length <= MAX_OUTBOUND_ATTACHMENT_CHARS) return text;
484
+ const omitted = text.length - MAX_OUTBOUND_ATTACHMENT_CHARS;
485
+ return `${text.slice(0, MAX_OUTBOUND_ATTACHMENT_CHARS)}\n\n[Attachment truncated after ${MAX_OUTBOUND_ATTACHMENT_CHARS.toLocaleString()} characters; ${omitted.toLocaleString()} characters omitted from the submitted attachment.]`;
486
+ }
487
+
488
+ function attachmentHistoryText(
489
+ attachment: AgentChatAdapterAttachment,
490
+ ): string | null {
491
+ if (isSvgAttachment(attachment)) {
492
+ const label = attachment.name || "SVG attachment";
493
+ const contentType = attachment.contentType
494
+ ? ` (${attachment.contentType})`
495
+ : "";
496
+ return `[Attached ${attachment.type || "file"}: ${label}${contentType}; SVG reference-only, raw markup omitted from prior chat history.]`;
497
+ }
498
+
499
+ if (typeof attachment.text === "string" && attachment.text.length > 0) {
500
+ const attrs = [
501
+ `name="${escapeAttachmentAttribute(attachment.name || "attachment")}"`,
502
+ attachment.contentType
503
+ ? `contentType="${escapeAttachmentAttribute(attachment.contentType)}"`
504
+ : null,
505
+ attachment.type
506
+ ? `type="${escapeAttachmentAttribute(attachment.type)}"`
507
+ : null,
508
+ ].filter(Boolean);
509
+ return `<attachment ${attrs.join(" ")}>\n${truncateHistoryAttachment(attachment.text)}\n</attachment>`;
510
+ }
511
+
512
+ if (attachment.name) {
513
+ return `[Attached ${attachment.type || "file"}: ${attachment.name}${attachment.contentType ? ` (${attachment.contentType})` : ""}]`;
514
+ }
515
+ return null;
516
+ }
517
+
518
+ function messageTextForHistory(message: {
519
+ content: readonly { type: string; text?: string }[];
520
+ attachments?: readonly AssistantUiAttachment[];
521
+ }): string {
522
+ const text = messageTextFromContentRaw(message.content);
523
+ const attachments = extractAttachmentsFromMessage(message)
524
+ .map(attachmentHistoryText)
525
+ .filter((part): part is string => !!part && part.trim().length > 0);
526
+ return truncateForHistory(
527
+ [text, ...attachments].filter((part) => part.trim()).join("\n\n"),
528
+ MAX_HISTORY_MESSAGE_CHARS,
529
+ "Message",
530
+ );
531
+ }
532
+
533
+ type AdapterMessage = {
534
+ id?: string;
535
+ role: string;
536
+ content: readonly { type: string; text?: string }[];
537
+ attachments?: readonly AssistantUiAttachment[];
538
+ metadata?: unknown;
539
+ };
540
+
541
+ const RECOVERY_USER_MESSAGE_PREFIXES = [
542
+ "Continue from where you left off",
543
+ "Continue from where you stopped",
544
+ "Retry the previous request from a clean approach",
545
+ ];
546
+
547
+ function recoveryActionFromMessage(
548
+ message: unknown,
549
+ ): "continue" | "retry" | null {
550
+ const meta = (message as { metadata?: unknown })?.metadata as
551
+ | { custom?: { agentNativeRecoveryAction?: unknown } }
552
+ | undefined;
553
+ const action = meta?.custom?.agentNativeRecoveryAction;
554
+ return action === "continue" || action === "retry" ? action : null;
555
+ }
556
+
557
+ function isRecoveryUserMessage(message: AdapterMessage): boolean {
558
+ if (recoveryActionFromMessage(message)) return true;
559
+ const text = messageTextFromContentRaw(message.content).trim();
560
+ return RECOVERY_USER_MESSAGE_PREFIXES.some((prefix) =>
561
+ text.startsWith(prefix),
562
+ );
563
+ }
564
+
565
+ function latestUserMessage(
566
+ messages: readonly AdapterMessage[],
567
+ options?: { skipRecovery?: boolean; beforeIndex?: number },
568
+ ): AdapterMessage | undefined {
569
+ const start =
570
+ typeof options?.beforeIndex === "number"
571
+ ? Math.min(options.beforeIndex, messages.length)
572
+ : messages.length;
573
+ for (let i = start - 1; i >= 0; i--) {
574
+ const message = messages[i];
575
+ if (message.role !== "user") continue;
576
+ if (options?.skipRecovery && isRecoveryUserMessage(message)) continue;
577
+ return message;
578
+ }
579
+ return undefined;
580
+ }
581
+
582
+ function isToolCallContentPart(
583
+ part: unknown,
584
+ ): part is Extract<ContentPart, { type: "tool-call" }> {
585
+ return Boolean(
586
+ part && typeof part === "object" && (part as any).type === "tool-call",
587
+ );
588
+ }
589
+
590
+ function isSuccessOnlyToolResult(value: Record<string, unknown>): boolean {
591
+ const keys = Object.keys(value);
592
+ if (keys.length === 0) return true;
593
+ return keys.every((key) => {
594
+ const item = value[key];
595
+ if (key === "ok" || key === "success") return item === true;
596
+ if (key === "status") {
597
+ return item === "ok" || item === "success" || item === "completed";
598
+ }
599
+ return false;
600
+ });
601
+ }
602
+
603
+ function toolResultContent(result: unknown, toolName?: string): string {
604
+ if (typeof result === "string") return result;
605
+ const completed = toolName?.trim()
606
+ ? `${toolName.trim()} completed.`
607
+ : "Tool completed.";
608
+ if (result === true || result == null) return completed;
609
+ if (result && typeof result === "object" && !Array.isArray(result)) {
610
+ const record = result as Record<string, unknown>;
611
+ const message = record.message ?? record.summary;
612
+ if (typeof message === "string" && message.trim()) return message.trim();
613
+ const title = record.title ?? record.name;
614
+ if (typeof title === "string" && title.trim()) {
615
+ return `${title.trim()} is ready.`;
616
+ }
617
+ const id = record.id ?? record.planId ?? record.commentId;
618
+ if (typeof id === "string" && id.trim() && toolName?.trim()) {
619
+ return `${toolName.trim()} completed for ${id.trim()}.`;
620
+ }
621
+ if (isSuccessOnlyToolResult(record)) return completed;
622
+ }
623
+ try {
624
+ return JSON.stringify(result);
625
+ } catch {
626
+ return String(result ?? "");
627
+ }
628
+ }
629
+
630
+ function contentToStructuredMessages(
631
+ content: readonly ContentPart[],
632
+ nextToolCallId: () => string,
633
+ options?: { truncateForHistory?: boolean },
634
+ ): AgentChatStructuredMessage[] {
635
+ const messages: AgentChatStructuredMessage[] = [];
636
+ let assistantParts: AgentChatStructuredContentPart[] = [];
637
+ let pendingToolResults: AgentChatStructuredContentPart[] = [];
638
+ const truncate = options?.truncateForHistory === true;
639
+
640
+ const flushToolTurn = () => {
641
+ if (pendingToolResults.length === 0) return;
642
+ if (assistantParts.length > 0) {
643
+ messages.push({ role: "assistant", content: assistantParts });
644
+ }
645
+ messages.push({ role: "user", content: pendingToolResults });
646
+ assistantParts = [];
647
+ pendingToolResults = [];
648
+ };
649
+
650
+ for (const part of content) {
651
+ if (part.type === "text") {
652
+ if (pendingToolResults.length > 0) flushToolTurn();
653
+ if (part.text.trim()) {
654
+ assistantParts.push({
655
+ type: "text",
656
+ text: truncate
657
+ ? truncateForHistory(
658
+ part.text,
659
+ MAX_HISTORY_MESSAGE_CHARS,
660
+ "Assistant text",
661
+ )
662
+ : part.text,
663
+ });
664
+ }
665
+ continue;
666
+ }
667
+
668
+ // Reasoning/thinking is UI-only — do not send chain-of-thought back into
669
+ // model history on continuation.
670
+ if (part.type === "reasoning") {
671
+ continue;
672
+ }
673
+
674
+ if (isToolCallContentPart(part)) {
675
+ if (part.activity === true) continue;
676
+ const toolCallId = nextToolCallId();
677
+ assistantParts.push({
678
+ type: "tool-call",
679
+ toolCallId,
680
+ toolName: part.toolName,
681
+ args: truncate
682
+ ? truncateToolArgsForHistory(part.args ?? {}, part.toolName)
683
+ : (part.args ?? {}),
684
+ });
685
+ if (part.result !== undefined) {
686
+ pendingToolResults.push({
687
+ type: "tool-result",
688
+ toolCallId,
689
+ toolName: part.toolName,
690
+ toolInput: JSON.stringify(part.args ?? {}),
691
+ content: truncate
692
+ ? truncateForHistory(
693
+ toolResultContent(part.result, part.toolName),
694
+ MAX_HISTORY_TOOL_RESULT_CHARS,
695
+ "Tool result",
696
+ )
697
+ : toolResultContent(part.result, part.toolName),
698
+ });
699
+ } else {
700
+ pendingToolResults.push({
701
+ type: "tool-result",
702
+ toolCallId,
703
+ toolName: part.toolName,
704
+ toolInput: JSON.stringify(part.args ?? {}),
705
+ content: "Interrupted before this tool returned a result.",
706
+ });
707
+ }
708
+ }
709
+ }
710
+
711
+ flushToolTurn();
712
+ if (assistantParts.length > 0) {
713
+ messages.push({ role: "assistant", content: assistantParts });
714
+ }
715
+ return messages;
716
+ }
717
+
718
+ function assistantUiMessagesToStructuredHistory(
719
+ messages: readonly {
720
+ role: string;
721
+ content: readonly any[];
722
+ attachments?: readonly AssistantUiAttachment[];
723
+ }[],
724
+ ): AgentChatStructuredMessage[] {
725
+ let nextId = 0;
726
+ const nextToolCallId = () => `history_tc_${++nextId}`;
727
+ const structured: AgentChatStructuredMessage[] = [];
728
+
729
+ for (const message of messages) {
730
+ if (message.role === "user") {
731
+ const text = messageTextForHistory(message);
732
+ if (text.trim()) {
733
+ structured.push({
734
+ role: "user",
735
+ content: [{ type: "text", text }],
736
+ });
737
+ }
738
+ continue;
739
+ }
740
+
741
+ if (message.role !== "assistant") continue;
742
+ const content: ContentPart[] = [];
743
+ for (const part of message.content) {
744
+ if (part?.type === "text" && typeof part.text === "string") {
745
+ content.push({ type: "text", text: part.text });
746
+ continue;
747
+ }
748
+ if (part?.type === "tool-call") {
749
+ if ((part as { activity?: unknown }).activity === true) continue;
750
+ const toolNameRaw =
751
+ typeof part.toolName === "string"
752
+ ? part.toolName
753
+ : typeof (part as { name?: string }).name === "string"
754
+ ? (part as { name?: string }).name
755
+ : "";
756
+ const toolName = toolNameRaw.trim();
757
+ if (!toolName) continue;
758
+ content.push({
759
+ type: "tool-call",
760
+ toolCallId:
761
+ typeof part.toolCallId === "string" ? part.toolCallId : "",
762
+ toolName,
763
+ argsText:
764
+ typeof part.argsText === "string"
765
+ ? part.argsText
766
+ : JSON.stringify(part.args ?? {}),
767
+ args:
768
+ part.args &&
769
+ typeof part.args === "object" &&
770
+ !Array.isArray(part.args)
771
+ ? part.args
772
+ : {},
773
+ ...(part.result !== undefined
774
+ ? { result: toolResultContent(part.result, toolName) }
775
+ : {}),
776
+ });
777
+ }
778
+ }
779
+ structured.push(
780
+ ...contentToStructuredMessages(content, nextToolCallId, {
781
+ truncateForHistory: true,
782
+ }),
783
+ );
784
+ }
785
+
786
+ return structured;
787
+ }
788
+
789
+ function estimateHistoryMessageCost(message: {
790
+ content: readonly { type: string; text?: string }[];
791
+ attachments?: readonly AssistantUiAttachment[];
792
+ }): number {
793
+ return Math.max(1, messageTextForHistory(message).length);
794
+ }
795
+
796
+ function limitPriorMessagesForRequest<
797
+ T extends {
798
+ role: string;
799
+ content: readonly { type: string; text?: string }[];
800
+ attachments?: readonly AssistantUiAttachment[];
801
+ },
802
+ >(messages: readonly T[]): T[] {
803
+ const recent = messages.slice(-MAX_HISTORY_MESSAGES);
804
+ const kept: T[] = [];
805
+ let totalChars = 0;
806
+
807
+ for (let i = recent.length - 1; i >= 0; i--) {
808
+ const message = recent[i];
809
+ if (message.role !== "user" && message.role !== "assistant") continue;
810
+ const cost = estimateHistoryMessageCost(message);
811
+ if (kept.length > 0 && totalChars + cost > MAX_HISTORY_TOTAL_CHARS) {
812
+ break;
813
+ }
814
+ kept.push(message);
815
+ totalChars += cost;
816
+ }
817
+
818
+ kept.reverse();
819
+ while (kept.length > 0 && kept[0].role !== "user") {
820
+ kept.shift();
821
+ }
822
+ return kept;
823
+ }
824
+
825
+ function combineContinuationHistory(fragments: string[]): string {
826
+ return truncateForContinuation(
827
+ fragments.filter(Boolean).join("\n\n"),
828
+ 40_000,
829
+ ).trim();
830
+ }
831
+
832
+ function hasContinuationProgress(content: ContentPart[]): boolean {
833
+ return content.some((part) => {
834
+ if (part.type === "text") return part.text.trim().length > 0;
835
+ if (part.type === "reasoning") return false;
836
+ return part.result !== undefined;
837
+ });
838
+ }
839
+
840
+ const COMPLETED_TOOL_TIMEOUT_NAME_RE =
841
+ /^(add|apply|archive|capture|compose|create|delete|deploy|duplicate|edit|generate|grant|insert|install|migrate|move|mutate|present|publish|remove|rename|reorder|revoke|save|send|set|sync|trash|update|write)(-|$)/;
842
+ const COMPLETED_TOOL_TIMEOUT_NAME_ALLOWLIST = new Set([
843
+ "connect-assets-mcp",
844
+ "import-design-tokens",
845
+ ]);
846
+
847
+ function isCompletedToolTimeoutCandidate(
848
+ part: Extract<ContentPart, { type: "tool-call" }>,
849
+ ): boolean {
850
+ if (part.completedSideEffect === false) return false;
851
+ if (part.completedSideEffect === true) return true;
852
+ const toolName = part.toolName.toLowerCase();
853
+ return (
854
+ COMPLETED_TOOL_TIMEOUT_NAME_ALLOWLIST.has(toolName) ||
855
+ COMPLETED_TOOL_TIMEOUT_NAME_RE.test(toolName)
856
+ );
857
+ }
858
+
859
+ function lastCompletedTimeoutCandidateTool(
860
+ content: ContentPart[],
861
+ ): Extract<ContentPart, { type: "tool-call" }> | undefined {
862
+ for (let i = content.length - 1; i >= 0; i--) {
863
+ const part = content[i];
864
+ if (
865
+ part.type === "tool-call" &&
866
+ part.activity !== true &&
867
+ part.result !== undefined &&
868
+ part.isError !== true &&
869
+ isCompletedToolTimeoutCandidate(part)
870
+ ) {
871
+ return part;
872
+ }
873
+ }
874
+ return undefined;
875
+ }
876
+
877
+ function humanizeActionName(toolName: string): string {
878
+ return toolName
879
+ .replace(/^agent:/, "")
880
+ .replace(/[-_]+/g, " ")
881
+ .trim();
882
+ }
883
+
884
+ function completedToolTimeoutMessage(toolName: string): string {
885
+ if (toolName === "generate-design" || toolName === "update-design") {
886
+ return "The design was saved, but the assistant timed out before sending its final note. You can open the generated design from the completed tool card above.";
887
+ }
888
+ if (toolName === "present-design-variants") {
889
+ return "The design variants were saved, but the assistant timed out before sending its final note. You can review the completed variants from the tool card above.";
890
+ }
891
+ return `The ${humanizeActionName(toolName)} action completed, but the assistant timed out before sending its final response. The saved result is in the completed tool card above.`;
892
+ }
893
+
894
+ /**
895
+ * Signature of the *unique* sentence-like segments in a continuation's newly
896
+ * streamed text, used to detect a degenerate repetition loop. A stuck model
897
+ * re-emits the same phrase ("I have the full HTML. Creating the extension
898
+ * now!") an arbitrary number of times per run, so the set of unique segments
899
+ * is small and stable across continuations regardless of how many times any
900
+ * single run repeated it. An empty signature (no visible text) is never a
901
+ * repeat — the stalled/empty budgets handle no-output stalls instead.
902
+ */
903
+ function continuationRepeatSignature(content: ContentPart[]): string {
904
+ const text = content
905
+ .map((part) => (part.type === "text" ? part.text : ""))
906
+ .join(" ")
907
+ .toLowerCase();
908
+ const segments = text
909
+ // Split after sentence punctuation even when runs are concatenated without
910
+ // a following space ("...now!I have..."), and on newlines.
911
+ .split(/(?<=[.!?])|\n+/)
912
+ .map((segment) => segment.replace(/[^a-z0-9]+/g, " ").trim())
913
+ .filter((segment) => segment.length > 0);
914
+ if (segments.length === 0) return "";
915
+ return Array.from(new Set(segments)).sort().join("\u0000");
916
+ }
917
+
918
+ /**
919
+ * True when an action was streamed but never returned a result yet — i.e. a
920
+ * `tool_start` with no matching `tool_done`. The server is still executing it,
921
+ * so a run_timeout that fires in this window is NOT a stall: the agent was
922
+ * actively working and `foldAssistantTurn` persisted the in-flight call. We
923
+ * must not count this against the stalled/empty continuation budgets.
924
+ */
925
+ function hasInFlightToolCall(content: ContentPart[]): boolean {
926
+ return content.some(
927
+ (part) =>
928
+ part.type === "tool-call" &&
929
+ part.result === undefined &&
930
+ part.activity !== true,
931
+ );
932
+ }
933
+
934
+ function lastActivityTool(
935
+ trail: readonly AgentActivityTrailEntry[],
936
+ ): string | undefined {
937
+ for (let i = trail.length - 1; i >= 0; i--) {
938
+ const tool = trail[i]?.tool?.trim();
939
+ if (tool) return tool;
940
+ }
941
+ return undefined;
942
+ }
943
+
944
+ function lastPreparingActionTool(
945
+ trail: readonly AgentActivityTrailEntry[],
946
+ ): string | undefined {
947
+ for (let i = trail.length - 1; i >= 0; i--) {
948
+ const entry = trail[i];
949
+ const tool = entry?.tool?.trim();
950
+ if (!tool) continue;
951
+ const label = entry.label.trim().toLowerCase();
952
+ if (label.startsWith("preparing ") && label.includes(" action")) {
953
+ return tool;
954
+ }
955
+ }
956
+ return undefined;
957
+ }
958
+
959
+ function formatActivityTrail(
960
+ trail: readonly AgentActivityTrailEntry[],
961
+ ): string | undefined {
962
+ const items = trail
963
+ .slice(-8)
964
+ .map((entry) => {
965
+ const label = entry.label.replace(/\s+/g, " ").trim();
966
+ const tool = entry.tool?.replace(/\s+/g, " ").trim();
967
+ if (label && tool && label !== tool) return `${label} (${tool})`;
968
+ return label || tool || "";
969
+ })
970
+ .filter(Boolean);
971
+ return items.length > 0 ? items.join(" > ") : undefined;
972
+ }
973
+
974
+ function lastUnresolvedToolActivity(
975
+ content: ContentPart[],
976
+ ): string | undefined {
977
+ for (let i = content.length - 1; i >= 0; i--) {
978
+ const part = content[i];
979
+ if (
980
+ part.type === "tool-call" &&
981
+ part.activity === true &&
982
+ part.result === undefined
983
+ ) {
984
+ return part.toolName;
985
+ }
986
+ }
987
+ return undefined;
988
+ }
989
+
990
+ function snapshotContent(content: ContentPart[]): ContentPart[] {
991
+ return content.map((part) =>
992
+ part.type === "text" || part.type === "reasoning"
993
+ ? { ...part }
994
+ : { ...part, args: { ...part.args } },
995
+ );
996
+ }
997
+
998
+ function stableJson(value: unknown): string {
999
+ try {
1000
+ return JSON.stringify(value);
1001
+ } catch {
1002
+ return String(value ?? "");
1003
+ }
1004
+ }
1005
+
1006
+ // Bounded signature of an in-flight tool call's input, used by the stall guard
1007
+ // to tell a genuinely-stuck retry (same tool, same payload, connection keeps
1008
+ // dropping) apart from a legitimate retry with a CHANGED payload (e.g. the
1009
+ // `create-extension` cutoff nudge tells the model to re-send a smaller body).
1010
+ // A changed payload yields a different signature, so the guard resets that
1011
+ // tool's stall budget instead of aborting the new attempt as a repeat. Bounded
1012
+ // to length + head so we never retain a large pasted payload across rounds.
1013
+ function inFlightToolInputSignature(
1014
+ part: Extract<ContentPart, { type: "tool-call" }>,
1015
+ ): string {
1016
+ const raw = part.argsText ?? stableJson(part.args);
1017
+ return `${raw.length}\u0000${raw.slice(0, 256)}`;
1018
+ }
1019
+
1020
+ function toolContinuationKey(
1021
+ part: Extract<ContentPart, { type: "tool-call" }>,
1022
+ ): string {
1023
+ return [
1024
+ part.toolCallId,
1025
+ part.toolName,
1026
+ part.argsText,
1027
+ stableJson(part.args),
1028
+ part.result === undefined ? "pending" : "done",
1029
+ part.result ?? "",
1030
+ part.isError === true ? "error" : "",
1031
+ part.completedSideEffect === true ? "side-effect" : "",
1032
+ part.activity === true ? "activity" : "tool",
1033
+ part.mcpApp ? "mcp-app" : "",
1034
+ ].join("\u0000");
1035
+ }
1036
+
1037
+ function contentAfterContinuationPrefix(
1038
+ content: ContentPart[],
1039
+ prefix: ContentPart[],
1040
+ ): ContentPart[] {
1041
+ if (prefix.length === 0) return content;
1042
+
1043
+ const delta: ContentPart[] = [];
1044
+ let contentIndex = 0;
1045
+
1046
+ for (const prefixPart of prefix) {
1047
+ const currentPart = content[contentIndex];
1048
+ if (!currentPart) return content.slice(contentIndex);
1049
+
1050
+ if (prefixPart.type === "text" && currentPart.type === "text") {
1051
+ if (currentPart.text === prefixPart.text) {
1052
+ contentIndex += 1;
1053
+ continue;
1054
+ }
1055
+
1056
+ if (currentPart.text.startsWith(prefixPart.text)) {
1057
+ const appendedText = currentPart.text.slice(prefixPart.text.length);
1058
+ if (appendedText) delta.push({ type: "text", text: appendedText });
1059
+ contentIndex += 1;
1060
+ continue;
1061
+ }
1062
+
1063
+ return content.slice(contentIndex);
1064
+ }
1065
+
1066
+ if (prefixPart.type === "reasoning" && currentPart.type === "reasoning") {
1067
+ if (currentPart.text === prefixPart.text) {
1068
+ contentIndex += 1;
1069
+ continue;
1070
+ }
1071
+ if (currentPart.text.startsWith(prefixPart.text)) {
1072
+ const appendedText = currentPart.text.slice(prefixPart.text.length);
1073
+ if (appendedText) {
1074
+ delta.push({ type: "reasoning", text: appendedText });
1075
+ }
1076
+ contentIndex += 1;
1077
+ continue;
1078
+ }
1079
+ return content.slice(contentIndex);
1080
+ }
1081
+
1082
+ if (
1083
+ prefixPart.type === "tool-call" &&
1084
+ currentPart.type === "tool-call" &&
1085
+ toolContinuationKey(currentPart) === toolContinuationKey(prefixPart)
1086
+ ) {
1087
+ contentIndex += 1;
1088
+ continue;
1089
+ }
1090
+
1091
+ return content.slice(contentIndex);
1092
+ }
1093
+
1094
+ return [...delta, ...content.slice(contentIndex)];
1095
+ }
1096
+
1097
+ function autoContinueMessage(signal: AgentAutoContinueSignal): string {
1098
+ const tool = lastActivityTool(signal.activityTrail);
1099
+ const reason =
1100
+ signal.reason === "loop_limit"
1101
+ ? "The previous run reached an internal step budget."
1102
+ : signal.reason === "stale_run"
1103
+ ? "The previous run stopped unexpectedly in the server runtime before it could finish."
1104
+ : signal.reason === "no_progress"
1105
+ ? "The previous run stopped producing progress events while the connection stayed open."
1106
+ : signal.reason === "stream_ended"
1107
+ ? "The previous stream ended before the agent sent a final completion signal."
1108
+ : "The previous run reached an internal execution budget.";
1109
+ // A run_timeout or a mid-stream cutoff while assembling one large action
1110
+ // payload (e.g. inlining a big pasted HTML file into `create-extension`, a
1111
+ // full file set into `generate-design`, or a whole `update-dashboard` config)
1112
+ // is the classic trigger for the repetition/cutoff loop. Nudge the model
1113
+ // toward a compact first version it can actually finish in a single run, then
1114
+ // incremental refinements — never re-streaming the same oversized payload.
1115
+ const cutoffPreparingAction =
1116
+ signal.reason === "run_timeout" ||
1117
+ signal.reason === "stream_ended" ||
1118
+ signal.reason === "no_progress";
1119
+ let actionInputNote = "";
1120
+ if (cutoffPreparingAction && tool) {
1121
+ actionInputNote = actionPreparationContinuationNote(tool);
1122
+ }
1123
+ return `${AUTO_CONTINUE_PROMPT}\n\n${AUTO_CONTINUE_COMPLETION_GUARD}\n\nInternal note: ${reason}${actionInputNote}`;
1124
+ }
1125
+
1126
+ function delay(ms: number, abortSignal: AbortSignal): Promise<void> {
1127
+ if (abortSignal.aborted) return Promise.resolve();
1128
+ return new Promise((resolve) => {
1129
+ const onAbort = () => {
1130
+ clearTimeout(timer);
1131
+ resolve();
1132
+ };
1133
+ const timer = setTimeout(() => {
1134
+ abortSignal.removeEventListener("abort", onAbort);
1135
+ resolve();
1136
+ }, ms);
1137
+ abortSignal.addEventListener("abort", onAbort, { once: true });
1138
+ });
1139
+ }
1140
+
1141
+ class AgentStartupTimeoutError extends Error {
1142
+ readonly timeoutMs: number;
1143
+
1144
+ constructor(timeoutMs: number) {
1145
+ super(
1146
+ `Agent chat did not start streaming within ${Math.round(timeoutMs / 1000)}s.`,
1147
+ );
1148
+ this.name = "AgentStartupTimeoutError";
1149
+ this.timeoutMs = timeoutMs;
1150
+ }
1151
+ }
1152
+
1153
+ async function fetchWithStartupTimeout(
1154
+ input: RequestInfo | URL,
1155
+ init: RequestInit,
1156
+ timeoutMs: number,
1157
+ abortSignal: AbortSignal,
1158
+ ): Promise<Response> {
1159
+ if (abortSignal.aborted) {
1160
+ throw new DOMException("The operation was aborted.", "AbortError");
1161
+ }
1162
+
1163
+ const controller = new AbortController();
1164
+ let timedOut = false;
1165
+ const timeout = setTimeout(() => {
1166
+ timedOut = true;
1167
+ controller.abort();
1168
+ }, timeoutMs);
1169
+ const abort = () => controller.abort();
1170
+ abortSignal.addEventListener("abort", abort, { once: true });
1171
+
1172
+ try {
1173
+ return await fetch(input, { ...init, signal: controller.signal });
1174
+ } catch (error) {
1175
+ if (timedOut) {
1176
+ throw new AgentStartupTimeoutError(timeoutMs);
1177
+ }
1178
+ throw error;
1179
+ } finally {
1180
+ clearTimeout(timeout);
1181
+ abortSignal.removeEventListener("abort", abort);
1182
+ }
1183
+ }
1184
+
1185
+ function retryDelay(attempt: number, abortSignal: AbortSignal): Promise<void> {
1186
+ const base = Math.min(
1187
+ RETRY_MAX_DELAY_MS,
1188
+ RETRY_BASE_DELAY_MS * Math.pow(2, attempt),
1189
+ );
1190
+ const jitter = base * 0.2;
1191
+ const ms = Math.max(0, base + (Math.random() * 2 - 1) * jitter);
1192
+ return delay(ms, abortSignal);
1193
+ }
1194
+
1195
+ function shouldCaptureRecoveryHttpStatus(status: number): boolean {
1196
+ return status < 500 || status >= 600;
1197
+ }
1198
+
1199
+ function generateTurnId(): string {
1200
+ if (
1201
+ typeof crypto !== "undefined" &&
1202
+ typeof crypto.randomUUID === "function"
1203
+ ) {
1204
+ return `turn-${crypto.randomUUID()}`;
1205
+ }
1206
+ return `turn-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
1207
+ }
1208
+
1209
+ function isRetryableStartupError(message: string): boolean {
1210
+ const msg = message.toLowerCase();
1211
+ if (
1212
+ msg.includes("cannot find any route matching") &&
1213
+ msg.includes("/_agent-native/agent-chat")
1214
+ ) {
1215
+ return true;
1216
+ }
1217
+ if (
1218
+ msg.includes("unauthorized") ||
1219
+ msg.includes("not authenticated") ||
1220
+ msg.includes("401") ||
1221
+ msg.includes("403") ||
1222
+ msg.includes("404") ||
1223
+ msg.includes("405") ||
1224
+ msg.includes("missing api key") ||
1225
+ msg.includes("api key") ||
1226
+ msg.includes("context_length") ||
1227
+ msg.includes("input_too_long") ||
1228
+ msg.includes("too many tokens") ||
1229
+ msg.includes("prompt is too long") ||
1230
+ msg.includes("credits-limit") ||
1231
+ msg.includes("billing") ||
1232
+ msg.includes("permission")
1233
+ ) {
1234
+ return false;
1235
+ }
1236
+ return (
1237
+ msg.includes("failed to fetch") ||
1238
+ msg.includes("network") ||
1239
+ msg.includes("connection") ||
1240
+ msg.includes("reset") ||
1241
+ msg.includes("econnreset") ||
1242
+ msg.includes("socket") ||
1243
+ msg.includes("timeout") ||
1244
+ msg.includes("gateway timeout") ||
1245
+ msg.includes("inactivity timeout") ||
1246
+ msg.includes("temporarily unavailable") ||
1247
+ msg.includes("server error: 408") ||
1248
+ msg.includes("server error: 429") ||
1249
+ msg.includes("server error: 500") ||
1250
+ msg.includes("server error: 502") ||
1251
+ msg.includes("server error: 503") ||
1252
+ msg.includes("server error: 504") ||
1253
+ msg.includes("429") ||
1254
+ msg.includes("500") ||
1255
+ msg.includes("502") ||
1256
+ msg.includes("503") ||
1257
+ msg.includes("504") ||
1258
+ msg.includes("529")
1259
+ );
1260
+ }
1261
+
1262
+ function isAuthErrorMessage(message: string): boolean {
1263
+ const msg = message.toLowerCase();
1264
+ return (
1265
+ msg.includes("authentication required") ||
1266
+ msg.includes("unauthorized") ||
1267
+ msg.includes("not authenticated") ||
1268
+ msg.includes("forbidden") ||
1269
+ msg.includes("invalid token") ||
1270
+ msg.includes("invalid or expired token") ||
1271
+ msg.includes("session expired") ||
1272
+ msg.includes("http_401") ||
1273
+ msg.includes("http_403") ||
1274
+ msg.includes("401") ||
1275
+ msg.includes("403") ||
1276
+ msg.includes("405")
1277
+ );
1278
+ }
1279
+
1280
+ function authErrorReasonFromMessage(
1281
+ message: string,
1282
+ ): "auth-required" | "session-expired" {
1283
+ const msg = message.toLowerCase();
1284
+ return msg.includes("session") ||
1285
+ msg.includes("expired") ||
1286
+ msg.includes("invalid token") ||
1287
+ msg.includes("405")
1288
+ ? "session-expired"
1289
+ : "auth-required";
1290
+ }
1291
+
1292
+ function authErrorText(
1293
+ reason: "auth-required" | "session-expired",
1294
+ message?: string,
1295
+ ): string {
1296
+ const fallback =
1297
+ reason === "session-expired"
1298
+ ? "Your chat session expired. Refresh chat and sign in again to continue."
1299
+ : "Authentication required. Sign in again to use chat.";
1300
+
1301
+ if (!message) return formatChatErrorText(fallback);
1302
+
1303
+ try {
1304
+ const parsed = JSON.parse(message) as {
1305
+ error?: unknown;
1306
+ message?: unknown;
1307
+ };
1308
+ const raw =
1309
+ typeof parsed.error === "string"
1310
+ ? parsed.error
1311
+ : typeof parsed.message === "string"
1312
+ ? parsed.message
1313
+ : undefined;
1314
+ return formatChatErrorText(raw || fallback);
1315
+ } catch {
1316
+ return formatChatErrorText(message || fallback);
1317
+ }
1318
+ }
1319
+
1320
+ function safeAgentNativePath(path: string): string {
1321
+ try {
1322
+ return agentNativePath(path);
1323
+ } catch {
1324
+ return path;
1325
+ }
1326
+ }
1327
+
1328
+ function isMissingCredentialMessage(message: string): boolean {
1329
+ const msg = message.toLowerCase();
1330
+ return (
1331
+ msg.includes("apikey") ||
1332
+ msg.includes("authtoken") ||
1333
+ msg.includes("anthropic_api_key") ||
1334
+ msg.includes("missing_api_key") ||
1335
+ msg.includes("missing api key") ||
1336
+ msg.includes("missing credentials") ||
1337
+ msg.includes("no llm provider") ||
1338
+ msg.includes("llm provider is connected")
1339
+ );
1340
+ }
1341
+
1342
+ function missingCredentialFailure(message: string): {
1343
+ text: string;
1344
+ runError: { message: string; errorCode: string };
1345
+ } {
1346
+ try {
1347
+ const parsed = JSON.parse(message) as {
1348
+ error?: unknown;
1349
+ message?: unknown;
1350
+ upgradeUrl?: unknown;
1351
+ errorCode?: unknown;
1352
+ };
1353
+ const raw =
1354
+ typeof parsed.error === "string"
1355
+ ? parsed.error
1356
+ : typeof parsed.message === "string"
1357
+ ? parsed.message
1358
+ : message;
1359
+ const errorCode =
1360
+ typeof parsed.errorCode === "string"
1361
+ ? parsed.errorCode
1362
+ : "missing_credentials";
1363
+ return {
1364
+ text: formatChatErrorText(
1365
+ raw,
1366
+ typeof parsed.upgradeUrl === "string" ? parsed.upgradeUrl : undefined,
1367
+ errorCode,
1368
+ ),
1369
+ runError: {
1370
+ message: normalizeChatError(raw).message,
1371
+ errorCode,
1372
+ },
1373
+ };
1374
+ } catch {
1375
+ return {
1376
+ text: formatChatErrorText(message, undefined, "missing_credentials"),
1377
+ runError: {
1378
+ message: normalizeChatError(message).message,
1379
+ errorCode: "missing_credentials",
1380
+ },
1381
+ };
1382
+ }
1383
+ }
1384
+
1385
+ /**
1386
+ * The composer's exec mode is sent as explicit request metadata. The server
1387
+ * owns the plan-mode prompt and read-only tool filtering so the chat history
1388
+ * stays clean and Plan mode is enforced outside the model's goodwill.
1389
+ */
1390
+ /**
1391
+ * Creates a ChatModelAdapter that connects to the agent-native
1392
+ * `/_agent-native/agent-chat` SSE endpoint. Supports reconnection via run-manager.
1393
+ */
1394
+ export interface CreateAgentChatAdapterOptions {
1395
+ apiUrl?: string;
1396
+ tabId?: string;
1397
+ threadId?: string;
1398
+ modelRef?: { current: string | undefined };
1399
+ engineRef?: { current: string | undefined };
1400
+ effortRef?: { current: ReasoningEffort | undefined };
1401
+ execModeRef?: { current: "build" | "plan" | undefined };
1402
+ browserTabId?: string;
1403
+ scopeRef?: { current: ChatThreadScope | null | undefined };
1404
+ surface?: AgentChatSurfaceKind;
1405
+ }
1406
+
1407
+ function runtimeDebugUrlForApiUrl(apiUrl: string): string | null {
1408
+ if (typeof window === "undefined") return null;
1409
+ try {
1410
+ const url = new URL(apiUrl, window.location.href);
1411
+ const marker = "/_agent-native/";
1412
+ const markerIndex = url.pathname.indexOf(marker);
1413
+ if (markerIndex < 0) return null;
1414
+ url.pathname = `${url.pathname.slice(0, markerIndex)}${marker}debug/runtime`;
1415
+ url.search = "";
1416
+ return url.toString();
1417
+ } catch {
1418
+ return null;
1419
+ }
1420
+ }
1421
+
1422
+ function stringValue(value: unknown): string {
1423
+ return typeof value === "string" ? value : "";
1424
+ }
1425
+
1426
+ function formatRuntimeDebugDetails(payload: unknown): string {
1427
+ if (!payload || typeof payload !== "object") return "";
1428
+ const root = payload as Record<string, any>;
1429
+ const runtime =
1430
+ root.runtime && typeof root.runtime === "object" ? root.runtime : {};
1431
+ const database =
1432
+ runtime.database && typeof runtime.database === "object"
1433
+ ? runtime.database
1434
+ : {};
1435
+ const schema =
1436
+ root.schema && typeof root.schema === "object" ? root.schema : {};
1437
+ const lines = [
1438
+ stringValue(runtime.app) ? `runtime_app: ${runtime.app}` : "",
1439
+ stringValue(runtime.environment)
1440
+ ? `runtime_environment: ${runtime.environment}`
1441
+ : "",
1442
+ stringValue(runtime.deployContext)
1443
+ ? `deploy_context: ${runtime.deployContext}`
1444
+ : "",
1445
+ stringValue(runtime.deployId) ? `deploy_id: ${runtime.deployId}` : "",
1446
+ stringValue(runtime.commitRef) ? `commit_ref: ${runtime.commitRef}` : "",
1447
+ stringValue(runtime.branch) ? `branch: ${runtime.branch}` : "",
1448
+ stringValue(runtime.siteName) ? `site_name: ${runtime.siteName}` : "",
1449
+ typeof database.configured === "boolean"
1450
+ ? `db_configured: ${database.configured}`
1451
+ : "",
1452
+ stringValue(database.source) ? `db_source: ${database.source}` : "",
1453
+ stringValue(database.dialect) ? `db_dialect: ${database.dialect}` : "",
1454
+ stringValue(database.protocol) ? `db_protocol: ${database.protocol}` : "",
1455
+ stringValue(database.host) ? `db_host: ${database.host}` : "",
1456
+ stringValue(database.database) ? `db_database: ${database.database}` : "",
1457
+ stringValue(database.urlHash) ? `db_url_hash: ${database.urlHash}` : "",
1458
+ database.neon?.endpointId
1459
+ ? `db_neon_endpoint: ${database.neon.endpointId}`
1460
+ : "",
1461
+ typeof database.neon?.pooled === "boolean"
1462
+ ? `db_neon_pooled: ${database.neon.pooled}`
1463
+ : "",
1464
+ typeof schema.ok === "boolean" ? `schema_ok: ${schema.ok}` : "",
1465
+ Array.isArray(schema.missingTables) && schema.missingTables.length
1466
+ ? `schema_missing_tables: ${schema.missingTables.join(", ")}`
1467
+ : "",
1468
+ Array.isArray(schema.missingColumns) && schema.missingColumns.length
1469
+ ? `schema_missing_columns: ${schema.missingColumns
1470
+ .map((entry: any) => `${entry.table}.${entry.column}`)
1471
+ .join(", ")}`
1472
+ : "",
1473
+ stringValue(schema.error) ? `schema_error: ${schema.error}` : "",
1474
+ ].filter(Boolean);
1475
+ return lines.join("\n");
1476
+ }
1477
+
1478
+ export function createAgentChatAdapter(
1479
+ options?: CreateAgentChatAdapterOptions,
1480
+ ): ChatModelAdapter {
1481
+ const apiUrl =
1482
+ options?.apiUrl ?? agentNativePath("/_agent-native/agent-chat");
1483
+ const tabId = options?.tabId;
1484
+ const threadId = options?.threadId;
1485
+ const modelRef = options?.modelRef;
1486
+ const engineRef = options?.engineRef;
1487
+ const effortRef = options?.effortRef;
1488
+ const execModeRef = options?.execModeRef;
1489
+ const browserTabId = options?.browserTabId;
1490
+ const scopeRef = options?.scopeRef;
1491
+ const surface = options?.surface ?? "app";
1492
+ // A queued recovery can survive until a server-owned continuation finishes,
1493
+ // and assistant-ui may ask the same memoized adapter to run that item again.
1494
+ const claimedRecoveryMessageIds = new Set<string>();
1495
+ let runtimeDebugDetails = "";
1496
+ const runtimeDebugUrl = runtimeDebugUrlForApiUrl(apiUrl);
1497
+ if (runtimeDebugUrl && typeof fetch === "function") {
1498
+ void fetch(runtimeDebugUrl, { credentials: "include" })
1499
+ .then((res) => (res.ok ? res.json() : null))
1500
+ .then((payload) => {
1501
+ runtimeDebugDetails = formatRuntimeDebugDetails(payload);
1502
+ })
1503
+ .catch(() => {});
1504
+ }
1505
+
1506
+ return {
1507
+ async *run({ messages, abortSignal, runConfig }) {
1508
+ // Extract latest user message and build history from prior messages
1509
+ const adapterMessages = messages as readonly AdapterMessage[];
1510
+ const latestUserIndex = (() => {
1511
+ for (let i = adapterMessages.length - 1; i >= 0; i--) {
1512
+ if (adapterMessages[i].role === "user") return i;
1513
+ }
1514
+ return -1;
1515
+ })();
1516
+ const latestUserMsg =
1517
+ latestUserIndex >= 0 ? adapterMessages[latestUserIndex] : undefined;
1518
+ const latestUserIsRecovery = latestUserMsg
1519
+ ? isRecoveryUserMessage(latestUserMsg)
1520
+ : false;
1521
+ const recoveryMessageId =
1522
+ latestUserIsRecovery && latestUserMsg?.id?.trim()
1523
+ ? latestUserMsg.id.trim()
1524
+ : null;
1525
+ if (recoveryMessageId) {
1526
+ if (claimedRecoveryMessageIds.has(recoveryMessageId)) return;
1527
+ claimedRecoveryMessageIds.add(recoveryMessageId);
1528
+ }
1529
+ const lastUserMsg =
1530
+ latestUserIsRecovery && latestUserIndex >= 0
1531
+ ? (latestUserMessage(adapterMessages, {
1532
+ skipRecovery: true,
1533
+ beforeIndex: latestUserIndex,
1534
+ }) ?? latestUserMsg)
1535
+ : latestUserMsg;
1536
+ const recoveryMessageText =
1537
+ latestUserIsRecovery && latestUserMsg
1538
+ ? messageTextFromContentRaw(latestUserMsg.content)
1539
+ : "";
1540
+ const rawMessageText =
1541
+ lastUserMsg?.content
1542
+ .filter((p): p is { type: "text"; text: string } => p.type === "text")
1543
+ .map((p) => p.text)
1544
+ .join("\n") ?? "";
1545
+ const runConfigRequestMode =
1546
+ runConfig?.custom &&
1547
+ typeof runConfig.custom === "object" &&
1548
+ "requestMode" in runConfig.custom
1549
+ ? (runConfig.custom as { requestMode?: unknown }).requestMode
1550
+ : undefined;
1551
+ const trackInRunsTray =
1552
+ runConfig?.custom &&
1553
+ typeof runConfig.custom === "object" &&
1554
+ (runConfig.custom as { trackInRunsTray?: unknown }).trackInRunsTray ===
1555
+ true;
1556
+ // Human-in-the-loop approval keys (opt-in `needsApproval` actions). When
1557
+ // the user approves a paused tool call, the turn is re-issued with the
1558
+ // approval key so the server lets that specific call run.
1559
+ const approvedToolCalls = (() => {
1560
+ const raw =
1561
+ runConfig?.custom &&
1562
+ typeof runConfig.custom === "object" &&
1563
+ "approvedToolCalls" in runConfig.custom
1564
+ ? (runConfig.custom as { approvedToolCalls?: unknown })
1565
+ .approvedToolCalls
1566
+ : undefined;
1567
+ if (!Array.isArray(raw)) return undefined;
1568
+ const keys = raw.filter(
1569
+ (key): key is string => typeof key === "string" && key.length > 0,
1570
+ );
1571
+ return keys.length > 0 ? keys : undefined;
1572
+ })();
1573
+ const requestMode =
1574
+ runConfigRequestMode === "act" || runConfigRequestMode === "plan"
1575
+ ? runConfigRequestMode
1576
+ : execModeRef?.current === "plan"
1577
+ ? "plan"
1578
+ : execModeRef?.current === "build"
1579
+ ? "act"
1580
+ : undefined;
1581
+
1582
+ const withRequestModeMetadata = (
1583
+ result: ChatModelRunResult,
1584
+ ): ChatModelRunResult => {
1585
+ if (!requestMode) return result;
1586
+ const metadata = (result.metadata ?? {}) as Record<string, unknown>;
1587
+ const custom =
1588
+ metadata.custom && typeof metadata.custom === "object"
1589
+ ? (metadata.custom as Record<string, unknown>)
1590
+ : {};
1591
+ return {
1592
+ ...result,
1593
+ metadata: {
1594
+ ...metadata,
1595
+ custom: { ...custom, requestMode },
1596
+ },
1597
+ };
1598
+ };
1599
+
1600
+ // Extract attachments (images as base64, text as content).
1601
+ // assistant-ui puts user attachments on msg.attachments (not on content);
1602
+ // each attachment carries its own content parts from the adapter.
1603
+ const attachments = lastUserMsg
1604
+ ? extractAttachmentsFromMessage(lastUserMsg as any)
1605
+ : [];
1606
+ const userMessageText =
1607
+ rawMessageText.trim() || attachments.length === 0
1608
+ ? rawMessageText
1609
+ : "Use the attached context.";
1610
+
1611
+ const priorMessages = limitPriorMessagesForRequest(
1612
+ messages.slice(0, latestUserIndex >= 0 ? latestUserIndex : -1) as any,
1613
+ ); // exclude latest user/recovery message and cap resend size
1614
+ const history = priorMessages
1615
+ .filter((m) => m.role === "user" || m.role === "assistant")
1616
+ .map((m) => ({
1617
+ role: m.role as "user" | "assistant",
1618
+ content:
1619
+ m.role === "user"
1620
+ ? messageTextForHistory(m as any)
1621
+ : messageTextFromContent(m.content),
1622
+ }))
1623
+ .filter((m) => m.content.trim());
1624
+ const structuredHistory =
1625
+ assistantUiMessagesToStructuredHistory(priorMessages);
1626
+
1627
+ // Signal that generation is starting
1628
+ if (typeof window !== "undefined") {
1629
+ window.dispatchEvent(
1630
+ new CustomEvent("agentNative.chatRunning", {
1631
+ detail: { isRunning: true, tabId },
1632
+ }),
1633
+ );
1634
+ }
1635
+
1636
+ const content: ContentPart[] = [];
1637
+ const toolCallCounter = { value: 0 };
1638
+ const turnId = generateTurnId();
1639
+ if (threadId) setPendingTurn({ threadId, turnId });
1640
+ let runId: string | null = null;
1641
+ let lastSeq = -1;
1642
+ const seenRunSeqs = new Map<string, number>();
1643
+ const preparingActionStatesByRun = new Map<
1644
+ string,
1645
+ PreparingActionState
1646
+ >();
1647
+ let currentRunDispatchMode: string | null = null;
1648
+ let currentMessageText = normalizeMentions(
1649
+ recoveryMessageText.trim() || userMessageText,
1650
+ );
1651
+ let currentHistory: AdapterHistoryMessage[] = history;
1652
+ let currentStructuredHistory: AgentChatStructuredMessage[] =
1653
+ structuredHistory;
1654
+ let includeAttachments = attachments.length > 0;
1655
+ let includeReferences = Boolean(runConfig?.custom?.references);
1656
+ let internalContinuationRequest = latestUserIsRecovery;
1657
+ let startupRecoveryAttempts = 0;
1658
+ let queuedConflictRetries = 0;
1659
+ let staleRunContinuationAttempts = 0;
1660
+ let stalledTransientContinuationAttempts = 0;
1661
+ let emptyTransientContinuationAttempts = 0;
1662
+ let totalTransientContinuationAttempts = 0;
1663
+ let repeatedTransientContinuationAttempts = 0;
1664
+ let lastContinuationRepeatSignature: string | null = null;
1665
+ let recoveryGaveUpOnRepetition = false;
1666
+ // Track when the same write tool is stuck in-flight across consecutive
1667
+ // continuations (connection keeps dropping mid-execution). This is
1668
+ // orthogonal to the text-repeat guard — in-flight tools reset
1669
+ // madeProgress=true so the stalled/empty budgets never fire, but the tool
1670
+ // never actually completes. After MAX_REPEATED_INFLIGHT_TOOL_STALLS
1671
+ // consecutive interruptions of the same tool, bail with a clear message.
1672
+ let lastInFlightToolName: string | undefined;
1673
+ // Signature of the stuck tool's input. A retry with a changed payload
1674
+ // (different signature) resets the stall count so the new attempt gets
1675
+ // its own budget rather than inheriting the prior payload's.
1676
+ let lastInFlightToolSignature: string | undefined;
1677
+ let repeatedInFlightToolCount = 0;
1678
+ let recoveryGaveUpOnInFlightTool = false;
1679
+ const MAX_REPEATED_INFLIGHT_TOOL_STALLS = 3;
1680
+ let lastPreparingToolName: string | undefined;
1681
+ let repeatedActionPreparationCount = 0;
1682
+ let recoveryGaveUpOnActionPreparation = false;
1683
+ const continuationHistoryFragments: string[] = [];
1684
+ const structuredContinuationFragments: AgentChatStructuredMessage[] = [];
1685
+ let visibleContinuationPrefix: ContentPart[] = [];
1686
+ let lastAutoContinueReason: string | null = null;
1687
+ let lastRecoverableRunError: AgentAutoContinueErrorInfo | null = null;
1688
+ let lastActivityTrail: AgentActivityTrailEntry[] = [];
1689
+ const attemptedRunIds: string[] = [];
1690
+ let authRecoveryAttempted = false;
1691
+ let continuationToolCallCounter = 0;
1692
+ const nextContinuationToolCallId = () =>
1693
+ `continuation_tc_${++continuationToolCallCounter}`;
1694
+
1695
+ const runDebugContextDetails = (): string => {
1696
+ const pageOrigin =
1697
+ typeof window !== "undefined" && window.location?.origin
1698
+ ? window.location.origin
1699
+ : "";
1700
+ return [
1701
+ `api_url: ${apiUrl}`,
1702
+ pageOrigin ? `page_origin: ${pageOrigin}` : "",
1703
+ tabId ? `tab_id: ${tabId}` : "",
1704
+ threadId ? `thread_id: ${threadId}` : "",
1705
+ `turn_id: ${turnId}`,
1706
+ runId ? `current_run: ${runId}` : "",
1707
+ attemptedRunIds.length > 0
1708
+ ? `attempted_runs: ${attemptedRunIds.join(", ")}`
1709
+ : "",
1710
+ runtimeDebugDetails,
1711
+ ]
1712
+ .filter(Boolean)
1713
+ .join("\n");
1714
+ };
1715
+
1716
+ const connectionRecoveryDetails = (): string => {
1717
+ return [
1718
+ runDebugContextDetails(),
1719
+ lastAutoContinueReason
1720
+ ? `last_auto_continue_reason: ${lastAutoContinueReason}`
1721
+ : "",
1722
+ lastRecoverableRunError?.errorCode
1723
+ ? `last_recoverable_error_code: ${lastRecoverableRunError.errorCode}`
1724
+ : "",
1725
+ lastRecoverableRunError?.message
1726
+ ? `last_recoverable_error: ${lastRecoverableRunError.message}`
1727
+ : "",
1728
+ `stale_run_continuations: ${staleRunContinuationAttempts}`,
1729
+ `stalled_transient_continuations: ${stalledTransientContinuationAttempts}`,
1730
+ `empty_transient_continuations: ${emptyTransientContinuationAttempts}`,
1731
+ `repeated_transient_continuations: ${repeatedTransientContinuationAttempts}`,
1732
+ `repeated_inflight_tool_stalls: ${repeatedInFlightToolCount}`,
1733
+ lastInFlightToolName
1734
+ ? `last_inflight_tool: ${lastInFlightToolName}`
1735
+ : "",
1736
+ `repeated_action_preparation_stalls: ${repeatedActionPreparationCount}`,
1737
+ lastPreparingToolName
1738
+ ? `last_preparing_tool: ${lastPreparingToolName}`
1739
+ : "",
1740
+ formatActivityTrail(lastActivityTrail)
1741
+ ? `activity_trail: ${formatActivityTrail(lastActivityTrail)}`
1742
+ : "",
1743
+ `total_transient_continuations: ${totalTransientContinuationAttempts}`,
1744
+ ]
1745
+ .filter(Boolean)
1746
+ .join("\n");
1747
+ };
1748
+
1749
+ const exhaustedRecoveryMessage = (reason?: string): string => {
1750
+ if (recoveryGaveUpOnInFlightTool) {
1751
+ return "The agent got stuck waiting for the same tool to finish, so I stopped the automatic retries. The tool did not report a completed result.";
1752
+ }
1753
+ if (recoveryGaveUpOnRepetition) {
1754
+ return "The agent got stuck repeating the same response without finishing, so I stopped the automatic retries. This often happens when it tries to re-type a large pasted file into one action — starting a new chat, or asking for a smaller first step, usually gets it unstuck.";
1755
+ }
1756
+ if (recoveryGaveUpOnActionPreparation) {
1757
+ const tool = lastPreparingToolName
1758
+ ? ` the ${humanizeActionName(lastPreparingToolName)} action`
1759
+ : " the same action";
1760
+ return `The agent got stuck preparing${tool} input and never started the tool, so I stopped the automatic retries. Try a smaller first step or a more compact version of the request.`;
1761
+ }
1762
+ if (
1763
+ content.length === 0 &&
1764
+ (reason === "run_timeout" ||
1765
+ reason === "no_progress" ||
1766
+ reason === "stream_ended")
1767
+ ) {
1768
+ return "The agent request started but did not produce any visible progress before timing out. I stopped the automatic retries so this chat would not stay stuck on Thinking.";
1769
+ }
1770
+ return "The agent connection kept failing after several automatic recovery attempts.";
1771
+ };
1772
+
1773
+ const dispatchAuthError = (
1774
+ reason: "auth-required" | "session-expired",
1775
+ ) => {
1776
+ if (typeof window === "undefined") return;
1777
+ window.dispatchEvent(
1778
+ new CustomEvent("agent-chat:auth-error", {
1779
+ detail: {
1780
+ reason,
1781
+ ...(tabId ? { tabId } : {}),
1782
+ ...(threadId ? { threadId } : {}),
1783
+ },
1784
+ }),
1785
+ );
1786
+ };
1787
+
1788
+ const dispatchMissingApiKey = () => {
1789
+ if (typeof window === "undefined") return;
1790
+ window.dispatchEvent(
1791
+ new CustomEvent("agent-chat:missing-api-key", {
1792
+ detail: {
1793
+ ...(tabId ? { tabId } : {}),
1794
+ ...(threadId ? { threadId } : {}),
1795
+ },
1796
+ }),
1797
+ );
1798
+ };
1799
+
1800
+ const tryRecoverAuthOnce = async (): Promise<boolean> => {
1801
+ if (authRecoveryAttempted || abortSignal.aborted) return false;
1802
+ authRecoveryAttempted = true;
1803
+ try {
1804
+ const sessionRes = await fetch(
1805
+ safeAgentNativePath("/_agent-native/auth/session"),
1806
+ {
1807
+ method: "GET",
1808
+ headers: { Accept: "application/json" },
1809
+ cache: "no-store",
1810
+ credentials: "same-origin",
1811
+ signal: abortSignal,
1812
+ },
1813
+ );
1814
+ if (!sessionRes.ok) return false;
1815
+ const session = await sessionRes.json().catch(() => null);
1816
+ return Boolean(session && !session.error);
1817
+ } catch {
1818
+ return false;
1819
+ }
1820
+ };
1821
+
1822
+ const updateCurrentRunDispatchMode = (value: unknown) => {
1823
+ if (typeof value !== "string") return;
1824
+ const mode = value.trim();
1825
+ if (mode) currentRunDispatchMode = mode;
1826
+ };
1827
+
1828
+ // Mode switch for recovery ownership: durable/background-dispatched runs
1829
+ // are always recovered by the server. Foreground self-chain follows the
1830
+ // server on healthy handoffs. A loud continuation-dispatch failure falls
1831
+ // back to the client POST path in either mode because the server has
1832
+ // already marked the unclaimed successor terminal; the per-turn tool
1833
+ // journal keeps that retry from replaying completed side effects.
1834
+ const isDurableBackgroundDispatch = () =>
1835
+ currentRunDispatchMode?.startsWith("background") === true;
1836
+ const isForegroundSelfChainDispatch = () =>
1837
+ currentRunDispatchMode === "foreground-self-chain";
1838
+ const shouldFollowServerContinuation = (
1839
+ signal?: AgentAutoContinueSignal,
1840
+ ) => {
1841
+ if (
1842
+ signal?.errorInfo?.errorCode ===
1843
+ "background_continuation_dispatch_failed"
1844
+ ) {
1845
+ return false;
1846
+ }
1847
+ if (isDurableBackgroundDispatch()) return true;
1848
+ if (!isForegroundSelfChainDispatch()) return false;
1849
+ return true;
1850
+ };
1851
+
1852
+ const rememberRunSeq = (seq: number) => {
1853
+ lastSeq = seq;
1854
+ if (runId) {
1855
+ seenRunSeqs.set(runId, seq);
1856
+ }
1857
+ };
1858
+
1859
+ const reconnectCursorForRun = (
1860
+ nextRunId: string,
1861
+ previousRunId: string | null,
1862
+ ) => {
1863
+ if (previousRunId !== nextRunId) {
1864
+ lastAutoContinueReason = null;
1865
+ lastRecoverableRunError = null;
1866
+ lastActivityTrail = [];
1867
+ }
1868
+ const rememberedSeq = seenRunSeqs.get(nextRunId);
1869
+ if (rememberedSeq !== undefined) {
1870
+ lastSeq = rememberedSeq;
1871
+ return;
1872
+ }
1873
+ if (previousRunId !== nextRunId) {
1874
+ lastSeq = -1;
1875
+ }
1876
+ };
1877
+
1878
+ const canAttachRun = (candidateRunId: string, candidateTurnId: string) =>
1879
+ attemptedRunIds.includes(candidateRunId) ||
1880
+ (candidateTurnId.length > 0 && candidateTurnId === turnId);
1881
+
1882
+ const preparingActionStateForRun = (
1883
+ id: string | null,
1884
+ ): PreparingActionState | undefined => {
1885
+ if (!id) return undefined;
1886
+ const existing = preparingActionStatesByRun.get(id);
1887
+ if (existing) return existing;
1888
+ const state: PreparingActionState = {};
1889
+ preparingActionStatesByRun.set(id, state);
1890
+ return state;
1891
+ };
1892
+
1893
+ const currentSSEOptions = () => ({
1894
+ durableBackgroundRun:
1895
+ currentRunDispatchMode?.startsWith("background") === true,
1896
+ ...(runId
1897
+ ? { preparingActionState: preparingActionStateForRun(runId) }
1898
+ : {}),
1899
+ });
1900
+
1901
+ const captureChatClientError = (
1902
+ error: unknown,
1903
+ phase: string,
1904
+ extra: Record<string, unknown> = {},
1905
+ ) => {
1906
+ captureError(error, {
1907
+ tags: {
1908
+ source: "agent-chat-client",
1909
+ phase,
1910
+ hasThread: threadId ? "true" : "false",
1911
+ hasRun: runId ? "true" : "false",
1912
+ lastAutoContinueReason: lastAutoContinueReason ?? undefined,
1913
+ },
1914
+ extra: {
1915
+ apiUrl,
1916
+ tabId,
1917
+ threadId,
1918
+ runId,
1919
+ lastSeq,
1920
+ contentParts: content.length,
1921
+ attemptedRunIds: [...attemptedRunIds],
1922
+ activityTrail: [...lastActivityTrail],
1923
+ startupRecoveryAttempts,
1924
+ staleRunContinuationAttempts,
1925
+ stalledTransientContinuationAttempts,
1926
+ emptyTransientContinuationAttempts,
1927
+ repeatedTransientContinuationAttempts,
1928
+ repeatedInFlightToolCount,
1929
+ lastInFlightToolName,
1930
+ repeatedActionPreparationCount,
1931
+ lastPreparingToolName,
1932
+ totalTransientContinuationAttempts,
1933
+ ...extra,
1934
+ },
1935
+ contexts: {
1936
+ agentChat: {
1937
+ tabId,
1938
+ threadId,
1939
+ runId,
1940
+ lastSeq,
1941
+ contentParts: content.length,
1942
+ startupRecoveryAttempts,
1943
+ staleRunContinuationAttempts,
1944
+ stalledTransientContinuationAttempts,
1945
+ emptyTransientContinuationAttempts,
1946
+ repeatedTransientContinuationAttempts,
1947
+ repeatedInFlightToolCount,
1948
+ lastInFlightToolName,
1949
+ repeatedActionPreparationCount,
1950
+ lastPreparingToolName,
1951
+ totalTransientContinuationAttempts,
1952
+ },
1953
+ },
1954
+ });
1955
+ };
1956
+
1957
+ try {
1958
+ const headers: Record<string, string> = {
1959
+ "Content-Type": "application/json",
1960
+ };
1961
+ try {
1962
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
1963
+ if (tz) headers["x-user-timezone"] = tz;
1964
+ } catch {
1965
+ // Non-browser or Intl unavailable — tool calls will fall back to UTC.
1966
+ }
1967
+ // Surface hint — the server uses this to keep code-editing dev tools
1968
+ // out of the app-rendered sidebar. The outer dev frame passes
1969
+ // "dev-frame" explicitly; the reusable in-product chat defaults to
1970
+ // "app" even when it is running in Desktop or inside a preview iframe.
1971
+ headers["x-agent-native-surface"] = surface;
1972
+
1973
+ const reconnectCurrentRun = async function* (): AsyncGenerator<
1974
+ ChatModelRunResult,
1975
+ boolean,
1976
+ unknown
1977
+ > {
1978
+ if (!runId) return false;
1979
+ let lastReconnectError: unknown = null;
1980
+ let reconnectErrorCaptured = false;
1981
+ for (let attempt = 0; attempt < MAX_RECONNECT_ATTEMPTS; attempt++) {
1982
+ try {
1983
+ const reconnectRes = await fetch(
1984
+ `${apiUrl}/runs/${encodeURIComponent(runId)}/events?after=${lastSeq + 1}`,
1985
+ { signal: abortSignal },
1986
+ );
1987
+ if (!reconnectRes.ok || !reconnectRes.body) {
1988
+ if (reconnectRes.status === 404) {
1989
+ clearActiveRun();
1990
+ return false;
1991
+ }
1992
+ lastReconnectError = new Error(
1993
+ `Reconnect failed: ${reconnectRes.status}`,
1994
+ );
1995
+ if (shouldCaptureRecoveryHttpStatus(reconnectRes.status)) {
1996
+ captureChatClientError(
1997
+ lastReconnectError,
1998
+ "reconnect-current-response",
1999
+ {
2000
+ status: reconnectRes.status,
2001
+ hasBody: Boolean(reconnectRes.body),
2002
+ attempt,
2003
+ },
2004
+ );
2005
+ }
2006
+ reconnectErrorCaptured = true;
2007
+ break;
2008
+ }
2009
+ updateCurrentRunDispatchMode(
2010
+ reconnectRes.headers.get("X-Dispatch-Mode"),
2011
+ );
2012
+
2013
+ for await (const result of readSSEStream(
2014
+ reconnectRes.body,
2015
+ content,
2016
+ toolCallCounter,
2017
+ tabId,
2018
+ (seq) => {
2019
+ rememberRunSeq(seq);
2020
+ if (threadId) updateActiveRunSeq(seq);
2021
+ },
2022
+ runId,
2023
+ currentSSEOptions(),
2024
+ )) {
2025
+ yield withRequestModeMetadata(result);
2026
+ }
2027
+ clearActiveRun();
2028
+ return true;
2029
+ } catch (reconnectErr: unknown) {
2030
+ if (
2031
+ reconnectErr instanceof Error &&
2032
+ reconnectErr.name === "AbortError"
2033
+ ) {
2034
+ clearActiveRun();
2035
+ return true;
2036
+ }
2037
+ if (reconnectErr instanceof AgentAutoContinueSignal) {
2038
+ if (reconnectErr.reason === "no_progress") {
2039
+ throw reconnectErr;
2040
+ }
2041
+ return false;
2042
+ }
2043
+ lastReconnectError = reconnectErr;
2044
+ await retryDelay(attempt, abortSignal);
2045
+ }
2046
+ }
2047
+ if (lastReconnectError && !reconnectErrorCaptured) {
2048
+ captureChatClientError(
2049
+ lastReconnectError,
2050
+ "reconnect-current-failed",
2051
+ );
2052
+ }
2053
+ return false;
2054
+ };
2055
+
2056
+ const abortCurrentRun = async (): Promise<void> => {
2057
+ if (!runId) return;
2058
+ try {
2059
+ await fetch(`${apiUrl}/runs/${encodeURIComponent(runId)}/abort`, {
2060
+ method: "POST",
2061
+ headers: { "Content-Type": "application/json" },
2062
+ body: JSON.stringify({ reason: "no_progress" }),
2063
+ signal: abortSignal,
2064
+ });
2065
+ } catch {
2066
+ // Best effort. The follow-up POST will still reconnect or 409 if
2067
+ // the producer is alive and cannot be aborted cross-isolate.
2068
+ } finally {
2069
+ clearActiveRun();
2070
+ }
2071
+ };
2072
+
2073
+ const reconnectActiveRunForThread = async function* (): AsyncGenerator<
2074
+ ChatModelRunResult,
2075
+ boolean,
2076
+ unknown
2077
+ > {
2078
+ if (!threadId) return false;
2079
+ let lastActiveRunError: unknown = null;
2080
+ for (let attempt = 0; attempt < MAX_RECONNECT_ATTEMPTS; attempt++) {
2081
+ try {
2082
+ const activeRes = await fetch(
2083
+ `${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId)}`,
2084
+ { signal: abortSignal },
2085
+ );
2086
+ if (!activeRes.ok) {
2087
+ if (activeRes.status === 404) {
2088
+ return false;
2089
+ }
2090
+ lastActiveRunError = new Error(
2091
+ `Active run lookup failed: ${activeRes.status}`,
2092
+ );
2093
+ if (shouldCaptureRecoveryHttpStatus(activeRes.status)) {
2094
+ captureChatClientError(
2095
+ lastActiveRunError,
2096
+ "reconnect-active-response",
2097
+ { status: activeRes.status, attempt },
2098
+ );
2099
+ }
2100
+ return false;
2101
+ }
2102
+ const active = await activeRes.json();
2103
+ if (active?.active && active.runId) {
2104
+ const activeRunId = String(active.runId);
2105
+ const activeTurnId =
2106
+ typeof active.turnId === "string" ? active.turnId : "";
2107
+ if (!canAttachRun(activeRunId, activeTurnId)) {
2108
+ return false;
2109
+ }
2110
+ updateCurrentRunDispatchMode(active.dispatchMode);
2111
+ const previousRunId = runId;
2112
+ runId = activeRunId;
2113
+ if (!attemptedRunIds.includes(activeRunId)) {
2114
+ attemptedRunIds.push(activeRunId);
2115
+ }
2116
+ reconnectCursorForRun(activeRunId, previousRunId);
2117
+ setActiveRun({ threadId, runId: activeRunId, lastSeq });
2118
+ const reconnected = yield* reconnectCurrentRun();
2119
+ if (reconnected) return true;
2120
+ }
2121
+ return false;
2122
+ } catch (activeErr: unknown) {
2123
+ if (
2124
+ activeErr instanceof Error &&
2125
+ activeErr.name === "AbortError"
2126
+ ) {
2127
+ clearActiveRun();
2128
+ return true;
2129
+ }
2130
+ lastActiveRunError = activeErr;
2131
+ await retryDelay(attempt, abortSignal);
2132
+ }
2133
+ }
2134
+ if (lastActiveRunError) {
2135
+ captureChatClientError(
2136
+ lastActiveRunError,
2137
+ "reconnect-active-failed",
2138
+ );
2139
+ }
2140
+ return false;
2141
+ };
2142
+
2143
+ const reconnectBackgroundContinuationForRunTimeout =
2144
+ async function* (): AsyncGenerator<
2145
+ ChatModelRunResult,
2146
+ boolean,
2147
+ unknown
2148
+ > {
2149
+ if (!threadId || !runId) return false;
2150
+ const interruptedRunId = runId;
2151
+ const interruptedLastSeq = lastSeq;
2152
+ let lastActiveRunError: unknown = null;
2153
+ for (let attempt = 0; attempt < 3; attempt++) {
2154
+ if (attempt > 0) {
2155
+ await delay(500, abortSignal);
2156
+ }
2157
+ if (abortSignal.aborted) return true;
2158
+ try {
2159
+ const activeRes = await fetch(
2160
+ `${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId)}`,
2161
+ { signal: abortSignal },
2162
+ );
2163
+ if (!activeRes.ok) {
2164
+ if (activeRes.status === 404) return false;
2165
+ lastActiveRunError = new Error(
2166
+ `Active run lookup failed: ${activeRes.status}`,
2167
+ );
2168
+ continue;
2169
+ }
2170
+ const active = await activeRes.json();
2171
+ if (!active?.active || !active.runId) return false;
2172
+ const activeRunId = String(active.runId);
2173
+ const dispatchMode =
2174
+ typeof active.dispatchMode === "string"
2175
+ ? active.dispatchMode
2176
+ : "";
2177
+ const activeTurnId =
2178
+ typeof active.turnId === "string" ? active.turnId : "";
2179
+ if (!canAttachRun(activeRunId, activeTurnId)) return false;
2180
+ updateCurrentRunDispatchMode(dispatchMode);
2181
+ if (activeRunId === interruptedRunId) {
2182
+ if (dispatchMode.startsWith("background")) continue;
2183
+ return false;
2184
+ }
2185
+ const activeStatus =
2186
+ typeof active.status === "string" ? active.status : "";
2187
+ if (!dispatchMode.startsWith("background")) return false;
2188
+ if (activeStatus !== "running" && activeStatus !== "starting") {
2189
+ return false;
2190
+ }
2191
+ const previousRunId = runId;
2192
+ runId = activeRunId;
2193
+ if (!attemptedRunIds.includes(activeRunId)) {
2194
+ attemptedRunIds.push(activeRunId);
2195
+ }
2196
+ reconnectCursorForRun(activeRunId, previousRunId);
2197
+ setActiveRun({ threadId, runId: activeRunId, lastSeq });
2198
+ const reconnected = yield* reconnectCurrentRun();
2199
+ if (reconnected) return true;
2200
+ } catch (activeErr: unknown) {
2201
+ if (
2202
+ activeErr instanceof Error &&
2203
+ activeErr.name === "AbortError"
2204
+ ) {
2205
+ clearActiveRun();
2206
+ return true;
2207
+ }
2208
+ lastActiveRunError = activeErr;
2209
+ }
2210
+ }
2211
+ if (lastActiveRunError) {
2212
+ captureChatClientError(
2213
+ lastActiveRunError,
2214
+ "reconnect-background-continuation-failed",
2215
+ );
2216
+ }
2217
+ runId = interruptedRunId;
2218
+ lastSeq = interruptedLastSeq;
2219
+ return false;
2220
+ };
2221
+
2222
+ // ── Background follow mode (see module comment on the constants) ──
2223
+ // Emits a terminal chat error using the same runError/content/yield
2224
+ // shape as the exhausted-recovery paths, so background failures render
2225
+ // identically to foreground ones.
2226
+ const emitBackgroundTerminalError = function* (args: {
2227
+ message: string;
2228
+ errorCode: string;
2229
+ details?: string;
2230
+ }): Generator<ChatModelRunResult, void, unknown> {
2231
+ const runError = {
2232
+ message: args.message,
2233
+ details: [args.details, connectionRecoveryDetails()]
2234
+ .filter(Boolean)
2235
+ .join("\n\n"),
2236
+ errorCode: args.errorCode,
2237
+ recoverable: true,
2238
+ ...(runId ? { runId } : {}),
2239
+ };
2240
+ if (typeof window !== "undefined") {
2241
+ if (args.errorCode === LLM_MISSING_CREDENTIALS_ERROR_CODE) {
2242
+ dispatchMissingApiKey();
2243
+ }
2244
+ window.dispatchEvent(
2245
+ new CustomEvent("agent-chat:run-error", {
2246
+ detail: { ...runError, tabId },
2247
+ }),
2248
+ );
2249
+ }
2250
+ settleInterruptedToolCalls(content, undefined, {
2251
+ includeActivity: true,
2252
+ });
2253
+ content.push({
2254
+ type: "text",
2255
+ text: formatChatErrorText(args.message, undefined, args.errorCode),
2256
+ });
2257
+ yield {
2258
+ content: [...content],
2259
+ status: { type: "incomplete" as const, reason: "error" as const },
2260
+ metadata: { custom: { ...(runId ? { runId } : {}), runError } },
2261
+ } as ChatModelRunResult;
2262
+ clearActiveRun();
2263
+ };
2264
+
2265
+ // Final outcome for a background turn the follow loop can no longer
2266
+ // follow: either the run went terminal server-side (surface its
2267
+ // error, mapping terminal_reason to clear copy) or it vanished
2268
+ // (finalize with received content when it completed, loud error
2269
+ // otherwise — never a silent stop).
2270
+ const emitBackgroundTerminalOutcome = function* (
2271
+ lastKnown: Record<string, unknown> | null,
2272
+ ): Generator<ChatModelRunResult, void, unknown> {
2273
+ const status =
2274
+ typeof lastKnown?.status === "string" ? lastKnown.status : "";
2275
+ const rawTerminalReason =
2276
+ typeof lastKnown?.terminalReason === "string"
2277
+ ? lastKnown.terminalReason
2278
+ : "";
2279
+ // terminal_reason is either a bare reason ("dispatch_payload_missing")
2280
+ // or "error:<errorCode>" when derived from a terminal error event.
2281
+ const hasErrorTerminalReason = rawTerminalReason.startsWith("error:");
2282
+ const terminalReason = rawTerminalReason.replace(/^error:/, "");
2283
+ const mappedMessage = terminalReason
2284
+ ? BACKGROUND_TERMINAL_REASON_MESSAGES[terminalReason]
2285
+ : undefined;
2286
+ const mappedErrorCode =
2287
+ terminalReason === "missing_api_key" ||
2288
+ terminalReason === LLM_MISSING_CREDENTIALS_ERROR_CODE
2289
+ ? LLM_MISSING_CREDENTIALS_ERROR_CODE
2290
+ : terminalReason;
2291
+
2292
+ if (mappedMessage) {
2293
+ yield* emitBackgroundTerminalError({
2294
+ message: mappedMessage,
2295
+ errorCode: mappedErrorCode,
2296
+ details: `terminal_reason: ${rawTerminalReason}`,
2297
+ });
2298
+ return;
2299
+ }
2300
+
2301
+ if (hasErrorTerminalReason) {
2302
+ if (lastRecoverableRunError) {
2303
+ yield* emitBackgroundTerminalError({
2304
+ message: lastRecoverableRunError.message,
2305
+ errorCode:
2306
+ lastRecoverableRunError.errorCode ||
2307
+ mappedErrorCode ||
2308
+ "background_run_failed",
2309
+ details: lastRecoverableRunError.details,
2310
+ });
2311
+ return;
2312
+ }
2313
+ yield* emitBackgroundTerminalError({
2314
+ message:
2315
+ "The agent's background run failed before its final response could be recovered. You can retry from the preserved chat context.",
2316
+ errorCode: mappedErrorCode || "background_run_failed",
2317
+ details: `terminal_reason: ${rawTerminalReason}`,
2318
+ });
2319
+ return;
2320
+ }
2321
+
2322
+ if (status === "completed") {
2323
+ // The turn finished server-side; the events we managed to fold are
2324
+ // the durable transcript. Finalize with them.
2325
+ settleInterruptedToolCalls(content, undefined, {
2326
+ includeActivity: true,
2327
+ });
2328
+ yield {
2329
+ content: [...content],
2330
+ status: { type: "complete" as const, reason: "stop" as const },
2331
+ metadata: { custom: { ...(runId ? { runId } : {}) } },
2332
+ } as ChatModelRunResult;
2333
+ clearActiveRun();
2334
+ return;
2335
+ }
2336
+
2337
+ if (lastRecoverableRunError) {
2338
+ // A replayed terminal error event already carried the real
2339
+ // message (recoverable errors replay as auto-continue signals
2340
+ // whose errorInfo we captured while following).
2341
+ yield* emitBackgroundTerminalError({
2342
+ message: lastRecoverableRunError.message,
2343
+ errorCode:
2344
+ lastRecoverableRunError.errorCode ?? "connection_error",
2345
+ details: lastRecoverableRunError.details,
2346
+ });
2347
+ return;
2348
+ }
2349
+ yield* emitBackgroundTerminalError({
2350
+ message:
2351
+ "The agent's background run stopped before finishing and no continuation appeared. You can retry from the preserved chat context.",
2352
+ errorCode: terminalReason || "background_run_lost",
2353
+ details: rawTerminalReason
2354
+ ? `terminal_reason: ${rawTerminalReason}`
2355
+ : undefined,
2356
+ });
2357
+ };
2358
+
2359
+ // One read-only attach to the current run's event stream. Unlike
2360
+ // reconnectCurrentRun this does NOT retry internally and lets the
2361
+ // follow loop see every auto-continue signal (with errorInfo intact),
2362
+ // because the loop — not this reader — decides what a detach means.
2363
+ // Return values: "completed" = terminal event consumed (turn over);
2364
+ // "aborted" = user abort; "detached" = we ATTACHED to a live stream
2365
+ // that ended/stalled without a terminal event (resets the follow
2366
+ // loop's idle window); "gone" = could not attach at all (404, HTTP
2367
+ // error, network failure — the idle window keeps accumulating so a
2368
+ // persistently unattachable run still terminates loudly).
2369
+ const followAttachOnce = async function* (): AsyncGenerator<
2370
+ ChatModelRunResult,
2371
+ "completed" | "aborted" | "detached" | "gone",
2372
+ unknown
2373
+ > {
2374
+ if (!runId) return "gone";
2375
+ let attached = false;
2376
+ try {
2377
+ const eventsRes = await fetch(
2378
+ `${apiUrl}/runs/${encodeURIComponent(runId)}/events?after=${lastSeq + 1}`,
2379
+ { signal: abortSignal },
2380
+ );
2381
+ if (!eventsRes.ok || !eventsRes.body) {
2382
+ return "gone";
2383
+ }
2384
+ attached = true;
2385
+ updateCurrentRunDispatchMode(
2386
+ eventsRes.headers.get("X-Dispatch-Mode"),
2387
+ );
2388
+ for await (const result of readSSEStream(
2389
+ eventsRes.body,
2390
+ content,
2391
+ toolCallCounter,
2392
+ tabId,
2393
+ (seq) => {
2394
+ rememberRunSeq(seq);
2395
+ if (threadId) updateActiveRunSeq(seq);
2396
+ },
2397
+ runId,
2398
+ currentSSEOptions(),
2399
+ )) {
2400
+ yield withRequestModeMetadata(result);
2401
+ }
2402
+ // readSSEStream returned normally: a terminal done/error was
2403
+ // consumed and rendered — the turn is over.
2404
+ clearActiveRun();
2405
+ return "completed";
2406
+ } catch (attachErr: unknown) {
2407
+ if (attachErr instanceof Error && attachErr.name === "AbortError") {
2408
+ clearActiveRun();
2409
+ return "aborted";
2410
+ }
2411
+ if (attachErr instanceof AgentAutoContinueSignal) {
2412
+ lastAutoContinueReason = attachErr.reason;
2413
+ if (attachErr.activityTrail.length > 0) {
2414
+ lastActivityTrail = [...attachErr.activityTrail];
2415
+ }
2416
+ if (attachErr.errorInfo) {
2417
+ lastRecoverableRunError = attachErr.errorInfo;
2418
+ }
2419
+ }
2420
+ // A fetch that never yielded a response body counts as "gone" for
2421
+ // the idle window; a broken mid-stream read counts as attached.
2422
+ return attached ? "detached" : "gone";
2423
+ }
2424
+ };
2425
+
2426
+ // The background follow loop. Entered instead of ANY synthetic
2427
+ // continuation POST when the current run is background-dispatched.
2428
+ // Keeps following as long as an active run for this turn exists — the
2429
+ // server keeps chaining until its own per-turn budget or a real
2430
+ // terminal error, so a successor that stalls again is simply
2431
+ // re-followed, not counted against any client budget. Only a
2432
+ // continuous BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS with no active run
2433
+ // ends the turn from the client side.
2434
+ const followBackgroundTurn = async function* (
2435
+ initialSignal: AgentAutoContinueSignal,
2436
+ ): AsyncGenerator<
2437
+ ChatModelRunResult,
2438
+ "completed" | "client_continue",
2439
+ unknown
2440
+ > {
2441
+ lastAutoContinueReason = initialSignal.reason;
2442
+ if (initialSignal.activityTrail.length > 0) {
2443
+ lastActivityTrail = [...initialSignal.activityTrail];
2444
+ }
2445
+ if (initialSignal.errorInfo) {
2446
+ lastRecoverableRunError = initialSignal.errorInfo;
2447
+ }
2448
+ // Show "Resuming…" instead of a frozen Thinking label while waiting
2449
+ // for the server-chained successor chunk.
2450
+ const dispatchResumingUiEvent = () => {
2451
+ if (typeof window !== "undefined") {
2452
+ window.dispatchEvent(
2453
+ new CustomEvent("agent-chat:auto-continue", {
2454
+ detail: { tabId },
2455
+ }),
2456
+ );
2457
+ }
2458
+ };
2459
+ dispatchResumingUiEvent();
2460
+
2461
+ // Before surfacing a background terminal ERROR (never called for a
2462
+ // genuine "done" success — see call site), give the server's
2463
+ // reap-and-recover path a short extra window to insert a claimable
2464
+ // successor row for this turn. Returns as soon as `/runs/active`
2465
+ // reports a DIFFERENT runId than the stale one, so the caller can
2466
+ // hand back to the main loop and follow it seamlessly instead of
2467
+ // erroring out from under it.
2468
+ const awaitBackgroundErrorRecoverySuccessor = async (
2469
+ staleRunId: string,
2470
+ ): Promise<"successor" | "timeout" | "aborted"> => {
2471
+ for (
2472
+ let attempt = 0;
2473
+ attempt < BACKGROUND_TERMINAL_ERROR_GRACE_POLLS;
2474
+ attempt++
2475
+ ) {
2476
+ if (abortSignal.aborted) return "aborted";
2477
+ dispatchResumingUiEvent();
2478
+ await delay(BACKGROUND_FOLLOW_POLL_INTERVAL_MS, abortSignal);
2479
+ if (abortSignal.aborted) return "aborted";
2480
+ let recheck: Record<string, unknown> | null = null;
2481
+ try {
2482
+ const activeRes = await fetch(
2483
+ `${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId!)}`,
2484
+ { signal: abortSignal },
2485
+ );
2486
+ if (activeRes.ok) {
2487
+ recheck = await activeRes.json().catch(() => null);
2488
+ }
2489
+ } catch (pollErr: unknown) {
2490
+ if (pollErr instanceof Error && pollErr.name === "AbortError") {
2491
+ return "aborted";
2492
+ }
2493
+ // Transient poll failure — this attempt counts as "no
2494
+ // successor yet", not a hard failure of the grace window.
2495
+ }
2496
+ const recheckRunId =
2497
+ recheck?.active === true && recheck.runId
2498
+ ? String(recheck.runId)
2499
+ : null;
2500
+ if (recheckRunId && recheckRunId !== staleRunId) {
2501
+ return "successor";
2502
+ }
2503
+ }
2504
+ return "timeout";
2505
+ };
2506
+
2507
+ let idleSince: number | null = null;
2508
+ let lastSeenActive: Record<string, unknown> | null = null;
2509
+ // Terminal runs already replayed once. A recoverable terminal error
2510
+ // event replays as an auto-continue signal (isAutoRecoverableError),
2511
+ // which used to re-drive a POST in foreground mode. In follow mode
2512
+ // the server owns recovery, so a SECOND visit to the same terminal
2513
+ // run means the turn is over — surface the outcome instead of
2514
+ // re-replaying it every poll for the whole reconnect window. (A
2515
+ // CONTINUATION-class terminal_reason is the one exception: see the
2516
+ // isContinuationChunkBoundary check below, which keeps polling
2517
+ // instead of surfacing anything.)
2518
+ const replayedTerminalRunIds = new Set<string>();
2519
+
2520
+ while (true) {
2521
+ if (abortSignal.aborted) {
2522
+ clearActiveRun();
2523
+ return "completed";
2524
+ }
2525
+ let active: Record<string, unknown> | null = null;
2526
+ try {
2527
+ const activeRes = await fetch(
2528
+ `${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId!)}`,
2529
+ { signal: abortSignal },
2530
+ );
2531
+ if (activeRes.ok) {
2532
+ active = await activeRes.json().catch(() => null);
2533
+ }
2534
+ } catch (pollErr: unknown) {
2535
+ if (pollErr instanceof Error && pollErr.name === "AbortError") {
2536
+ clearActiveRun();
2537
+ return "completed";
2538
+ }
2539
+ // Transient poll failure — counts as "no active run" this tick.
2540
+ }
2541
+
2542
+ const activeRunId =
2543
+ active?.active === true && active.runId
2544
+ ? String(active.runId)
2545
+ : null;
2546
+ const activeTurnId =
2547
+ typeof active?.turnId === "string" ? active.turnId : "";
2548
+ const activeStatus =
2549
+ typeof active?.status === "string" ? active.status : "";
2550
+ // Only follow runs belonging to THIS turn (server-chained
2551
+ // successors reuse the turnId) or runs we already attached to.
2552
+ const isOurRun =
2553
+ activeRunId !== null && canAttachRun(activeRunId, activeTurnId);
2554
+
2555
+ if (activeRunId && isOurRun) {
2556
+ lastSeenActive = active;
2557
+ updateCurrentRunDispatchMode(active?.dispatchMode);
2558
+ const isTerminal =
2559
+ activeStatus === "completed" || activeStatus === "errored";
2560
+ if (isTerminal && replayedTerminalRunIds.has(activeRunId)) {
2561
+ if (
2562
+ lastAutoContinueReason === "stale_run" &&
2563
+ lastRecoverableRunError?.errorCode === "stale_run"
2564
+ ) {
2565
+ const continuation = prepareAutoContinuation(
2566
+ new AgentAutoContinueSignal({
2567
+ reason: "stale_run",
2568
+ errorInfo: lastRecoverableRunError,
2569
+ activityTrail: lastActivityTrail,
2570
+ }),
2571
+ );
2572
+ if (continuation.ok) {
2573
+ dispatchResumingUiEvent();
2574
+ await delay(250, abortSignal);
2575
+ return "client_continue";
2576
+ }
2577
+ }
2578
+ const rawTerminalReason =
2579
+ typeof active?.terminalReason === "string"
2580
+ ? active.terminalReason
2581
+ : "";
2582
+ const bareTerminalReason = rawTerminalReason.replace(
2583
+ /^error:/,
2584
+ "",
2585
+ );
2586
+ // A re-observed "completed" row with a CONTINUATION-class
2587
+ // reason (run_timeout/no_progress/loop_limit/…) is a MID-TURN
2588
+ // chunk boundary, not the end of the turn — a warm instance
2589
+ // can keep serving this stale row for a beat before the
2590
+ // pre-inserted successor becomes visible. Emitting a terminal
2591
+ // outcome here is exactly the bug this branch exists to
2592
+ // avoid: it would flip the message to "done" while the
2593
+ // successor's content is still coming. Keep polling on the
2594
+ // shared idle budget instead; a truly stuck handoff still
2595
+ // fails loud (never a silent stop) once it expires.
2596
+ const isContinuationChunkBoundary =
2597
+ activeStatus === "completed" &&
2598
+ !rawTerminalReason.startsWith("error:") &&
2599
+ !BACKGROUND_TERMINAL_REASON_MESSAGES[bareTerminalReason] &&
2600
+ BACKGROUND_CONTINUATION_TERMINAL_REASONS.has(
2601
+ bareTerminalReason,
2602
+ );
2603
+ if (isContinuationChunkBoundary) {
2604
+ if (idleSince === null) idleSince = Date.now();
2605
+ if (
2606
+ Date.now() - idleSince >=
2607
+ BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS
2608
+ ) {
2609
+ yield* emitBackgroundTerminalError({
2610
+ message:
2611
+ "The agent's background run stopped between chunks and no continuation appeared. You can retry from the preserved chat context.",
2612
+ errorCode: "background_run_lost",
2613
+ details: `terminal_reason: ${rawTerminalReason}`,
2614
+ });
2615
+ return "completed";
2616
+ }
2617
+ dispatchResumingUiEvent();
2618
+ await delay(BACKGROUND_FOLLOW_POLL_INTERVAL_MS, abortSignal);
2619
+ continue;
2620
+ }
2621
+ if (isBackgroundTerminalErrorOutcome(active)) {
2622
+ const graceOutcome =
2623
+ await awaitBackgroundErrorRecoverySuccessor(activeRunId);
2624
+ if (graceOutcome === "aborted") {
2625
+ clearActiveRun();
2626
+ return "completed";
2627
+ }
2628
+ if (graceOutcome === "successor") {
2629
+ // A successor claimed the turn while we waited — hand
2630
+ // back to the top of the loop so it attaches and follows
2631
+ // it exactly like any other newly-discovered run instead
2632
+ // of duplicating that bookkeeping here.
2633
+ continue;
2634
+ }
2635
+ }
2636
+ yield* emitBackgroundTerminalOutcome(active);
2637
+ return "completed";
2638
+ }
2639
+ const previousRunId = runId;
2640
+ runId = activeRunId;
2641
+ if (!attemptedRunIds.includes(activeRunId)) {
2642
+ attemptedRunIds.push(activeRunId);
2643
+ }
2644
+ reconnectCursorForRun(activeRunId, previousRunId);
2645
+ if (threadId) {
2646
+ setActiveRun({ threadId, runId: activeRunId, lastSeq });
2647
+ }
2648
+ const attach = yield* followAttachOnce();
2649
+ if (attach === "completed" || attach === "aborted") {
2650
+ return "completed";
2651
+ }
2652
+ if (isTerminal) {
2653
+ replayedTerminalRunIds.add(activeRunId);
2654
+ }
2655
+ // Server-authoritative "silently deferred, recovery in
2656
+ // progress" signal (see `awaitingRedispatch` on
2657
+ // `getActiveRunForThreadAsync`, run-manager.ts): a
2658
+ // `chainServerDrivenContinuation` successor still inside
2659
+ // `UNCLAIMED_BACKGROUND_RUN_REDISPATCH_BOUND_MS`, being
2660
+ // redispatched by the server's fast/slow sweeps. This is known
2661
+ // to demonstrably exist — same as a "detached" attach — so
2662
+ // treat it identically and never accumulate idle time against
2663
+ // it. Once the server's own bound is exceeded (or the row is
2664
+ // claimed/terminal) this flips false and normal idle accounting
2665
+ // resumes, so a truly stuck deferral still fails loud via the
2666
+ // existing timeout below.
2667
+ const awaitingRedispatch = active?.awaitingRedispatch === true;
2668
+ if (attach === "detached" || awaitingRedispatch) {
2669
+ // We really attached to a live stream (chunk boundary or
2670
+ // transport blip), or the server told us recovery is
2671
+ // in-flight — the run demonstrably exists; keep following
2672
+ // with a fresh idle window.
2673
+ idleSince = null;
2674
+ } else {
2675
+ // "gone": /runs/active reported the run but its event stream
2676
+ // 404s (reaped row / cross-isolate lag). Let the idle window
2677
+ // ACCUMULATE instead of resetting, so a persistently
2678
+ // inconsistent state still terminates loudly instead of
2679
+ // hot-looping for the whole reconnect window.
2680
+ if (idleSince === null) idleSince = Date.now();
2681
+ if (
2682
+ Date.now() - idleSince >=
2683
+ BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS
2684
+ ) {
2685
+ yield* emitBackgroundTerminalOutcome(lastSeenActive);
2686
+ return "completed";
2687
+ }
2688
+ }
2689
+ dispatchResumingUiEvent();
2690
+ } else {
2691
+ // No active run visible for this turn yet (the gap between a
2692
+ // chunk ending and the successor becoming pollable). Refresh
2693
+ // the "Resuming…" bridge on every tick here too — not just
2694
+ // when an active run IS visible — so the client's own
2695
+ // isAutoResuming window can't lapse into a false-idle UI while
2696
+ // this loop is still confidently waiting out its own budget.
2697
+ dispatchResumingUiEvent();
2698
+ if (idleSince === null) idleSince = Date.now();
2699
+ if (Date.now() - idleSince >= BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS) {
2700
+ captureChatClientError(
2701
+ new Error(
2702
+ "Background run went idle with no successor run appearing",
2703
+ ),
2704
+ "background-follow-idle",
2705
+ {
2706
+ lastSeenStatus:
2707
+ typeof lastSeenActive?.status === "string"
2708
+ ? lastSeenActive.status
2709
+ : null,
2710
+ },
2711
+ );
2712
+ yield* emitBackgroundTerminalOutcome(lastSeenActive);
2713
+ return "completed";
2714
+ }
2715
+ }
2716
+ await delay(BACKGROUND_FOLLOW_POLL_INTERVAL_MS, abortSignal);
2717
+ if (abortSignal.aborted) {
2718
+ clearActiveRun();
2719
+ return "completed";
2720
+ }
2721
+ }
2722
+ };
2723
+
2724
+ const visibleContentForContinuation = (): ContentPart[] => {
2725
+ return contentAfterContinuationPrefix(
2726
+ content,
2727
+ visibleContinuationPrefix,
2728
+ );
2729
+ };
2730
+
2731
+ const prepareAutoContinuation = (
2732
+ signal: AgentAutoContinueSignal,
2733
+ ): {
2734
+ ok: boolean;
2735
+ resetVisibleContent: boolean;
2736
+ completedToolName?: string;
2737
+ } => {
2738
+ lastAutoContinueReason = signal.reason;
2739
+ lastActivityTrail = [...signal.activityTrail];
2740
+ if (signal.errorInfo) {
2741
+ lastRecoverableRunError = signal.errorInfo;
2742
+ }
2743
+ const isTransient = signal.reason !== "loop_limit";
2744
+ const visibleContent = visibleContentForContinuation();
2745
+ let currentPartialHistory =
2746
+ contentToContinuationHistory(visibleContent);
2747
+ // Real, content-weight progress: streamed text or a completed tool
2748
+ // result. Used to reset the stalled/empty counters so trivial
2749
+ // whitespace-only output cannot keep the run alive indefinitely.
2750
+ const madeContentProgress = hasContinuationProgress(visibleContent);
2751
+ // An action was streamed but has not returned yet (a tool_start with
2752
+ // no tool_done). This is durable enough to survive continuation: the
2753
+ // server already emitted a real tool call. A tool-scoped activity
2754
+ // card ("Preparing generate-design") is useful UI, but it happens
2755
+ // before tool_start; treating it as progress caused silent retry
2756
+ // loops when the LLM timed out while assembling a large tool input.
2757
+ const hasInFlightTool = hasInFlightToolCall(visibleContent);
2758
+ const completedTool = lastCompletedTimeoutCandidateTool(content);
2759
+ // Either real output or an actively-running tool counts as progress
2760
+ // for the stalled/empty caps.
2761
+ const madeProgress = madeContentProgress || hasInFlightTool;
2762
+ const madeDurableToolProgress = visibleContent.some(
2763
+ (part) =>
2764
+ part.type === "tool-call" &&
2765
+ part.activity !== true &&
2766
+ part.result !== undefined,
2767
+ );
2768
+ const currentPreparingToolName =
2769
+ lastUnresolvedToolActivity(visibleContent) ??
2770
+ lastPreparingActionTool(signal.activityTrail);
2771
+ // In-flight tool stall guard. When the same write tool is stuck
2772
+ // in-flight because the connection keeps dropping (stream_ended),
2773
+ // hasInFlightTool=true keeps madeProgress=true and completely
2774
+ // bypasses the stalled/empty budgets. Track the last in-flight tool
2775
+ // name; when the same tool is still unresolved after
2776
+ // MAX_REPEATED_INFLIGHT_TOOL_STALLS consecutive stream_ended events,
2777
+ // bail with a clear message.
2778
+ //
2779
+ // Count broken streams (connection drop / reconnect failed) and
2780
+ // no_progress stalls (the client aborts a stream that stayed open but
2781
+ // stopped producing events). Do NOT count run_timeout: with an
2782
+ // in-flight tool that means the server is still actively executing a
2783
+ // slow action and reconnection may recover the result.
2784
+ const currentInFlightToolPart = visibleContent.find(
2785
+ (p): p is Extract<ContentPart, { type: "tool-call" }> =>
2786
+ p.type === "tool-call" &&
2787
+ p.result === undefined &&
2788
+ p.activity !== true,
2789
+ );
2790
+ const currentInFlightToolName = currentInFlightToolPart?.toolName;
2791
+ const currentInFlightToolSignature = currentInFlightToolPart
2792
+ ? inFlightToolInputSignature(currentInFlightToolPart)
2793
+ : undefined;
2794
+ const isBrokenInFlightTool =
2795
+ signal.reason === "stream_ended" || signal.reason === "no_progress";
2796
+ if (currentInFlightToolName && isBrokenInFlightTool) {
2797
+ if (
2798
+ currentInFlightToolName === lastInFlightToolName &&
2799
+ currentInFlightToolSignature === lastInFlightToolSignature
2800
+ ) {
2801
+ repeatedInFlightToolCount += 1;
2802
+ } else {
2803
+ // New tool, or the same tool retried with a CHANGED (e.g.
2804
+ // smaller) payload — give the changed input a fresh stall budget
2805
+ // instead of aborting it as a repeat of the prior payload.
2806
+ repeatedInFlightToolCount = 0;
2807
+ lastInFlightToolName = currentInFlightToolName;
2808
+ lastInFlightToolSignature = currentInFlightToolSignature;
2809
+ }
2810
+ } else if (!currentInFlightToolName) {
2811
+ repeatedInFlightToolCount = 0;
2812
+ }
2813
+
2814
+ const isRepeatedActionPreparationCandidate =
2815
+ signal.reason !== "loop_limit" &&
2816
+ currentPreparingToolName !== undefined &&
2817
+ !hasInFlightTool &&
2818
+ !madeDurableToolProgress;
2819
+ if (isRepeatedActionPreparationCandidate) {
2820
+ if (currentPreparingToolName === lastPreparingToolName) {
2821
+ repeatedActionPreparationCount += 1;
2822
+ } else {
2823
+ repeatedActionPreparationCount = 0;
2824
+ lastPreparingToolName = currentPreparingToolName;
2825
+ }
2826
+ } else if (
2827
+ !currentPreparingToolName ||
2828
+ hasInFlightTool ||
2829
+ madeDurableToolProgress
2830
+ ) {
2831
+ repeatedActionPreparationCount = 0;
2832
+ lastPreparingToolName = undefined;
2833
+ }
2834
+
2835
+ // Degenerate repetition guard. When the model gets stuck re-streaming
2836
+ // the SAME narration every continuation without ever starting or
2837
+ // finishing a tool, each round is "new" text — so madeProgress stays
2838
+ // true and the stalled/empty budgets never trip. Compare this round's
2839
+ // unique-sentence signature to the previous round's; a match with no
2840
+ // tool progress is a non-advancing loop. Tracked by its own counter
2841
+ // so it bails after a few rounds instead of the full transient budget,
2842
+ // without perturbing the stalled/empty/stale accounting.
2843
+ const repeatSignature = continuationRepeatSignature(visibleContent);
2844
+ const isNonAdvancingRepeat =
2845
+ signal.reason !== "loop_limit" &&
2846
+ repeatSignature !== "" &&
2847
+ repeatSignature === lastContinuationRepeatSignature &&
2848
+ !hasInFlightTool &&
2849
+ !madeDurableToolProgress;
2850
+
2851
+ if (signal.reason === "loop_limit") {
2852
+ stalledTransientContinuationAttempts = 0;
2853
+ emptyTransientContinuationAttempts = 0;
2854
+ } else {
2855
+ totalTransientContinuationAttempts += 1;
2856
+ // If a tool already completed, do not turn a missing closing
2857
+ // sentence into a scary connection failure. Give the model one
2858
+ // continuation opportunity (the completed tool itself counts as
2859
+ // progress on the first timeout); if the follow-up produces no new
2860
+ // content, stop locally with a clear completed-tool warning.
2861
+ if (
2862
+ signal.reason === "run_timeout" &&
2863
+ completedTool &&
2864
+ !hasInFlightToolCall(content) &&
2865
+ !currentPreparingToolName &&
2866
+ !madeContentProgress &&
2867
+ !hasInFlightTool
2868
+ ) {
2869
+ return {
2870
+ ok: false,
2871
+ resetVisibleContent: false,
2872
+ completedToolName: completedTool.toolName,
2873
+ };
2874
+ }
2875
+ // Bail when the same write tool is stuck in-flight across too many
2876
+ // consecutive continuations. Checked before the text-repeat guard
2877
+ // because hasInFlightTool=true would mask the repeat as progress.
2878
+ if (
2879
+ repeatedInFlightToolCount >= MAX_REPEATED_INFLIGHT_TOOL_STALLS
2880
+ ) {
2881
+ recoveryGaveUpOnInFlightTool = true;
2882
+ return { ok: false, resetVisibleContent: false };
2883
+ }
2884
+ if (
2885
+ repeatedActionPreparationCount >
2886
+ MAX_REPEATED_ACTION_PREPARATION_CONTINUATIONS
2887
+ ) {
2888
+ recoveryGaveUpOnActionPreparation = true;
2889
+ return { ok: false, resetVisibleContent: false };
2890
+ }
2891
+ // Bail fast on a non-advancing repetition loop, well before the
2892
+ // stalled/empty/total budgets would (each round otherwise re-sends
2893
+ // the whole pasted payload). Tracked separately so it never trips
2894
+ // on legitimately-progressing runs that happen to be slow.
2895
+ if (isNonAdvancingRepeat) {
2896
+ repeatedTransientContinuationAttempts += 1;
2897
+ if (
2898
+ repeatedTransientContinuationAttempts >
2899
+ MAX_REPEATED_TRANSIENT_CONTINUATIONS
2900
+ ) {
2901
+ recoveryGaveUpOnRepetition = true;
2902
+ return { ok: false, resetVisibleContent: false };
2903
+ }
2904
+ } else {
2905
+ repeatedTransientContinuationAttempts = 0;
2906
+ }
2907
+ if (repeatSignature) {
2908
+ lastContinuationRepeatSignature = repeatSignature;
2909
+ }
2910
+ // A run_timeout that produced nothing visible and no activity (the
2911
+ // model spent the whole soft-timeout window thinking before its
2912
+ // first output) is NOT an immediate give-up: a transient slow start
2913
+ // routinely recovers on the next continuation. Let it fall through
2914
+ // to the empty-continuation budget below so it retries a bounded
2915
+ // number of times before surfacing the "no visible progress" error.
2916
+ // Reset the empty-continuation counter on real progress — streamed
2917
+ // text/completed tool OR an in-flight tool the server is running —
2918
+ // not merely on a non-zero part count, which whitespace-only or
2919
+ // unresolved-only output would falsely satisfy.
2920
+ if (!madeProgress) {
2921
+ emptyTransientContinuationAttempts += 1;
2922
+ if (
2923
+ emptyTransientContinuationAttempts >
2924
+ MAX_EMPTY_TRANSIENT_CONTINUATIONS
2925
+ ) {
2926
+ return { ok: false, resetVisibleContent: false };
2927
+ }
2928
+ } else {
2929
+ emptyTransientContinuationAttempts = 0;
2930
+ }
2931
+ if (signal.reason === "stale_run") {
2932
+ staleRunContinuationAttempts = madeDurableToolProgress
2933
+ ? 0
2934
+ : staleRunContinuationAttempts + 1;
2935
+ if (staleRunContinuationAttempts > MAX_STALE_RUN_CONTINUATIONS) {
2936
+ return { ok: false, resetVisibleContent: false };
2937
+ }
2938
+ }
2939
+ stalledTransientContinuationAttempts = madeProgress
2940
+ ? 0
2941
+ : stalledTransientContinuationAttempts + 1;
2942
+ if (
2943
+ stalledTransientContinuationAttempts >
2944
+ MAX_STALLED_TRANSIENT_CONTINUATIONS ||
2945
+ totalTransientContinuationAttempts >
2946
+ MAX_TOTAL_TRANSIENT_CONTINUATIONS
2947
+ ) {
2948
+ return { ok: false, resetVisibleContent: false };
2949
+ }
2950
+ }
2951
+
2952
+ if (isTransient) {
2953
+ const settledInterruptedTools = settleInterruptedToolCalls(
2954
+ visibleContent,
2955
+ undefined,
2956
+ { includeActivity: true },
2957
+ );
2958
+ if (settledInterruptedTools) {
2959
+ currentPartialHistory =
2960
+ contentToContinuationHistory(visibleContent);
2961
+ }
2962
+ }
2963
+
2964
+ if (isTransient && currentPartialHistory) {
2965
+ continuationHistoryFragments.push(currentPartialHistory);
2966
+ }
2967
+ const partialHistory = combineContinuationHistory(
2968
+ isTransient
2969
+ ? continuationHistoryFragments
2970
+ : [...continuationHistoryFragments, currentPartialHistory],
2971
+ );
2972
+ const structuredPartialHistory = contentToStructuredMessages(
2973
+ visibleContent,
2974
+ nextContinuationToolCallId,
2975
+ );
2976
+ if (isTransient && structuredPartialHistory.length > 0) {
2977
+ structuredContinuationFragments.push(...structuredPartialHistory);
2978
+ }
2979
+ const structuredCombinedHistory = isTransient
2980
+ ? structuredContinuationFragments
2981
+ : [...structuredContinuationFragments, ...structuredPartialHistory];
2982
+ currentHistory = [
2983
+ ...history,
2984
+ { role: "user", content: normalizeMentions(userMessageText) },
2985
+ ...(partialHistory
2986
+ ? [{ role: "assistant" as const, content: partialHistory }]
2987
+ : []),
2988
+ ];
2989
+ currentStructuredHistory = [
2990
+ ...structuredHistory,
2991
+ {
2992
+ role: "user",
2993
+ content: [
2994
+ { type: "text", text: normalizeMentions(userMessageText) },
2995
+ ],
2996
+ },
2997
+ ...structuredCombinedHistory,
2998
+ ];
2999
+ currentMessageText = autoContinueMessage(signal);
3000
+ // Continuation requests are stateless new POSTs. If the interrupted
3001
+ // turn depended on uploaded context, re-send that context; otherwise
3002
+ // an attachment-only prompt degrades to "Use the attached context."
3003
+ // with nothing attached after a stale run or reconnect recovery.
3004
+ includeAttachments = attachments.length > 0;
3005
+ includeReferences = Boolean(runConfig?.custom?.references);
3006
+ internalContinuationRequest = true;
3007
+ startupRecoveryAttempts = 0;
3008
+ clearActiveRun();
3009
+ if (!isTransient) {
3010
+ return { ok: true, resetVisibleContent: false };
3011
+ }
3012
+
3013
+ // Keep everything visible during transient recovery. The continuation
3014
+ // prefix diff tracks what the next request has already seen, so
3015
+ // preserving text no longer causes duplicate continuation history.
3016
+ visibleContinuationPrefix = snapshotContent(content);
3017
+ return { ok: true, resetVisibleContent: false };
3018
+ };
3019
+
3020
+ while (true) {
3021
+ try {
3022
+ runId = null;
3023
+ lastSeq = -1;
3024
+ const res = await fetchWithStartupTimeout(
3025
+ apiUrl,
3026
+ {
3027
+ method: "POST",
3028
+ headers,
3029
+ body: JSON.stringify({
3030
+ message: currentMessageText,
3031
+ displayMessage: userMessageText,
3032
+ history: currentHistory,
3033
+ structuredHistory: currentStructuredHistory,
3034
+ turnId,
3035
+ ...(trackInRunsTray ? { trackInRunsTray: true } : {}),
3036
+ ...(threadId ? { threadId } : {}),
3037
+ ...(internalContinuationRequest
3038
+ ? { internalContinuation: true }
3039
+ : {}),
3040
+ ...(requestMode ? { mode: requestMode } : {}),
3041
+ ...(modelRef?.current ? { model: modelRef.current } : {}),
3042
+ ...(engineRef?.current ? { engine: engineRef.current } : {}),
3043
+ ...(effortRef?.current ? { effort: effortRef.current } : {}),
3044
+ ...(browserTabId ? { browserTabId } : {}),
3045
+ ...(scopeRef?.current ? { scope: scopeRef.current } : {}),
3046
+ ...(includeAttachments ? { attachments } : {}),
3047
+ ...(includeReferences && runConfig?.custom?.references
3048
+ ? { references: runConfig.custom.references }
3049
+ : {}),
3050
+ ...(approvedToolCalls ? { approvedToolCalls } : {}),
3051
+ }),
3052
+ },
3053
+ STARTUP_RESPONSE_TIMEOUT_MS,
3054
+ abortSignal,
3055
+ );
3056
+
3057
+ // Check for auth errors returned as 200 with JSON (common with middleware issues)
3058
+ const contentType = res.headers.get("content-type") || "";
3059
+ if (
3060
+ res.ok &&
3061
+ contentType.includes("application/json") &&
3062
+ !contentType.includes("text/event-stream")
3063
+ ) {
3064
+ try {
3065
+ const body = await res.text();
3066
+ const parsed = JSON.parse(body);
3067
+ if (parsed.error) {
3068
+ throw new Error(parsed.error);
3069
+ }
3070
+ } catch (e) {
3071
+ if (
3072
+ e instanceof Error &&
3073
+ e.message !== "Unexpected end of JSON input"
3074
+ ) {
3075
+ throw e;
3076
+ }
3077
+ }
3078
+ }
3079
+
3080
+ if (!res.ok) {
3081
+ if (res.status === 409) {
3082
+ let activeRunId: string | null = null;
3083
+ try {
3084
+ const body = await res.json();
3085
+ if (body?.activeRunId) {
3086
+ activeRunId = String(body.activeRunId);
3087
+ }
3088
+ } catch {
3089
+ // Fall through to the generic response handling below.
3090
+ }
3091
+ // A 409 means the server still has an active run for this
3092
+ // thread. For a fresh user turn — a queued follow-up OR a normal
3093
+ // send fired shortly after the previous run finished (the server
3094
+ // can report a just-finished run as active for up to
3095
+ // RUN_STALE_MS while its terminal status write lands) — adopting
3096
+ // `activeRunId` below would reconnect to that prior run, replay
3097
+ // its final answer, and silently drop this turn. The same race
3098
+ // can happen after an internal auto-continue: if the reported
3099
+ // active run is one this adapter already consumed, reconnecting
3100
+ // to it replays the terminal auto_continue and exits instead of
3101
+ // posting the continuation. Wait for stale/previous active runs
3102
+ // to clear and retry THIS prompt. An unknown run reported to an
3103
+ // internal continuation is only adoptable after /runs/active
3104
+ // proves it carries this turnId; a bare 409 run id is not
3105
+ // enough ownership evidence.
3106
+ if (
3107
+ activeRunId !== null &&
3108
+ internalContinuationRequest &&
3109
+ !attemptedRunIds.includes(activeRunId)
3110
+ ) {
3111
+ const reconnected = yield* reconnectActiveRunForThread();
3112
+ if (reconnected) return;
3113
+ }
3114
+ const shouldRetryConflictingActiveRun = activeRunId !== null;
3115
+ if (shouldRetryConflictingActiveRun) {
3116
+ queuedConflictRetries += 1;
3117
+ if (queuedConflictRetries <= MAX_QUEUED_CONFLICT_RETRIES) {
3118
+ await delay(500, abortSignal);
3119
+ if (abortSignal.aborted) return;
3120
+ continue;
3121
+ }
3122
+ const message =
3123
+ "The previous response is still finishing and the new message could not start yet. Please try again.";
3124
+ const runError = {
3125
+ message,
3126
+ details: `The server kept reporting active run ${activeRunId} for this thread after ${MAX_QUEUED_CONFLICT_RETRIES} retries.`,
3127
+ errorCode: "active_run_conflict",
3128
+ recoverable: true,
3129
+ runId: activeRunId,
3130
+ };
3131
+ // This is a terminal outcome for the attempted turn. Settle
3132
+ // both real in-flight calls and activity-only placeholders
3133
+ // before yielding the error; otherwise a prior "Preparing
3134
+ // run-code" event remains an unresolved tool card and the UI
3135
+ // keeps showing a spinner beside an already-terminal error.
3136
+ settleInterruptedToolCalls(content, undefined, {
3137
+ includeActivity: true,
3138
+ });
3139
+ if (typeof window !== "undefined") {
3140
+ window.dispatchEvent(
3141
+ new CustomEvent("agent-chat:activity-clear", {
3142
+ detail: { tabId },
3143
+ }),
3144
+ );
3145
+ window.dispatchEvent(
3146
+ new CustomEvent("agent-chat:run-error", {
3147
+ detail: { ...runError, tabId },
3148
+ }),
3149
+ );
3150
+ }
3151
+ content.push({
3152
+ type: "text",
3153
+ text: `Something went wrong: ${message}`,
3154
+ });
3155
+ yield {
3156
+ content: [...content],
3157
+ status: {
3158
+ type: "incomplete" as const,
3159
+ reason: "error" as const,
3160
+ },
3161
+ metadata: { custom: { runError } },
3162
+ } as ChatModelRunResult;
3163
+ clearActiveRun();
3164
+ return;
3165
+ }
3166
+ }
3167
+
3168
+ if (res.status === 401 || res.status === 403) {
3169
+ if (await tryRecoverAuthOnce()) {
3170
+ continue;
3171
+ }
3172
+ dispatchAuthError("auth-required");
3173
+ content.push({
3174
+ type: "text",
3175
+ text: authErrorText("auth-required"),
3176
+ });
3177
+ yield {
3178
+ content: [...content],
3179
+ status: {
3180
+ type: "incomplete" as const,
3181
+ reason: "error" as const,
3182
+ },
3183
+ } as ChatModelRunResult;
3184
+ return;
3185
+ }
3186
+
3187
+ // 405 Method Not Allowed usually means the session is broken/expired
3188
+ // (e.g. a redirect to a login page that only accepts GET).
3189
+ if (res.status === 405) {
3190
+ if (await tryRecoverAuthOnce()) {
3191
+ continue;
3192
+ }
3193
+ dispatchAuthError("session-expired");
3194
+ content.push({
3195
+ type: "text",
3196
+ text: authErrorText("session-expired"),
3197
+ });
3198
+ yield {
3199
+ content: [...content],
3200
+ status: {
3201
+ type: "incomplete" as const,
3202
+ reason: "error" as const,
3203
+ },
3204
+ } as ChatModelRunResult;
3205
+ return;
3206
+ }
3207
+
3208
+ let errorText = `Server error: ${res.status}`;
3209
+ try {
3210
+ const body = await res.text();
3211
+ if (isAuthErrorMessage(body)) {
3212
+ if (await tryRecoverAuthOnce()) {
3213
+ continue;
3214
+ }
3215
+ const reason = authErrorReasonFromMessage(body);
3216
+ dispatchAuthError(reason);
3217
+ content.push({
3218
+ type: "text",
3219
+ text: authErrorText(reason, body),
3220
+ });
3221
+ yield {
3222
+ content: [...content],
3223
+ status: {
3224
+ type: "incomplete" as const,
3225
+ reason: "error" as const,
3226
+ },
3227
+ } as ChatModelRunResult;
3228
+ return;
3229
+ }
3230
+ if (isMissingCredentialMessage(body)) {
3231
+ const failure = missingCredentialFailure(body);
3232
+ if (typeof window !== "undefined") {
3233
+ dispatchMissingApiKey();
3234
+ window.dispatchEvent(
3235
+ new CustomEvent("agent-chat:run-error", {
3236
+ detail: { ...failure.runError, tabId },
3237
+ }),
3238
+ );
3239
+ }
3240
+ content.push({ type: "text", text: failure.text });
3241
+ yield {
3242
+ content: [...content],
3243
+ status: {
3244
+ type: "incomplete" as const,
3245
+ reason: "error" as const,
3246
+ },
3247
+ metadata: { custom: { runError: failure.runError } },
3248
+ } as ChatModelRunResult;
3249
+ return;
3250
+ } else if (body.includes("Cannot find any path")) {
3251
+ errorText =
3252
+ "Agent chat endpoint not found. Make sure the agent-chat plugin is loaded in server/plugins/.";
3253
+ } else if (body) {
3254
+ errorText =
3255
+ body.length > 200 ? body.slice(0, 200) + "..." : body;
3256
+ }
3257
+ } catch {}
3258
+ throw new Error(errorText);
3259
+ }
3260
+ if (!res.body) {
3261
+ throw new Error("No response body");
3262
+ }
3263
+
3264
+ // Track the run ID for reconnection
3265
+ runId = res.headers.get("X-Run-Id");
3266
+ updateCurrentRunDispatchMode(res.headers.get("X-Dispatch-Mode"));
3267
+ if (runId && !attemptedRunIds.includes(runId)) {
3268
+ attemptedRunIds.push(runId);
3269
+ }
3270
+ if (runId && threadId) {
3271
+ clearPendingTurnIfMatches(threadId, turnId);
3272
+ setActiveRun({ threadId, runId, lastSeq: -1 });
3273
+ }
3274
+
3275
+ for await (const result of readSSEStream(
3276
+ res.body,
3277
+ content,
3278
+ toolCallCounter,
3279
+ tabId,
3280
+ (seq) => {
3281
+ rememberRunSeq(seq);
3282
+ if (runId && threadId) {
3283
+ updateActiveRunSeq(seq);
3284
+ }
3285
+ },
3286
+ runId,
3287
+ currentSSEOptions(),
3288
+ )) {
3289
+ yield withRequestModeMetadata(result);
3290
+ }
3291
+
3292
+ // Run completed normally — clear active run state
3293
+ clearActiveRun();
3294
+ return;
3295
+ } catch (err: unknown) {
3296
+ if (err instanceof Error && err.name === "AbortError") {
3297
+ // User-initiated abort (Stop button) — clear active run
3298
+ clearActiveRun();
3299
+ return;
3300
+ }
3301
+
3302
+ if (err instanceof AgentAutoContinueSignal) {
3303
+ // Background-dispatched runs: the server chains continuations
3304
+ // itself (successor row pre-inserted before the old chunk
3305
+ // completes). Never POST a synthetic continuation and never
3306
+ // abort the live server-side run — switch to read-only
3307
+ // following of server state instead. This is the fix for the
3308
+ // client/server recovery race: client watchdog signals here are
3309
+ // just "reattach", not "recover".
3310
+ if (shouldFollowServerContinuation(err) && threadId) {
3311
+ const followOutcome = yield* followBackgroundTurn(err);
3312
+ if (followOutcome === "client_continue") {
3313
+ continue;
3314
+ }
3315
+ return;
3316
+ }
3317
+ if (err.reason === "no_progress") {
3318
+ await abortCurrentRun();
3319
+ }
3320
+ if (err.reason === "run_timeout" && !err.errorInfo) {
3321
+ const reconnected =
3322
+ yield* reconnectBackgroundContinuationForRunTimeout();
3323
+ if (reconnected) return;
3324
+ }
3325
+ if (err.reason === "stream_ended") {
3326
+ const reconnected = yield* reconnectCurrentRun();
3327
+ if (reconnected) return;
3328
+ const activeReconnected = yield* reconnectActiveRunForThread();
3329
+ if (activeReconnected) return;
3330
+ }
3331
+ const continuation = prepareAutoContinuation(err);
3332
+ if (!continuation.ok) {
3333
+ if (continuation.completedToolName) {
3334
+ const message = completedToolTimeoutMessage(
3335
+ continuation.completedToolName,
3336
+ );
3337
+ content.push({ type: "text", text: message });
3338
+ yield {
3339
+ content: [...content],
3340
+ status: {
3341
+ type: "complete" as const,
3342
+ reason: "stop" as const,
3343
+ },
3344
+ metadata: {
3345
+ custom: {
3346
+ ...(runId ? { runId } : {}),
3347
+ runWarning: {
3348
+ message,
3349
+ errorCode: "final_response_timeout_after_tool",
3350
+ recoverable: true,
3351
+ },
3352
+ },
3353
+ },
3354
+ };
3355
+ clearActiveRun();
3356
+ return;
3357
+ }
3358
+ const preservedError =
3359
+ err.errorInfo ?? lastRecoverableRunError ?? null;
3360
+ const message =
3361
+ preservedError?.message ??
3362
+ exhaustedRecoveryMessage(err.reason);
3363
+ const details = [
3364
+ preservedError?.details,
3365
+ connectionRecoveryDetails(),
3366
+ ]
3367
+ .filter(Boolean)
3368
+ .join("\n\n");
3369
+ const errorCode =
3370
+ preservedError?.errorCode ?? "connection_error";
3371
+ captureChatClientError(err, "auto-continuation-exhausted", {
3372
+ autoContinueReason: err.reason,
3373
+ ...(errorCode ? { errorCode } : {}),
3374
+ });
3375
+ const runError = {
3376
+ message,
3377
+ ...(details ? { details } : {}),
3378
+ errorCode,
3379
+ recoverable: preservedError?.recoverable ?? true,
3380
+ ...(runId ? { runId } : {}),
3381
+ };
3382
+ if (typeof window !== "undefined") {
3383
+ window.dispatchEvent(
3384
+ new CustomEvent("agent-chat:run-error", {
3385
+ detail: { ...runError, tabId },
3386
+ }),
3387
+ );
3388
+ }
3389
+ settleInterruptedToolCalls(content, undefined, {
3390
+ includeActivity: true,
3391
+ });
3392
+ content.push({
3393
+ type: "text",
3394
+ text: formatChatErrorText(
3395
+ message,
3396
+ preservedError?.upgradeUrl,
3397
+ // `message` is already user-facing here: either a
3398
+ // recovery-specific explanation from exhaustedRecoveryMessage
3399
+ // or a normalized gateway message from errorInfo. Passing
3400
+ // the fallback connection_error code back through the
3401
+ // formatter would replace the useful recovery guidance
3402
+ // with the generic interruption copy.
3403
+ preservedError ? errorCode : undefined,
3404
+ ),
3405
+ });
3406
+ yield {
3407
+ content: [...content],
3408
+ status: {
3409
+ type: "incomplete" as const,
3410
+ reason: "error" as const,
3411
+ },
3412
+ metadata: {
3413
+ custom: { ...(runId ? { runId } : {}), runError },
3414
+ },
3415
+ };
3416
+ clearActiveRun();
3417
+ return;
3418
+ }
3419
+ if (continuation.resetVisibleContent) {
3420
+ yield {
3421
+ content: snapshotContent(content),
3422
+ } as ChatModelRunResult;
3423
+ }
3424
+ // Signal to the UI that we are in the continuation window so it
3425
+ // can display "Resuming…" instead of "Thinking" during the gap.
3426
+ if (typeof window !== "undefined") {
3427
+ window.dispatchEvent(
3428
+ new CustomEvent("agent-chat:auto-continue", {
3429
+ detail: { tabId },
3430
+ }),
3431
+ );
3432
+ }
3433
+ await delay(250, abortSignal);
3434
+ if (abortSignal.aborted) return;
3435
+ continue;
3436
+ }
3437
+
3438
+ const errMsg =
3439
+ err instanceof Error ? err.message : "Something went wrong.";
3440
+ const isAuthError = isAuthErrorMessage(errMsg);
3441
+
3442
+ // Don't try to reconnect for auth/client errors — show error directly
3443
+ if (isAuthError) {
3444
+ if (await tryRecoverAuthOnce()) {
3445
+ continue;
3446
+ }
3447
+ const reason = authErrorReasonFromMessage(errMsg);
3448
+ dispatchAuthError(reason);
3449
+ content.push({
3450
+ type: "text",
3451
+ text: authErrorText(reason, errMsg),
3452
+ });
3453
+ yield {
3454
+ content: [...content],
3455
+ status: {
3456
+ type: "incomplete" as const,
3457
+ reason: "error" as const,
3458
+ },
3459
+ };
3460
+ clearActiveRun();
3461
+ return;
3462
+ }
3463
+
3464
+ if (isMissingCredentialMessage(errMsg)) {
3465
+ const failure = missingCredentialFailure(errMsg);
3466
+ if (typeof window !== "undefined") {
3467
+ dispatchMissingApiKey();
3468
+ window.dispatchEvent(
3469
+ new CustomEvent("agent-chat:run-error", {
3470
+ detail: { ...failure.runError, tabId },
3471
+ }),
3472
+ );
3473
+ }
3474
+ content.push({ type: "text", text: failure.text });
3475
+ yield {
3476
+ content: [...content],
3477
+ status: {
3478
+ type: "incomplete" as const,
3479
+ reason: "error" as const,
3480
+ },
3481
+ metadata: { custom: { runError: failure.runError } },
3482
+ };
3483
+ clearActiveRun();
3484
+ return;
3485
+ }
3486
+
3487
+ // Connection lost — try to reconnect to the run
3488
+ const reconnected = yield* reconnectCurrentRun();
3489
+ if (reconnected) return;
3490
+ const activeReconnected = yield* reconnectActiveRunForThread();
3491
+ if (activeReconnected) return;
3492
+
3493
+ // Background-dispatched run whose transport failed and could not
3494
+ // be reconnected above: follow server state instead of the
3495
+ // synthetic-continuation POST below. (An initial POST that failed
3496
+ // before any response headers arrived has no dispatch mode yet
3497
+ // and keeps the foreground startup-retry path.)
3498
+ if (shouldFollowServerContinuation() && threadId) {
3499
+ const followOutcome = yield* followBackgroundTurn(
3500
+ new AgentAutoContinueSignal({ reason: "stream_ended" }),
3501
+ );
3502
+ if (followOutcome === "client_continue") {
3503
+ continue;
3504
+ }
3505
+ return;
3506
+ }
3507
+
3508
+ if (err instanceof AgentStartupTimeoutError) {
3509
+ if (startupRecoveryAttempts < MAX_STARTUP_RECOVERY_ATTEMPTS) {
3510
+ await retryDelay(startupRecoveryAttempts++, abortSignal);
3511
+ if (abortSignal.aborted) return;
3512
+ continue;
3513
+ }
3514
+ const message =
3515
+ "The agent chat endpoint did not start streaming in time after several recovery attempts. This usually means prompt setup, the LLM gateway, or the provider is stalled.";
3516
+ captureChatClientError(err, "startup-timeout", {
3517
+ timeoutMs: err.timeoutMs,
3518
+ startupRecoveryAttempts,
3519
+ });
3520
+ const runError = {
3521
+ message,
3522
+ details: connectionRecoveryDetails(),
3523
+ errorCode: "startup_timeout",
3524
+ recoverable: true,
3525
+ ...(runId ? { runId } : {}),
3526
+ };
3527
+ if (typeof window !== "undefined") {
3528
+ window.dispatchEvent(
3529
+ new CustomEvent("agent-chat:run-error", {
3530
+ detail: { ...runError, tabId },
3531
+ }),
3532
+ );
3533
+ }
3534
+ content.push({
3535
+ type: "text",
3536
+ text: `Something went wrong: ${message}`,
3537
+ });
3538
+ yield {
3539
+ content: [...content],
3540
+ status: {
3541
+ type: "incomplete" as const,
3542
+ reason: "error" as const,
3543
+ },
3544
+ metadata: { custom: { ...(runId ? { runId } : {}), runError } },
3545
+ };
3546
+ clearActiveRun();
3547
+ return;
3548
+ }
3549
+
3550
+ // Reconnect failed or not possible — keep going from the partial
3551
+ // streamed content instead of surfacing a transient transport error.
3552
+ if (content.length > 0) {
3553
+ const continuation = prepareAutoContinuation(
3554
+ new AgentAutoContinueSignal({ reason: "stream_ended" }),
3555
+ );
3556
+ if (!continuation.ok) {
3557
+ if (continuation.completedToolName) {
3558
+ const message = completedToolTimeoutMessage(
3559
+ continuation.completedToolName,
3560
+ );
3561
+ content.push({ type: "text", text: message });
3562
+ yield {
3563
+ content: [...content],
3564
+ status: {
3565
+ type: "complete" as const,
3566
+ reason: "stop" as const,
3567
+ },
3568
+ metadata: {
3569
+ custom: {
3570
+ ...(runId ? { runId } : {}),
3571
+ runWarning: {
3572
+ message,
3573
+ errorCode: "final_response_timeout_after_tool",
3574
+ recoverable: true,
3575
+ },
3576
+ },
3577
+ },
3578
+ };
3579
+ clearActiveRun();
3580
+ return;
3581
+ }
3582
+ const message = exhaustedRecoveryMessage("stream_ended");
3583
+ captureChatClientError(err, "recovery-exhausted");
3584
+ const runError = {
3585
+ message,
3586
+ details: connectionRecoveryDetails(),
3587
+ errorCode: "connection_error",
3588
+ recoverable: true,
3589
+ ...(runId ? { runId } : {}),
3590
+ };
3591
+ if (typeof window !== "undefined") {
3592
+ window.dispatchEvent(
3593
+ new CustomEvent("agent-chat:run-error", {
3594
+ detail: { ...runError, tabId },
3595
+ }),
3596
+ );
3597
+ }
3598
+ settleInterruptedToolCalls(content, undefined, {
3599
+ includeActivity: true,
3600
+ });
3601
+ content.push({
3602
+ type: "text",
3603
+ text: `Something went wrong: ${message}`,
3604
+ });
3605
+ yield {
3606
+ content: [...content],
3607
+ status: {
3608
+ type: "incomplete" as const,
3609
+ reason: "error" as const,
3610
+ },
3611
+ metadata: {
3612
+ custom: { ...(runId ? { runId } : {}), runError },
3613
+ },
3614
+ };
3615
+ clearActiveRun();
3616
+ return;
3617
+ }
3618
+ if (continuation.resetVisibleContent) {
3619
+ yield {
3620
+ content: snapshotContent(content),
3621
+ } as ChatModelRunResult;
3622
+ }
3623
+ // Signal to the UI that we are in the continuation window.
3624
+ if (typeof window !== "undefined") {
3625
+ window.dispatchEvent(
3626
+ new CustomEvent("agent-chat:auto-continue", {
3627
+ detail: { tabId },
3628
+ }),
3629
+ );
3630
+ }
3631
+ await delay(250, abortSignal);
3632
+ if (abortSignal.aborted) return;
3633
+ continue;
3634
+ }
3635
+
3636
+ if (
3637
+ isRetryableStartupError(errMsg) &&
3638
+ startupRecoveryAttempts < MAX_STARTUP_RECOVERY_ATTEMPTS
3639
+ ) {
3640
+ await retryDelay(startupRecoveryAttempts++, abortSignal);
3641
+ if (abortSignal.aborted) return;
3642
+ continue;
3643
+ }
3644
+
3645
+ // No partial work exists, so this is still a real startup failure.
3646
+ captureChatClientError(err, "startup-failed", {
3647
+ retryableStartupError: isRetryableStartupError(errMsg),
3648
+ });
3649
+ const normalized = normalizeChatError(errMsg);
3650
+ const runError = {
3651
+ message: normalized.message,
3652
+ ...(normalized.details ? { details: normalized.details } : {}),
3653
+ errorCode: "connection_error",
3654
+ recoverable: true,
3655
+ ...(runId ? { runId } : {}),
3656
+ };
3657
+ if (typeof window !== "undefined") {
3658
+ window.dispatchEvent(
3659
+ new CustomEvent("agent-chat:run-error", {
3660
+ detail: { ...runError, tabId },
3661
+ }),
3662
+ );
3663
+ }
3664
+ content.push({
3665
+ type: "text",
3666
+ text: errMsg.startsWith("Server error:")
3667
+ ? errMsg
3668
+ : `Something went wrong: ${normalized.message}`,
3669
+ });
3670
+ yield {
3671
+ content: [...content],
3672
+ status: {
3673
+ type: "incomplete" as const,
3674
+ reason: "error" as const,
3675
+ },
3676
+ metadata: { custom: { ...(runId ? { runId } : {}), runError } },
3677
+ };
3678
+ return;
3679
+ }
3680
+ }
3681
+ } finally {
3682
+ if (typeof window !== "undefined") {
3683
+ window.dispatchEvent(
3684
+ new CustomEvent("agentNative.chatRunning", {
3685
+ detail: { isRunning: false, tabId },
3686
+ }),
3687
+ );
3688
+ }
3689
+ }
3690
+ },
3691
+ };
3692
+ }