@agent-native/core 0.132.2 → 0.133.1

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 (437) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +181 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/correlation.ts +11 -1
  5. package/corpus/core/src/a2a/types.ts +11 -3
  6. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +61 -46
  7. package/corpus/core/src/agent/engine/builder-engine.ts +22 -10
  8. package/corpus/core/src/agent/engine/error-detail.ts +184 -3
  9. package/corpus/core/src/agent/engine/failure-taxonomy.ts +123 -0
  10. package/corpus/core/src/agent/engine/index.ts +8 -0
  11. package/corpus/core/src/agent/engine/registry.ts +92 -8
  12. package/corpus/core/src/agent/engine/translate-ai-sdk.ts +17 -2
  13. package/corpus/core/src/agent/production-agent.ts +13 -33
  14. package/corpus/core/src/agent/run-loop-with-resume.ts +1 -1
  15. package/corpus/core/src/agent/run-manager.ts +43 -14
  16. package/corpus/core/src/agent/run-store.ts +49 -7
  17. package/corpus/core/src/agent/thread-data-builder.ts +36 -4
  18. package/corpus/core/src/cli/templates-meta.ts +13 -0
  19. package/corpus/core/src/cli/workspace-dev.ts +1 -0
  20. package/corpus/core/src/client/error-format.ts +12 -0
  21. package/corpus/core/src/credentials/index.ts +51 -12
  22. package/corpus/core/src/extensions/url-safety.ts +46 -12
  23. package/corpus/core/src/integrations/adapters/slack.ts +32 -10
  24. package/corpus/core/src/integrations/google-docs-poller.ts +12 -1
  25. package/corpus/core/src/integrations/installations-store.ts +30 -0
  26. package/corpus/core/src/integrations/plugin.ts +5 -5
  27. package/corpus/core/src/integrations/types.ts +7 -0
  28. package/corpus/core/src/integrations/webhook-handler.ts +24 -1
  29. package/corpus/core/src/jobs/background-automation-runner.ts +38 -1
  30. package/corpus/core/src/provider-api/index.ts +8 -2
  31. package/corpus/core/src/scripts/call-agent.ts +6 -0
  32. package/corpus/core/src/server/agent-chat-plugin.ts +30 -9
  33. package/corpus/core/src/server/agent-discovery.ts +2 -0
  34. package/corpus/core/src/server/builder-design-systems.ts +21 -0
  35. package/corpus/core/src/server/credential-provider.ts +51 -12
  36. package/corpus/core/src/server/index.ts +1 -0
  37. package/corpus/core/src/server/onboarding-html.ts +37 -2
  38. package/corpus/core/src/shared/reasoning-effort.ts +1 -1
  39. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +11 -0
  40. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +6 -2
  41. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +39 -19
  42. package/corpus/templates/assets/changelog/2026-07-31-the-agent-native-logo-stays-visible-when-the-sidebar-is-coll.md +6 -0
  43. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +30 -3
  44. package/corpus/templates/brain/changelog/2026-07-31-clicking-the-agent-native-logo-now-toggles-the-app-sidebar.md +6 -0
  45. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +1 -1
  46. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +54 -25
  47. package/corpus/templates/calendar/changelog/2026-07-31-the-agent-native-logo-stays-visible-when-the-sidebar-is-coll.md +6 -0
  48. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +30 -3
  49. package/corpus/templates/chat/changelog/2026-07-31-clicking-the-agent-native-logo-now-toggles-the-app-sidebar.md +6 -0
  50. package/corpus/templates/clips/app/components/library/library-layout.tsx +37 -21
  51. package/corpus/templates/clips/changelog/2026-07-31-the-agent-native-logo-stays-visible-when-the-sidebar-is-coll.md +6 -0
  52. package/corpus/templates/clips/desktop/src/hooks/useMediaDevices.ts +1 -1
  53. package/corpus/templates/clips/desktop/src/lib/media-capture-constraints.ts +1 -1
  54. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +34 -17
  55. package/corpus/templates/content/changelog/2026-07-31-the-agent-native-logo-stays-visible-when-the-sidebar-is-coll.md +6 -0
  56. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +6 -2
  57. package/corpus/templates/design/actions/create-design-system.ts +1 -0
  58. package/corpus/templates/design/actions/set-default-design-system.ts +34 -26
  59. package/corpus/templates/design/app/components/layout/Sidebar.tsx +21 -4
  60. package/corpus/templates/design/app/i18n-data.ts +39 -0
  61. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +229 -12
  62. package/corpus/templates/design/app/pages/DesignSystems.tsx +10 -4
  63. package/corpus/templates/design/changelog/2026-07-31-clicking-the-agent-native-logo-now-toggles-the-app-sidebar.md +6 -0
  64. package/corpus/templates/design/changelog/2026-07-31-design-system-indexing-keeps-your-chosen-name-when-you-uploa.md +6 -0
  65. package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +19 -4
  66. package/corpus/templates/dispatch/AGENTS.md +4 -0
  67. package/corpus/templates/factory/.agents/skills/actions/SKILL.md +562 -0
  68. package/corpus/templates/factory/.agents/skills/adding-a-feature/SKILL.md +198 -0
  69. package/corpus/templates/factory/.agents/skills/agent-native-docs/SKILL.md +115 -0
  70. package/corpus/templates/factory/.agents/skills/agent-native-toolkit/SKILL.md +182 -0
  71. package/corpus/templates/factory/.agents/skills/capture-learnings/SKILL.md +89 -0
  72. package/corpus/templates/factory/.agents/skills/create-skill/SKILL.md +221 -0
  73. package/corpus/templates/factory/.agents/skills/customizing-agent-native/SKILL.md +220 -0
  74. package/corpus/templates/factory/.agents/skills/delegate-to-agent/SKILL.md +263 -0
  75. package/corpus/templates/factory/.agents/skills/factory-graphs/SKILL.md +52 -0
  76. package/corpus/templates/factory/.agents/skills/feature-flags/SKILL.md +169 -0
  77. package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +174 -0
  78. package/corpus/templates/factory/.agents/skills/real-time-sync/SKILL.md +232 -0
  79. package/corpus/templates/factory/.agents/skills/security/SKILL.md +324 -0
  80. package/corpus/templates/factory/.agents/skills/self-modifying-code/SKILL.md +119 -0
  81. package/corpus/templates/factory/.agents/skills/shadcn-ui/SKILL.md +123 -0
  82. package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +232 -0
  83. package/corpus/templates/factory/.agents/skills/storing-data/SKILL.md +178 -0
  84. package/corpus/templates/factory/.agents/skills/upgrade-agent-native/SKILL.md +122 -0
  85. package/corpus/templates/factory/.env.example +24 -0
  86. package/corpus/templates/factory/.ignore +0 -0
  87. package/corpus/templates/factory/.oxfmtrc.json +8 -0
  88. package/corpus/templates/factory/AGENTS.md +90 -0
  89. package/corpus/templates/factory/CHANGELOG.md +10 -0
  90. package/corpus/templates/factory/DEVELOPING.md +185 -0
  91. package/corpus/templates/factory/README.md +64 -0
  92. package/corpus/templates/factory/_gitignore +42 -0
  93. package/corpus/templates/factory/actions/add-factory-comment.ts +55 -0
  94. package/corpus/templates/factory/actions/approve-factory-item.ts +214 -0
  95. package/corpus/templates/factory/actions/babysit-pull-request.ts +126 -0
  96. package/corpus/templates/factory/actions/evaluate-triage-item.ts +172 -0
  97. package/corpus/templates/factory/actions/get-factory-graph.ts +62 -0
  98. package/corpus/templates/factory/actions/get-triage-config.ts +42 -0
  99. package/corpus/templates/factory/actions/get-triage-item.ts +74 -0
  100. package/corpus/templates/factory/actions/hello.ts +13 -0
  101. package/corpus/templates/factory/actions/ingest-github-observation.ts +100 -0
  102. package/corpus/templates/factory/actions/list-factories.ts +40 -0
  103. package/corpus/templates/factory/actions/list-factory-comments.ts +40 -0
  104. package/corpus/templates/factory/actions/list-triage-items.ts +85 -0
  105. package/corpus/templates/factory/actions/list-triage-rules.ts +44 -0
  106. package/corpus/templates/factory/actions/navigate.ts +41 -0
  107. package/corpus/templates/factory/actions/poll-slack-channel.ts +118 -0
  108. package/corpus/templates/factory/actions/reconcile-triage-run.ts +179 -0
  109. package/corpus/templates/factory/actions/record-triage-feedback.ts +58 -0
  110. package/corpus/templates/factory/actions/run.ts +2 -0
  111. package/corpus/templates/factory/actions/save-factory-graph.ts +129 -0
  112. package/corpus/templates/factory/actions/save-triage-config.ts +67 -0
  113. package/corpus/templates/factory/actions/save-triage-rule.ts +89 -0
  114. package/corpus/templates/factory/actions/suggest-factory-rules.ts +70 -0
  115. package/corpus/templates/factory/actions/view-screen.ts +73 -0
  116. package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +441 -0
  117. package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +388 -0
  118. package/corpus/templates/factory/app/components/layout/Header.tsx +60 -0
  119. package/corpus/templates/factory/app/components/layout/Layout.tsx +190 -0
  120. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +538 -0
  121. package/corpus/templates/factory/app/components/triage/triage-status-pill.tsx +28 -0
  122. package/corpus/templates/factory/app/components/ui/button.tsx +1 -0
  123. package/corpus/templates/factory/app/components/ui/card.tsx +1 -0
  124. package/corpus/templates/factory/app/components/ui/dropdown-menu.tsx +1 -0
  125. package/corpus/templates/factory/app/components/ui/input.tsx +1 -0
  126. package/corpus/templates/factory/app/components/ui/label.tsx +1 -0
  127. package/corpus/templates/factory/app/components/ui/sheet.tsx +1 -0
  128. package/corpus/templates/factory/app/components/ui/textarea.tsx +1 -0
  129. package/corpus/templates/factory/app/components/ui/toolkit-provider.tsx +17 -0
  130. package/corpus/templates/factory/app/components/ui/tooltip.tsx +1 -0
  131. package/corpus/templates/factory/app/design-system.ts +3 -0
  132. package/corpus/templates/factory/app/entry.client.tsx +22 -0
  133. package/corpus/templates/factory/app/entry.server.tsx +10 -0
  134. package/corpus/templates/factory/app/global.css +93 -0
  135. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +113 -0
  136. package/corpus/templates/factory/app/i18n/en-US.ts +211 -0
  137. package/corpus/templates/factory/app/i18n/index.ts +34 -0
  138. package/corpus/templates/factory/app/i18n-data.ts +556 -0
  139. package/corpus/templates/factory/app/lib/agent-page.tsx +49 -0
  140. package/corpus/templates/factory/app/lib/app-config.ts +11 -0
  141. package/corpus/templates/factory/app/lib/tab-id.ts +1 -0
  142. package/corpus/templates/factory/app/lib/utils.ts +1 -0
  143. package/corpus/templates/factory/app/root.tsx +175 -0
  144. package/corpus/templates/factory/app/routes/_index.tsx +94 -0
  145. package/corpus/templates/factory/app/routes/agent.tsx +24 -0
  146. package/corpus/templates/factory/app/routes/chat.$threadId.tsx +1 -0
  147. package/corpus/templates/factory/app/routes/database.tsx +17 -0
  148. package/corpus/templates/factory/app/routes/extensions.$id.$slug.tsx +2 -0
  149. package/corpus/templates/factory/app/routes/extensions.$id.tsx +11 -0
  150. package/corpus/templates/factory/app/routes/extensions._index.tsx +11 -0
  151. package/corpus/templates/factory/app/routes/extensions.tsx +5 -0
  152. package/corpus/templates/factory/app/routes/factory.tsx +1003 -0
  153. package/corpus/templates/factory/app/routes/observability.tsx +19 -0
  154. package/corpus/templates/factory/app/routes/settings.tsx +89 -0
  155. package/corpus/templates/factory/app/routes/team.tsx +11 -0
  156. package/corpus/templates/factory/app/routes.ts +4 -0
  157. package/corpus/templates/factory/app/vite-env.d.ts +6 -0
  158. package/corpus/templates/factory/changelog/2026-06-24-a-new-settings-page-gives-quick-access-to-language-workspace.md +6 -0
  159. package/corpus/templates/factory/changelog/2026-06-24-added-a-language-picker-and-localized-app-chrome-for-support.md +6 -0
  160. package/corpus/templates/factory/changelog/2026-06-27-traditional-chinese-copy-uses-taiwan-terminology.md +6 -0
  161. package/corpus/templates/factory/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  162. package/corpus/templates/factory/changelog/2026-06-29-chat-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  163. package/corpus/templates/factory/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  164. package/corpus/templates/factory/changelog/2026-07-10-chat-now-makes-ai-connection-setup-clear-without-shifting-th.md +6 -0
  165. package/corpus/templates/factory/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
  166. package/corpus/templates/factory/changelog/2026-07-14-chat-opens-reliably-on-hosted-deployments-instead-of-failing.md +6 -0
  167. package/corpus/templates/factory/changelog/2026-07-14-fixed-chat-template-startup-with-older-core-versions.md +6 -0
  168. package/corpus/templates/factory/changelog/2026-07-15-chat-navigation-focuses-on-chat-and-agent.md +6 -0
  169. package/corpus/templates/factory/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  170. package/corpus/templates/factory/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
  171. package/corpus/templates/factory/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
  172. package/corpus/templates/factory/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  173. package/corpus/templates/factory/changelog/2026-07-22-recent-chats-are-easier-to-scan-and-expand.md +6 -0
  174. package/corpus/templates/factory/changelog/2026-07-23-full-page-chat-is-better-centered-with-quieter-chat-history-.md +6 -0
  175. package/corpus/templates/factory/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  176. package/corpus/templates/factory/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  177. package/corpus/templates/factory/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  178. package/corpus/templates/factory/changelog/2026-07-29-sidebar-footers-now-keep-feedback-search-and-collapse-togeth.md +6 -0
  179. package/corpus/templates/factory/changelog/2026-07-31-added-an-inspectable-observe-only-factory-queue-for-slack-.md +6 -0
  180. package/corpus/templates/factory/changelog/2026-07-31-clicking-the-agent-native-logo-now-toggles-the-app-sidebar.md +6 -0
  181. package/corpus/templates/factory/components.json +20 -0
  182. package/corpus/templates/factory/data/sync-config.json +1 -0
  183. package/corpus/templates/factory/learnings.defaults.md +5 -0
  184. package/corpus/templates/factory/netlify.toml +10 -0
  185. package/corpus/templates/factory/package.json +105 -0
  186. package/corpus/templates/factory/public/agent-native-icon-dark.svg +10 -0
  187. package/corpus/templates/factory/public/agent-native-icon-light.svg +10 -0
  188. package/corpus/templates/factory/public/agent-native-logo-dark.svg +21 -0
  189. package/corpus/templates/factory/public/agent-native-logo-light.svg +21 -0
  190. package/corpus/templates/factory/public/favicon.svg +1 -0
  191. package/corpus/templates/factory/public/icon-180.svg +1 -0
  192. package/corpus/templates/factory/public/icon-192.svg +1 -0
  193. package/corpus/templates/factory/public/icon-512.svg +1 -0
  194. package/corpus/templates/factory/public/manifest.json +21 -0
  195. package/corpus/templates/factory/react-router.config.ts +7 -0
  196. package/corpus/templates/factory/server/connectors/credentials.ts +129 -0
  197. package/corpus/templates/factory/server/connectors/slack.ts +129 -0
  198. package/corpus/templates/factory/server/db/index.ts +6 -0
  199. package/corpus/templates/factory/server/db/schema.ts +193 -0
  200. package/corpus/templates/factory/server/factory-graph/contracts.ts +212 -0
  201. package/corpus/templates/factory/server/factory-graph/store.ts +121 -0
  202. package/corpus/templates/factory/server/lib/require-workspace-member.ts +55 -0
  203. package/corpus/templates/factory/server/middleware/auth.ts +15 -0
  204. package/corpus/templates/factory/server/plugins/agent-chat.ts +58 -0
  205. package/corpus/templates/factory/server/plugins/auth.ts +14 -0
  206. package/corpus/templates/factory/server/plugins/factory-migrations.ts +247 -0
  207. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +116 -0
  208. package/corpus/templates/factory/server/plugins/integrations.ts +46 -0
  209. package/corpus/templates/factory/server/routes/[...page].get.ts +5 -0
  210. package/corpus/templates/factory/server/routes/_agent-native/factory/builder-callback.post.ts +116 -0
  211. package/corpus/templates/factory/server/triage/ai-services-git.ts +197 -0
  212. package/corpus/templates/factory/server/triage/builder-executor.ts +118 -0
  213. package/corpus/templates/factory/server/triage/contracts.ts +164 -0
  214. package/corpus/templates/factory/server/triage/github-ingestion.ts +35 -0
  215. package/corpus/templates/factory/server/triage/guards.ts +121 -0
  216. package/corpus/templates/factory/server/triage/ids.ts +27 -0
  217. package/corpus/templates/factory/server/triage/pr-babysit.ts +100 -0
  218. package/corpus/templates/factory/server/triage/pr-monitor.ts +220 -0
  219. package/corpus/templates/factory/server/triage/slack-client.ts +55 -0
  220. package/corpus/templates/factory/server/triage/slack-poller.ts +152 -0
  221. package/corpus/templates/factory/ssr-entry.ts +15 -0
  222. package/corpus/templates/factory/tsconfig.json +21 -0
  223. package/corpus/templates/factory/vite.config.ts +19 -0
  224. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +55 -5
  225. package/corpus/templates/forms/changelog/2026-07-31-the-agent-native-logo-stays-visible-when-the-sidebar-is-coll.md +6 -0
  226. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +6 -2
  227. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +21 -6
  228. package/corpus/templates/plan/changelog/2026-07-31-clicking-the-agent-native-logo-now-toggles-the-app-sidebar.md +6 -0
  229. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +12 -0
  230. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +9 -0
  231. package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +11 -0
  232. package/corpus/templates/slides/AGENTS.md +5 -6
  233. package/corpus/templates/slides/actions/_await-fit-check.ts +20 -10
  234. package/corpus/templates/slides/actions/add-slide.ts +17 -4
  235. package/corpus/templates/slides/actions/create-design-system.ts +16 -4
  236. package/corpus/templates/slides/actions/delete-design-system.ts +18 -2
  237. package/corpus/templates/slides/actions/get-layout-overflows.ts +88 -0
  238. package/corpus/templates/slides/actions/import-file.ts +1 -0
  239. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +8 -0
  240. package/corpus/templates/slides/actions/patch-deck.ts +48 -16
  241. package/corpus/templates/slides/actions/set-default-design-system.ts +49 -19
  242. package/corpus/templates/slides/actions/update-slide.ts +13 -3
  243. package/corpus/templates/slides/actions/view-screen.ts +87 -10
  244. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +155 -28
  245. package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +21 -17
  246. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +849 -341
  247. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +102 -2
  248. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +13 -16
  249. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +60 -12
  250. package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +11 -1
  251. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +66 -19
  252. package/corpus/templates/slides/app/i18n/en-US.ts +23 -0
  253. package/corpus/templates/slides/app/lib/normalize-slide-padding.ts +8 -15
  254. package/corpus/templates/slides/app/pages/DeckEditor.tsx +4 -3
  255. package/corpus/templates/slides/app/pages/DesignSystems.tsx +4 -3
  256. package/corpus/templates/slides/app/pages/Index.tsx +1 -1
  257. package/corpus/templates/slides/changelog/2026-07-31-design-system-details-now-show-their-source-and-indexing-sta.md +6 -0
  258. package/corpus/templates/slides/changelog/2026-07-31-design-system-indexing-keeps-your-chosen-name-when-you-uploa.md +6 -0
  259. package/corpus/templates/slides/changelog/2026-07-31-design-system-setup-keeps-focus-rings-visible-and-uses-a-sim.md +6 -0
  260. package/corpus/templates/slides/changelog/2026-07-31-skip-prompt-is-now-available-in-the-new-deck-prompt-header.md +6 -0
  261. package/corpus/templates/slides/changelog/2026-07-31-slides-now-detect-real-text-and-box-overflow-across-the-deck.md +6 -0
  262. package/corpus/templates/slides/changelog/2026-07-31-the-agent-native-logo-stays-visible-when-the-sidebar-is-coll.md +6 -0
  263. package/corpus/templates/slides/server/handlers/design-systems.ts +1 -0
  264. package/corpus/templates/slides/server/handlers/index-design-system-sources.ts +1 -0
  265. package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +23 -4
  266. package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -0
  267. package/corpus/templates/slides/server/plugins/creative-context.ts +16 -3
  268. package/corpus/templates/slides/server/workspace-defaults.ts +5 -1
  269. package/corpus/templates/slides/shared/slide-fit.ts +26 -0
  270. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +30 -3
  271. package/corpus/templates/tasks/changelog/2026-07-31-clicking-the-agent-native-logo-now-toggles-the-app-sidebar.md +6 -0
  272. package/dist/a2a/correlation.d.ts +4 -1
  273. package/dist/a2a/correlation.d.ts.map +1 -1
  274. package/dist/a2a/correlation.js +8 -1
  275. package/dist/a2a/correlation.js.map +1 -1
  276. package/dist/a2a/types.d.ts +11 -3
  277. package/dist/a2a/types.d.ts.map +1 -1
  278. package/dist/a2a/types.js.map +1 -1
  279. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  280. package/dist/agent/engine/ai-sdk-engine.js +51 -41
  281. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  282. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  283. package/dist/agent/engine/builder-engine.js +18 -8
  284. package/dist/agent/engine/builder-engine.js.map +1 -1
  285. package/dist/agent/engine/error-detail.d.ts +61 -0
  286. package/dist/agent/engine/error-detail.d.ts.map +1 -1
  287. package/dist/agent/engine/error-detail.js +145 -3
  288. package/dist/agent/engine/error-detail.js.map +1 -1
  289. package/dist/agent/engine/failure-taxonomy.d.ts +24 -0
  290. package/dist/agent/engine/failure-taxonomy.d.ts.map +1 -0
  291. package/dist/agent/engine/failure-taxonomy.js +88 -0
  292. package/dist/agent/engine/failure-taxonomy.js.map +1 -0
  293. package/dist/agent/engine/index.d.ts +2 -1
  294. package/dist/agent/engine/index.d.ts.map +1 -1
  295. package/dist/agent/engine/index.js +2 -1
  296. package/dist/agent/engine/index.js.map +1 -1
  297. package/dist/agent/engine/registry.d.ts +16 -0
  298. package/dist/agent/engine/registry.d.ts.map +1 -1
  299. package/dist/agent/engine/registry.js +71 -6
  300. package/dist/agent/engine/registry.js.map +1 -1
  301. package/dist/agent/engine/translate-ai-sdk.d.ts.map +1 -1
  302. package/dist/agent/engine/translate-ai-sdk.js +14 -2
  303. package/dist/agent/engine/translate-ai-sdk.js.map +1 -1
  304. package/dist/agent/production-agent.d.ts.map +1 -1
  305. package/dist/agent/production-agent.js +13 -24
  306. package/dist/agent/production-agent.js.map +1 -1
  307. package/dist/agent/run-loop-with-resume.js +1 -1
  308. package/dist/agent/run-loop-with-resume.js.map +1 -1
  309. package/dist/agent/run-manager.d.ts.map +1 -1
  310. package/dist/agent/run-manager.js +39 -10
  311. package/dist/agent/run-manager.js.map +1 -1
  312. package/dist/agent/run-store.d.ts +10 -0
  313. package/dist/agent/run-store.d.ts.map +1 -1
  314. package/dist/agent/run-store.js +38 -6
  315. package/dist/agent/run-store.js.map +1 -1
  316. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  317. package/dist/agent/thread-data-builder.js +28 -4
  318. package/dist/agent/thread-data-builder.js.map +1 -1
  319. package/dist/cli/templates-meta.d.ts.map +1 -1
  320. package/dist/cli/templates-meta.js +13 -0
  321. package/dist/cli/templates-meta.js.map +1 -1
  322. package/dist/cli/workspace-dev.d.ts.map +1 -1
  323. package/dist/cli/workspace-dev.js +1 -0
  324. package/dist/cli/workspace-dev.js.map +1 -1
  325. package/dist/client/error-format.d.ts.map +1 -1
  326. package/dist/client/error-format.js +10 -0
  327. package/dist/client/error-format.js.map +1 -1
  328. package/dist/collab/struct-routes.d.ts +1 -1
  329. package/dist/credentials/index.d.ts +9 -2
  330. package/dist/credentials/index.d.ts.map +1 -1
  331. package/dist/credentials/index.js +50 -8
  332. package/dist/credentials/index.js.map +1 -1
  333. package/dist/extensions/url-safety.d.ts.map +1 -1
  334. package/dist/extensions/url-safety.js +38 -12
  335. package/dist/extensions/url-safety.js.map +1 -1
  336. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  337. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  338. package/dist/integrations/adapters/slack.js +25 -8
  339. package/dist/integrations/adapters/slack.js.map +1 -1
  340. package/dist/integrations/google-docs-poller.d.ts.map +1 -1
  341. package/dist/integrations/google-docs-poller.js +13 -1
  342. package/dist/integrations/google-docs-poller.js.map +1 -1
  343. package/dist/integrations/installations-store.d.ts +10 -0
  344. package/dist/integrations/installations-store.d.ts.map +1 -1
  345. package/dist/integrations/installations-store.js +24 -0
  346. package/dist/integrations/installations-store.js.map +1 -1
  347. package/dist/integrations/plugin.d.ts.map +1 -1
  348. package/dist/integrations/plugin.js +6 -5
  349. package/dist/integrations/plugin.js.map +1 -1
  350. package/dist/integrations/types.d.ts +7 -0
  351. package/dist/integrations/types.d.ts.map +1 -1
  352. package/dist/integrations/types.js.map +1 -1
  353. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  354. package/dist/integrations/webhook-handler.js +16 -1
  355. package/dist/integrations/webhook-handler.js.map +1 -1
  356. package/dist/jobs/background-automation-runner.d.ts.map +1 -1
  357. package/dist/jobs/background-automation-runner.js +35 -2
  358. package/dist/jobs/background-automation-runner.js.map +1 -1
  359. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  360. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
  361. package/dist/notifications/routes.d.ts +1 -1
  362. package/dist/observability/routes.d.ts +1 -1
  363. package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
  364. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  365. package/dist/provider-api/index.js +7 -3
  366. package/dist/provider-api/index.js.map +1 -1
  367. package/dist/resources/handlers.d.ts +1 -1
  368. package/dist/scripts/call-agent.d.ts.map +1 -1
  369. package/dist/scripts/call-agent.js +6 -1
  370. package/dist/scripts/call-agent.js.map +1 -1
  371. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  372. package/dist/server/agent-chat-plugin.js +25 -7
  373. package/dist/server/agent-chat-plugin.js.map +1 -1
  374. package/dist/server/agent-discovery.d.ts +2 -0
  375. package/dist/server/agent-discovery.d.ts.map +1 -1
  376. package/dist/server/agent-discovery.js.map +1 -1
  377. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  378. package/dist/server/builder-design-systems.d.ts +4 -1
  379. package/dist/server/builder-design-systems.d.ts.map +1 -1
  380. package/dist/server/builder-design-systems.js +11 -1
  381. package/dist/server/builder-design-systems.js.map +1 -1
  382. package/dist/server/credential-provider.d.ts.map +1 -1
  383. package/dist/server/credential-provider.js +49 -12
  384. package/dist/server/credential-provider.js.map +1 -1
  385. package/dist/server/index.d.ts +1 -1
  386. package/dist/server/index.d.ts.map +1 -1
  387. package/dist/server/index.js.map +1 -1
  388. package/dist/server/onboarding-html.d.ts.map +1 -1
  389. package/dist/server/onboarding-html.js +37 -2
  390. package/dist/server/onboarding-html.js.map +1 -1
  391. package/dist/shared/reasoning-effort.d.ts +1 -0
  392. package/dist/shared/reasoning-effort.d.ts.map +1 -1
  393. package/dist/shared/reasoning-effort.js +1 -1
  394. package/dist/shared/reasoning-effort.js.map +1 -1
  395. package/dist/templates/chat/app/components/layout/Sidebar.tsx +30 -3
  396. package/dist/templates/chat/changelog/2026-07-31-clicking-the-agent-native-logo-now-toggles-the-app-sidebar.md +6 -0
  397. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +11 -0
  398. package/package.json +1 -1
  399. package/src/a2a/correlation.ts +11 -1
  400. package/src/a2a/types.ts +11 -3
  401. package/src/agent/engine/ai-sdk-engine.ts +61 -46
  402. package/src/agent/engine/builder-engine.ts +22 -10
  403. package/src/agent/engine/error-detail.ts +184 -3
  404. package/src/agent/engine/failure-taxonomy.ts +123 -0
  405. package/src/agent/engine/index.ts +8 -0
  406. package/src/agent/engine/registry.ts +92 -8
  407. package/src/agent/engine/translate-ai-sdk.ts +17 -2
  408. package/src/agent/production-agent.ts +13 -33
  409. package/src/agent/run-loop-with-resume.ts +1 -1
  410. package/src/agent/run-manager.ts +43 -14
  411. package/src/agent/run-store.ts +49 -7
  412. package/src/agent/thread-data-builder.ts +36 -4
  413. package/src/cli/templates-meta.ts +13 -0
  414. package/src/cli/workspace-dev.ts +1 -0
  415. package/src/client/error-format.ts +12 -0
  416. package/src/credentials/index.ts +51 -12
  417. package/src/extensions/url-safety.ts +46 -12
  418. package/src/integrations/adapters/slack.ts +32 -10
  419. package/src/integrations/google-docs-poller.ts +12 -1
  420. package/src/integrations/installations-store.ts +30 -0
  421. package/src/integrations/plugin.ts +5 -5
  422. package/src/integrations/types.ts +7 -0
  423. package/src/integrations/webhook-handler.ts +24 -1
  424. package/src/jobs/background-automation-runner.ts +38 -1
  425. package/src/provider-api/index.ts +8 -2
  426. package/src/scripts/call-agent.ts +6 -0
  427. package/src/server/agent-chat-plugin.ts +30 -9
  428. package/src/server/agent-discovery.ts +2 -0
  429. package/src/server/builder-design-systems.ts +21 -0
  430. package/src/server/credential-provider.ts +51 -12
  431. package/src/server/index.ts +1 -0
  432. package/src/server/onboarding-html.ts +37 -2
  433. package/src/shared/reasoning-effort.ts +1 -1
  434. package/src/templates/chat/app/components/layout/Sidebar.tsx +30 -3
  435. package/src/templates/chat/changelog/2026-07-31-clicking-the-agent-native-logo-now-toggles-the-app-sidebar.md +6 -0
  436. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +11 -0
  437. package/corpus/templates/clips/desktop/src/lib/media-device-selection.ts +0 -157
@@ -4,6 +4,11 @@ import {
4
4
  LLM_MISSING_CREDENTIALS_ERROR_CODE,
5
5
  LLM_MISSING_CREDENTIALS_MESSAGE,
6
6
  } from "./engine/credential-errors.js";
7
+ import {
8
+ classifyTerminalErrorCode,
9
+ describeErrorWithCauses,
10
+ isProviderConnectionError,
11
+ } from "./engine/error-detail.js";
7
12
  import { EngineError } from "./engine/types.js";
8
13
  import {
9
14
  insertRun,
@@ -341,23 +346,16 @@ function getRunErrorMessage(err: unknown): string {
341
346
  return "Unknown error";
342
347
  }
343
348
 
344
- function isProviderConnectionErrorMessage(message: string): boolean {
345
- const normalized = message.trim().toLowerCase();
346
- return (
347
- normalized === "connection error." ||
348
- normalized.includes("cannot connect to api:")
349
- );
350
- }
351
-
352
349
  function getRunErrorCode(err: unknown): string | undefined {
353
350
  if (err instanceof EngineError) {
354
351
  if (err.errorCode) return err.errorCode;
355
352
  if (err.statusCode === 429) return PROVIDER_RATE_LIMITED_ERROR_CODE;
356
353
  }
357
- if (err instanceof Error && isProviderConnectionErrorMessage(err.message)) {
358
- return PROVIDER_NETWORK_ERROR_CODE;
359
- }
360
- return undefined;
354
+ if (isProviderConnectionError(err)) return PROVIDER_NETWORK_ERROR_CODE;
355
+ // The code rides the error EVENT to the client, which decides recovery from
356
+ // it — so an uncoded transport failure has to be classified here too, not
357
+ // only when the run row is persisted.
358
+ return classifyTerminalErrorCode(describeErrorWithCauses(err));
361
359
  }
362
360
 
363
361
  function getEngineRunErrorDetails(err: EngineError): string | undefined {
@@ -376,7 +374,7 @@ function shouldCaptureRunError(err: unknown): boolean {
376
374
  }
377
375
  if (!(err instanceof Error)) return true;
378
376
  if (/^40[13] status code\b/i.test(err.message)) return false;
379
- if (isProviderConnectionErrorMessage(err.message)) return false;
377
+ if (isProviderConnectionError(err)) return false;
380
378
  if (!errorCode) return true;
381
379
  const normalizedCode = errorCode.toLowerCase();
382
380
  return (
@@ -984,6 +982,13 @@ export function startRun(
984
982
  // false-stale-reap zombie scenario where the reaper flipped the row while
985
983
  // this isolate was briefly unable to heartbeat (DB latency / GC pause).
986
984
  let lastAbortCheck = Date.now() - 3000;
985
+ // A read failure here used to be indistinguishable from "not aborted" —
986
+ // exactly the coerced-to-false pattern that lets a real Stop go unseen for
987
+ // the rest of the run. Count consecutive failures like the heartbeat-write
988
+ // handler above; past the same threshold, fail closed (self-abort with a
989
+ // reason outside TURN_ENDING_ABORT_REASONS/RECOVERABLE_ABORT_REASONS, so it
990
+ // surfaces as a typed error) instead of silently retrying forever.
991
+ let consecutiveAbortCheckFailures = 0;
987
992
  const checkSqlAbort = () => {
988
993
  const now = Date.now();
989
994
  if (now - lastAbortCheck < 3000) return;
@@ -1004,7 +1009,26 @@ export function startRun(
1004
1009
  }
1005
1010
  }
1006
1011
  })
1007
- .catch(() => {});
1012
+ .then(() => {
1013
+ consecutiveAbortCheckFailures = 0;
1014
+ })
1015
+ .catch((error) => {
1016
+ consecutiveAbortCheckFailures += 1;
1017
+ if (consecutiveAbortCheckFailures >= 3) {
1018
+ captureError(error, {
1019
+ route: "/_agent-native/agent-chat",
1020
+ tags: {
1021
+ source: "agent-run-manager",
1022
+ phase: "abort-check",
1023
+ consecutiveFailures: String(consecutiveAbortCheckFailures),
1024
+ },
1025
+ extra: { runId, threadId },
1026
+ });
1027
+ if (!abort.signal.aborted) {
1028
+ abortInMemoryRun(run, "abort_check_unavailable");
1029
+ }
1030
+ }
1031
+ });
1008
1032
  };
1009
1033
 
1010
1034
  // Heartbeat: bump heartbeat_at every 1.5s so watchers can detect a dead
@@ -1456,6 +1480,11 @@ export function startRun(
1456
1480
  ? completionError.message
1457
1481
  : String(completionError));
1458
1482
  }
1483
+ // An engine that emitted an error event without a code has NOT told us
1484
+ // the failure is unclassifiable — it told us nothing. Recover the code
1485
+ // from the message before falling back to "unknown", which the client
1486
+ // reads as "do not attempt recovery".
1487
+ errorCode ??= classifyTerminalErrorCode(errorDetail);
1459
1488
  runTerminalErrorCode = errorCode ?? "unknown";
1460
1489
  runTerminalErrorDetail = errorDetail;
1461
1490
  await setRunError(runId, errorCode ?? "unknown", errorDetail);
@@ -736,7 +736,13 @@ function backgroundAwareStaleCutoffSql(): string {
736
736
  // `CAST(? AS BIGINT)` is required: without it Postgres infers the param as
737
737
  // int4 from the int4 window literals, so the bound `Date.now()` ms epoch
738
738
  // overflows int4. The cast keeps the subtraction 64-bit; a no-op on SQLite.
739
- return `(CAST(? AS BIGINT) - CASE WHEN dispatch_mode = 'background-processing' THEN ${BACKGROUND_PROCESSING_RUN_STALE_MS} WHEN dispatch_mode LIKE 'background%' THEN ${BACKGROUND_RUN_STALE_MS} ELSE ${RUN_STALE_MS} END)`;
739
+ // The tight post-claim window buys ONE thing: reaching the durable successor
740
+ // sooner (see BACKGROUND_PROCESSING_RUN_STALE_MS). A row with no
741
+ // `dispatch_payload` has no successor to reach — `attemptStaleRunRecovery`
742
+ // declines it as `not_redispatchable` — so reaping it early is pure loss: it
743
+ // kills an in-process background automation (scheduler/trigger) that is still
744
+ // working, and nothing recovers it. Those get the wider background window.
745
+ return `(CAST(? AS BIGINT) - CASE WHEN dispatch_mode = 'background-processing' AND dispatch_payload IS NOT NULL THEN ${BACKGROUND_PROCESSING_RUN_STALE_MS} WHEN dispatch_mode LIKE 'background%' THEN ${BACKGROUND_RUN_STALE_MS} ELSE ${RUN_STALE_MS} END)`;
740
746
  }
741
747
 
742
748
  function terminalRunEventExclusionSql(runIdColumn = "id"): string {
@@ -946,6 +952,16 @@ export interface UnclaimedBackgroundRunRow {
946
952
  * pre-inserted — independent of any liveness bump a redispatch attempt
947
953
  * makes along the way. */
948
954
  startedAt: number;
955
+ /**
956
+ * Whether the row still carries the `dispatch_payload` a redispatched worker
957
+ * would rehydrate its request body from. Eligibility for this sweep does NOT
958
+ * imply it: a background row can reach the grace window having never had a
959
+ * payload at all. Redispatching one of those asserts `payloadRef: true` to a
960
+ * worker that then cannot rehydrate, so it kills the run as
961
+ * `dispatch_payload_missing` — a reason that reads like data loss for what is
962
+ * really an un-redispatchable handoff.
963
+ */
964
+ hasDispatchPayload: boolean;
949
965
  }
950
966
 
951
967
  /**
@@ -965,7 +981,10 @@ export async function listUnclaimedBackgroundRunRows(): Promise<
965
981
  const { rows } = await client.execute({
966
982
  // CAST keeps the ms-epoch param 64-bit on Postgres (see
967
983
  // backgroundAwareStaleCutoffSql for the int4-inference failure mode).
968
- sql: `SELECT id, started_at FROM agent_runs
984
+ // Report payload presence rather than filtering on it: a payload-less row
985
+ // must still be VISIBLE to the sweep so the slow pass can reap it. Filtering
986
+ // it out here would leave it `running` forever.
987
+ sql: `SELECT id, started_at, (dispatch_payload IS NOT NULL) AS has_dispatch_payload FROM agent_runs
969
988
  WHERE status = 'running'
970
989
  AND dispatch_mode = 'background'
971
990
  AND COALESCE(heartbeat_at, started_at) < (CAST(? AS BIGINT) - ${UNCLAIMED_BACKGROUND_RUN_GRACE_MS})`,
@@ -975,11 +994,15 @@ export async function listUnclaimedBackgroundRunRows(): Promise<
975
994
  for (const row of rows ?? []) {
976
995
  const id = (row as { id?: unknown }).id;
977
996
  const startedAt = (row as { started_at?: unknown }).started_at;
997
+ const hasPayload = (row as { has_dispatch_payload?: unknown })
998
+ .has_dispatch_payload;
978
999
  if (typeof id === "string" && id) {
979
1000
  result.push({
980
1001
  id,
981
1002
  startedAt:
982
1003
  typeof startedAt === "number" ? startedAt : Number(startedAt) || 0,
1004
+ // SQLite returns 1/0 where Postgres returns a boolean.
1005
+ hasDispatchPayload: hasPayload === true || hasPayload === 1,
983
1006
  });
984
1007
  }
985
1008
  }
@@ -1147,10 +1170,20 @@ export async function setRunTerminalReason(
1147
1170
  await ensureRunTables();
1148
1171
  const client = getDbExec();
1149
1172
  const reason = terminalReason.slice(0, 200);
1173
+ // Write-once for a row that is already terminal. Three writers in three
1174
+ // isolates race on this column — the mid-run checkpoint, the run-manager's
1175
+ // finalization, and the background worker's failure path — with no ordering
1176
+ // between them, and last-writer-wins let a late checkpoint relabel a row
1177
+ // another isolate had already finalized. That produced impossible rows
1178
+ // (status='errored' carrying a continuation reason, no error_code, no
1179
+ // terminal event) and misattributed 130 production runs to a failure mode
1180
+ // they never hit. A row still `running` has no honest reason yet, so it
1181
+ // stays writable; once one is recorded on a terminal row, it stands.
1182
+ const guard = `AND (status = 'running' OR terminal_reason IS NULL OR terminal_reason = '')`;
1150
1183
  await client.execute({
1151
1184
  sql: isContinuationTerminalReason(reason)
1152
- ? `UPDATE agent_runs SET terminal_reason = ?, status = CASE WHEN status = 'completed' THEN 'truncated' ELSE status END WHERE id = ?`
1153
- : `UPDATE agent_runs SET terminal_reason = ? WHERE id = ?`,
1185
+ ? `UPDATE agent_runs SET terminal_reason = ?, status = CASE WHEN status = 'completed' THEN 'truncated' ELSE status END WHERE id = ? ${guard}`
1186
+ : `UPDATE agent_runs SET terminal_reason = ? WHERE id = ? ${guard}`,
1154
1187
  args: [reason, runId],
1155
1188
  });
1156
1189
  } catch {
@@ -1519,7 +1552,7 @@ interface StaleRunRecoverySuccessor {
1519
1552
  type StaleRunRecoveryOutcome =
1520
1553
  | ({ outcome: "recovered" } & StaleRunRecoverySuccessor)
1521
1554
  | { outcome: "not_background" }
1522
- | { outcome: "payload_missing" }
1555
+ | { outcome: "not_redispatchable" }
1523
1556
  | { outcome: "newer_run_exists" }
1524
1557
  | { outcome: "budget_exhausted" }
1525
1558
  | { outcome: "repeated_no_progress" };
@@ -1569,7 +1602,9 @@ function staleRecoveryDispatchPayload(payload: string): string {
1569
1602
  * - the row is a background chat-turn dispatch (`dispatch_mode` starting
1570
1603
  * with "background") — a foreground/foreground-self-chain run has a
1571
1604
  * connected client to recover it via its own `auto_continue` re-POST.
1572
- * - its `dispatch_payload` is still present without it there is nothing
1605
+ * - its `dispatch_payload` is still present (an in-process automation run
1606
+ * never had one and is declined as `not_redispatchable`, not as a loss)
1607
+ * — without it there is nothing
1573
1608
  * to rehydrate the successor's request body from. Read HERE, before the
1574
1609
  * caller's terminal write (which NULLs `dispatch_payload` on every other
1575
1610
  * path), so it survives long enough to carry over.
@@ -1606,7 +1641,14 @@ async function attemptStaleRunRecovery(
1606
1641
  }
1607
1642
  const payload = row.dispatch_payload;
1608
1643
  if (typeof payload !== "string" || payload.length === 0) {
1609
- return { outcome: "payload_missing" };
1644
+ // Not an anomaly, and specifically NOT a lost payload: every HTTP-dispatched
1645
+ // background run writes `dispatch_payload` in the same INSERT that creates
1646
+ // the row, so a payload-less row is one that was never HTTP-dispatched at
1647
+ // all — an in-process automation from `runBackgroundAutomation`. It has no
1648
+ // request body to rehydrate because there was never a request. Naming this
1649
+ // `payload_missing` read as "we are losing payloads" in production
1650
+ // forensics and sent a reader hunting a bug that does not exist.
1651
+ return { outcome: "not_redispatchable" };
1610
1652
  }
1611
1653
  const threadId = row.thread_id;
1612
1654
  const turnId = row.turn_id ?? runId;
@@ -1,4 +1,8 @@
1
1
  import type { ActionChatUIConfig } from "../action-ui.js";
2
+ import {
3
+ formatChatErrorText,
4
+ normalizeChatError,
5
+ } from "../client/error-format.js";
2
6
  import {
3
7
  isCredentialGapCodeAgentEvent,
4
8
  normalizeCodeAgentTranscript,
@@ -140,12 +144,29 @@ export function buildAssistantMessage(
140
144
  }
141
145
  };
142
146
 
147
+ // Index of the last event that is not a `clear`. Everything after it is a
148
+ // trailing run of clears with no successor chunk to re-emit what they wipe.
149
+ let lastNonClearIndex = events.length - 1;
150
+ while (
151
+ lastNonClearIndex >= 0 &&
152
+ events[lastNonClearIndex]?.event.type === "clear"
153
+ ) {
154
+ lastNonClearIndex -= 1;
155
+ }
156
+
143
157
  for (const [index, { event }] of events.entries()) {
144
158
  if (event.type === "clear") {
145
159
  // A live stream always follows `clear` with the chunk that re-emits the
146
160
  // wiped content. A rebuild has no successor, so applying a TRAILING
147
161
  // clear can only destroy the transcript permanently.
148
- if (index === events.length - 1) continue;
162
+ //
163
+ // The whole trailing RUN has to be skipped, not just the final element:
164
+ // each failed engine attempt emits one `clear`, so three failed attempts
165
+ // in a row is the common shape, and skipping only the last still applied
166
+ // the other two. When the run made no tool calls that emptied `content`
167
+ // entirely and this builder returned null — the user's message was left
168
+ // with no assistant reply at all.
169
+ if (index > lastNonClearIndex) continue;
149
170
  clearAssistantDraftContent(content);
150
171
  continue;
151
172
  }
@@ -248,13 +269,24 @@ export function buildAssistantMessage(
248
269
  if (event.errorCode === "run_timeout" && event.recoverable) {
249
270
  continue;
250
271
  }
272
+ // Mirror the live client (client/sse-event-processor.ts): route the raw
273
+ // provider/engine string through the same friendly-copy layer before it
274
+ // ever becomes persisted chat text, and keep the raw text only in
275
+ // `details`. Without this, a rebuild (background run, reconnect, poller,
276
+ // webhook turn) dumps whatever the provider sent — a JSON error body, an
277
+ // SSL handshake failure — straight into the user-visible transcript.
278
+ const normalized = normalizeChatError(event.error, event.errorCode);
251
279
  runError = {
252
- message: event.error,
280
+ message: normalized.message,
253
281
  ...(event.errorCode ? { errorCode: event.errorCode } : {}),
254
- ...(event.details ? { details: event.details } : {}),
282
+ ...((event.details ?? normalized.details)
283
+ ? { details: event.details ?? normalized.details }
284
+ : {}),
255
285
  ...(event.recoverable ? { recoverable: event.recoverable } : {}),
256
286
  };
257
- appendText(`${content.length > 0 ? "\n\n" : ""}Error: ${event.error}`);
287
+ appendText(
288
+ `${content.length > 0 ? "\n\n" : ""}${formatChatErrorText(event.error, event.upgradeUrl, event.errorCode)}`,
289
+ );
258
290
  continue;
259
291
  }
260
292
 
@@ -254,6 +254,19 @@ export const TEMPLATES: TemplateMeta[] = [
254
254
  hidden: true,
255
255
  defaultMode: "dev",
256
256
  },
257
+ {
258
+ name: "factory",
259
+ label: "Factory",
260
+ hint: "Build agent factories with gates you control",
261
+ icon: "Users",
262
+ color: "#7C3AED",
263
+ colorRgb: "124 58 237",
264
+ devPort: 8108,
265
+ prodUrl: "https://agent-native-factory.netlify.app",
266
+ hidden: true,
267
+ defaultMode: "dev",
268
+ core: false,
269
+ },
257
270
  ];
258
271
 
259
272
  /** Return templates visible in user-facing pickers (excludes hidden). */
@@ -712,6 +712,7 @@ export async function runWorkspaceDev(
712
712
  name: workspaceApp.name,
713
713
  description: workspaceApp.description,
714
714
  path: `/${workspaceApp.id}`,
715
+ port: workspaceApp.port,
715
716
  audience: workspaceApp.audience,
716
717
  publicPaths: workspaceApp.publicPaths,
717
718
  protectedPaths: workspaceApp.protectedPaths,
@@ -95,6 +95,7 @@ function isProviderAuthenticationError(
95
95
  /\b(?:http\s*)?401\b.*\b(?:status|unauthorized|authentication|auth|no body)\b/i.test(
96
96
  text,
97
97
  ) ||
98
+ lower.includes("missing authentication header") ||
98
99
  lower.includes("invalid x-api-key") ||
99
100
  lower.includes("invalid api key") ||
100
101
  lower.includes("incorrect api key") ||
@@ -141,6 +142,17 @@ export function normalizeChatError(
141
142
  };
142
143
  }
143
144
 
145
+ // A model/parameter combination this provider will never accept. Retrying is
146
+ // pointless and the raw sentence names an API surface the reader has no way
147
+ // to act on, so say what they can actually change.
148
+ if (code === "provider_config_error") {
149
+ return {
150
+ message:
151
+ "This model can't use tools with the current settings. Switch models in Settings, then retry.",
152
+ details: text,
153
+ };
154
+ }
155
+
144
156
  if (isProviderRateLimit(text, errorCode)) {
145
157
  return {
146
158
  message:
@@ -5,6 +5,7 @@ import {
5
5
  isEncryptedSecretValue,
6
6
  } from "../secrets/crypto.js";
7
7
  import { readAppSecret, type SecretRef } from "../secrets/storage.js";
8
+ import { assertCredentialStoreReadable } from "../server/credential-provider.js";
8
9
  import { getSetting, putSetting, deleteSetting } from "../settings/store.js";
9
10
 
10
11
  const SETTING_PREFIX = "credential:";
@@ -76,6 +77,33 @@ export async function resolveCredentialForScope(
76
77
  return readCredentialSetting(userCredentialSettingKey(ctx.userEmail, key));
77
78
  }
78
79
 
80
+ /**
81
+ * `ctx.orgId` when the caller supplied one, otherwise the org resolved from
82
+ * `ctx.userEmail`'s membership. Interactive requests always supply `orgId`
83
+ * (session/`getOrgContext` backfill it); CLI runs, cron jobs, and any other
84
+ * caller built straight from `getCredentialContext()` outside a request event
85
+ * do not, and previously fell through as "no active org" — invisibly skipping
86
+ * every org-scoped credential a signed-in session could see. Mirrors the
87
+ * fallback already proven in `resolveSecretDetailed`
88
+ * (server/credential-provider.ts).
89
+ */
90
+ async function resolveEffectiveOrgId(
91
+ ctx: CredentialContext,
92
+ ): Promise<{ orgId: string | null; lookupFailed: boolean; cause?: unknown }> {
93
+ if (ctx.orgId) return { orgId: ctx.orgId, lookupFailed: false };
94
+ try {
95
+ const { resolveOrgIdForEmail } = await import("../org/context.js");
96
+ return {
97
+ orgId: await resolveOrgIdForEmail(ctx.userEmail),
98
+ lookupFailed: false,
99
+ };
100
+ } catch (cause) {
101
+ // Membership was unreadable, not merely absent — must not collapse to
102
+ // "caller has no org", which would silently hide every org-scoped row.
103
+ return { orgId: null, lookupFailed: true, cause };
104
+ }
105
+ }
106
+
79
107
  /**
80
108
  * Resolve a credential across the encrypted app_secrets store and the legacy
81
109
  * settings-backed credential store. User overrides win, followed by the
@@ -92,7 +120,12 @@ export async function resolveCredentialForScope(
92
120
  * 5. org-scoped legacy settings credential
93
121
  * 6. solo workspace-scoped app_secrets (`solo:<email>`)
94
122
  *
95
- * Steps 3-5 are skipped without an active org.
123
+ * Steps 3-5 use `ctx.orgId` when given, else the org resolved from
124
+ * `ctx.userEmail` (see `resolveEffectiveOrgId`), and are skipped only when the
125
+ * caller truly has no org. A membership lookup that could not be read throws
126
+ * `CredentialStoreUnavailableError` instead of silently reporting the
127
+ * credential as unset — "the store didn't answer" and "nothing is saved" are
128
+ * different outcomes callers must not conflate.
96
129
  */
97
130
  export async function resolveCredential(
98
131
  key: string,
@@ -109,19 +142,20 @@ export async function resolveCredential(
109
142
  });
110
143
  if (userSetting) return userSetting;
111
144
 
112
- if (ctx.orgId) {
113
- const orgSecret = await readScopedAppSecret(key, "org", ctx.orgId);
145
+ const orgLookup = await resolveEffectiveOrgId(ctx);
146
+ assertCredentialStoreReadable(orgLookup);
147
+ const { orgId } = orgLookup;
148
+
149
+ if (orgId) {
150
+ const orgSecret = await readScopedAppSecret(key, "org", orgId);
114
151
  if (orgSecret) return orgSecret;
115
152
 
116
- const workspaceSecret = await readScopedAppSecret(
117
- key,
118
- "workspace",
119
- ctx.orgId,
120
- );
153
+ const workspaceSecret = await readScopedAppSecret(key, "workspace", orgId);
121
154
  if (workspaceSecret) return workspaceSecret;
122
155
 
123
156
  const orgSetting = await resolveCredentialForScope(key, {
124
157
  ...ctx,
158
+ orgId,
125
159
  scope: "org",
126
160
  });
127
161
  if (orgSetting) return orgSetting;
@@ -156,7 +190,9 @@ export async function resolveCredential(
156
190
  * org the caller already belongs to), and never return the owning account, how
157
191
  * many rows matched, or any part of the value. Without an active org there is
158
192
  * no boundary to bound the probe to, so it declines to answer rather than
159
- * revealing that some other tenant holds a key of the same name.
193
+ * revealing that some other tenant holds a key of the same name — this
194
+ * includes callers whose `ctx.orgId` is unset AND whose org could not be
195
+ * resolved from `ctx.userEmail` (see `resolveEffectiveOrgId`).
160
196
  *
161
197
  * Returns null when nothing safe and useful can be said.
162
198
  */
@@ -164,10 +200,13 @@ export async function describeCredentialScopeGap(
164
200
  keys: readonly string[],
165
201
  ctx: CredentialContext,
166
202
  ): Promise<string | null> {
167
- if (!ctx?.userEmail || !ctx.orgId) return null;
203
+ if (!ctx?.userEmail) return null;
204
+ const orgLookup = await resolveEffectiveOrgId(ctx);
205
+ if (orgLookup.lookupFailed || !orgLookup.orgId) return null;
206
+ const scopedCtx: CredentialContext = { ...ctx, orgId: orgLookup.orgId };
168
207
 
169
208
  for (const key of keys) {
170
- if (await hasForeignPersonalCredentialInOrg(key, ctx)) {
209
+ if (await hasForeignPersonalCredentialInOrg(key, scopedCtx)) {
171
210
  return (
172
211
  `A "${key}" key is saved in this workspace with Personal scope. ` +
173
212
  `Personal keys are readable only by their own owner's signed-in sessions, ` +
@@ -177,7 +216,7 @@ export async function describeCredentialScopeGap(
177
216
  );
178
217
  }
179
218
 
180
- const holder = await findMemberOrgHoldingCredential(key, ctx);
219
+ const holder = await findMemberOrgHoldingCredential(key, scopedCtx);
181
220
  if (holder) {
182
221
  return (
183
222
  `A "${key}" key is saved in the ${holder} organization, but this ` +
@@ -167,6 +167,21 @@ function normalizeLookupHostname(hostname: string): string {
167
167
  return hostname.toLowerCase().replace(/^\[|\]$/g, "");
168
168
  }
169
169
 
170
+ function loopbackHostnameVariants(hostname: string): string[] {
171
+ const normalized = normalizeLookupHostname(hostname);
172
+ if (
173
+ normalized !== "localhost" &&
174
+ normalized !== "127.0.0.1" &&
175
+ normalized !== "::1"
176
+ ) {
177
+ return [normalized];
178
+ }
179
+ // Local workspace manifests can identify the same child server as
180
+ // localhost, 127.0.0.1, or ::1. They are equivalent only for loopback; do
181
+ // not alias arbitrary private or public hostnames.
182
+ return ["localhost", "127.0.0.1", "::1"];
183
+ }
184
+
170
185
  function normalizeAllowedPrivateOriginKeys(
171
186
  origins: readonly string[],
172
187
  ): Set<string> {
@@ -178,7 +193,30 @@ function normalizeAllowedPrivateOriginKeys(
178
193
  continue;
179
194
  }
180
195
  const port = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
181
- keys.add(`${normalizeLookupHostname(parsed.hostname)}:${port}`);
196
+ for (const hostname of loopbackHostnameVariants(parsed.hostname)) {
197
+ keys.add(`${hostname}:${port}`);
198
+ }
199
+ } catch {
200
+ // coercion-ok: malformed deployment configuration is omitted, preserving the fail-closed private-IP guard.
201
+ }
202
+ }
203
+ return keys;
204
+ }
205
+
206
+ function normalizeAllowedPrivateOriginOriginKeys(
207
+ origins: readonly string[],
208
+ ): Set<string> {
209
+ const keys = new Set<string>();
210
+ for (const origin of origins) {
211
+ try {
212
+ const parsed = new URL(origin);
213
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
214
+ continue;
215
+ }
216
+ const port = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
217
+ for (const hostname of loopbackHostnameVariants(parsed.hostname)) {
218
+ keys.add(`${parsed.protocol}//${hostname}:${port}`);
219
+ }
182
220
  } catch {
183
221
  // Ignore malformed deployment configuration and retain the private-IP guard.
184
222
  }
@@ -309,21 +347,17 @@ export async function ssrfSafeFetch(
309
347
  const dispatcher =
310
348
  (await createSsrfSafeDispatcher(options.allowedPrivateOrigins)) ??
311
349
  undefined;
312
- const allowedPrivateOrigins = new Set(
313
- (options.allowedPrivateOrigins ?? [])
314
- .map((origin) => {
315
- try {
316
- return new URL(origin).origin;
317
- } catch {
318
- return "";
319
- }
320
- })
321
- .filter(Boolean),
350
+ const allowedPrivateOrigins = normalizeAllowedPrivateOriginOriginKeys(
351
+ options.allowedPrivateOrigins ?? [],
322
352
  );
323
353
  const isAllowedPrivateOrigin = (candidate: string): boolean => {
324
354
  if (allowedPrivateOrigins.size === 0) return false;
325
355
  try {
326
- return allowedPrivateOrigins.has(new URL(candidate).origin);
356
+ const parsed = new URL(candidate);
357
+ const port = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
358
+ return allowedPrivateOrigins.has(
359
+ `${parsed.protocol}//${normalizeLookupHostname(parsed.hostname)}:${port}`,
360
+ );
327
361
  } catch {
328
362
  return false;
329
363
  }
@@ -11,7 +11,7 @@ import { consumeIntegrationAwaitingInput } from "../awaiting-input-store.js";
11
11
  import { createIntegrationControl } from "../controls-store.js";
12
12
  import {
13
13
  getActiveIntegrationInstallationByKey,
14
- getActiveIntegrationInstallationForTenant,
14
+ listActiveIntegrationInstallationsForTenant,
15
15
  listIntegrationInstallations,
16
16
  resolveIntegrationTokenBundle,
17
17
  } from "../installations-store.js";
@@ -666,13 +666,18 @@ export function slackAdapter(
666
666
  channelId: target.destination,
667
667
  threadTs: target.threadRef,
668
668
  teamId: target.tenantId,
669
+ installationKey: target.installationKey,
669
670
  },
670
671
  tenantId: target.tenantId,
671
672
  timestamp: Date.now(),
672
673
  };
673
674
  const token = await resolveBotToken(targetContext);
674
675
  if (!token) {
675
- console.error("[slack] SLACK_BOT_TOKEN not configured");
676
+ console.error(
677
+ "[slack] no bot token for outbound target" +
678
+ (target.tenantId ? ` (tenant ${target.tenantId})` : "") +
679
+ "; set SLACK_BOT_TOKEN or pass installationKey to name the app",
680
+ );
676
681
  return;
677
682
  }
678
683
 
@@ -808,25 +813,42 @@ async function resolveManagedSlackBotToken(
808
813
  typeof incoming.platformContext.enterpriseId === "string"
809
814
  ? incoming.platformContext.enterpriseId
810
815
  : undefined;
816
+ const installationKeyHint =
817
+ typeof incoming.platformContext.installationKey === "string"
818
+ ? incoming.platformContext.installationKey
819
+ : undefined;
811
820
  if (!teamId && !enterpriseId) return undefined;
812
821
  try {
813
- let installation = apiAppId
822
+ let installation = installationKeyHint
814
823
  ? await getActiveIntegrationInstallationByKey(
815
824
  "slack",
816
- slackInstallationKey({ teamId, enterpriseId, apiAppId }),
825
+ installationKeyHint,
817
826
  )
818
827
  : null;
819
- if (!installation && !apiAppId && teamId) {
820
- installation = await getActiveIntegrationInstallationForTenant(
828
+ if (!installation && apiAppId) {
829
+ installation = await getActiveIntegrationInstallationByKey(
821
830
  "slack",
822
- teamId,
831
+ slackInstallationKey({ teamId, enterpriseId, apiAppId }),
823
832
  );
824
833
  }
825
- if (!installation && !apiAppId && enterpriseId) {
826
- installation = await getActiveIntegrationInstallationForTenant(
834
+ if (!installation && !apiAppId) {
835
+ // Without an app id the tenant can match several connected Slack apps.
836
+ // Sending as an arbitrary one posts under the wrong bot identity, so
837
+ // only proceed when the tenant resolves to exactly one installation.
838
+ const tenant = teamId ?? enterpriseId!;
839
+ const candidates = await listActiveIntegrationInstallationsForTenant(
827
840
  "slack",
828
- enterpriseId,
841
+ tenant,
829
842
  );
843
+ if (candidates.length > 1) {
844
+ console.error(
845
+ `[slack] ${candidates.length} connected Slack apps for tenant ${tenant}; ` +
846
+ `cannot choose one without an app id. Pass installationKey on the outbound target. ` +
847
+ `Candidates: ${candidates.map((c) => c.installationKey).join(", ")}`,
848
+ );
849
+ return undefined;
850
+ }
851
+ installation = candidates[0] ?? null;
830
852
  }
831
853
  const key =
832
854
  installation?.installationKey ??
@@ -1,3 +1,4 @@
1
+ import { isInBackgroundFunctionRuntime } from "../agent/durable-background.js";
1
2
  import { createAnthropicEngine } from "../agent/engine/index.js";
2
3
  import type { EngineMessage } from "../agent/engine/types.js";
3
4
  import {
@@ -518,7 +519,17 @@ async function processComment(
518
519
  signal,
519
520
  },
520
521
  undefined,
521
- { useHostedDefault: true },
522
+ // Same omission the scheduled-job runner had: without this, a
523
+ // poller-driven reply inherits the interactive clamp (40s soft
524
+ // timeout, a no-progress backstop at 0.75x that, 6 continuations)
525
+ // even though no client is waiting on a response. Uses the runtime
526
+ // check rather than a hardcoded `true` — matching webhook-handler.ts
527
+ // in this same subsystem — because unlike the job runner there is no
528
+ // hard-abort cap here to bound a wider ceiling.
529
+ {
530
+ useHostedDefault: true,
531
+ backgroundFunction: isInBackgroundFunctionRuntime(),
532
+ },
522
533
  ),
523
534
  );
524
535
  },