@agent-native/core 0.146.8 → 0.148.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 (317) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +115 -68
  3. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +18 -6
  4. package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  5. package/corpus/templates/analytics/DEVELOPING.md +50 -3
  6. package/corpus/templates/analytics/actions/purge-first-party-analytics-postgres.ts +190 -0
  7. package/corpus/templates/analytics/app/i18n/index.ts +14 -29
  8. package/corpus/templates/analytics/app/i18n-data.ts +10 -10
  9. package/corpus/templates/analytics/app/pages/Settings.tsx +5 -21
  10. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -3
  11. package/corpus/templates/analytics/app/routes/status.$slug.tsx +4 -2
  12. package/corpus/templates/analytics/scripts/first-party-analytics-bigquery-backfill.ts +253 -24
  13. package/corpus/templates/analytics/server/db/schema.ts +23 -0
  14. package/corpus/templates/analytics/server/handlers/first-party-analytics.ts +3 -1
  15. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +1 -1
  16. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +113 -13
  17. package/corpus/templates/analytics/server/lib/first-party-analytics-purge.ts +140 -0
  18. package/corpus/templates/analytics/server/lib/first-party-analytics-volume.ts +207 -0
  19. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +40 -9
  20. package/corpus/templates/analytics/server/lib/gcloud.ts +148 -20
  21. package/corpus/templates/analytics/server/plugins/db.ts +42 -0
  22. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +115 -68
  23. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +18 -6
  24. package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  25. package/corpus/templates/assets/app/i18n/index.ts +14 -29
  26. package/corpus/templates/brain/.agents/skills/adding-a-feature/SKILL.md +9 -0
  27. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +115 -68
  28. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +18 -6
  29. package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  30. package/corpus/templates/brain/app/i18n/index.ts +14 -29
  31. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +115 -68
  32. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +18 -6
  33. package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  34. package/corpus/templates/chat/.agents/skills/adding-a-feature/SKILL.md +9 -0
  35. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
  36. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +18 -6
  37. package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  38. package/corpus/templates/chat/app/i18n/index.ts +14 -29
  39. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +115 -68
  40. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +18 -6
  41. package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  42. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +2 -7
  43. package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +18 -13
  44. package/corpus/templates/clips/app/components/settings/ai-setup-section.tsx +87 -370
  45. package/corpus/templates/clips/app/components/settings/slack-section.tsx +59 -101
  46. package/corpus/templates/clips/app/components/settings/video-storage-section.tsx +136 -213
  47. package/corpus/templates/clips/app/i18n/en-US.ts +11 -8
  48. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +16 -4
  49. package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +14 -5
  50. package/corpus/templates/clips/desktop/src/app.tsx +1 -1
  51. package/corpus/templates/clips/package.json +1 -0
  52. package/corpus/templates/clips/react-router.config.ts +7 -0
  53. package/corpus/templates/clips/server/plugins/auth.ts +6 -1
  54. package/corpus/templates/clips/shared/prerendered-public-paths.ts +21 -0
  55. package/corpus/templates/clips/shared/share-meta.ts +8 -3
  56. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +115 -68
  57. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +18 -6
  58. package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  59. package/corpus/templates/content/app/i18n/index.ts +14 -29
  60. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +18 -6
  61. package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  62. package/corpus/templates/crm/app/i18n/index.ts +14 -29
  63. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +115 -68
  64. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +18 -6
  65. package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  66. package/corpus/templates/design/actions/analyze-brand-assets.ts +15 -15
  67. package/corpus/templates/design/actions/import-code.ts +2 -43
  68. package/corpus/templates/design/app/i18n/index.ts +14 -29
  69. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +115 -68
  70. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +18 -6
  71. package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  72. package/corpus/templates/dispatch/app/chat-first-pages.ts +747 -0
  73. package/corpus/templates/dispatch/app/i18n-data.ts +182 -1
  74. package/corpus/templates/dispatch/app/routes/settings.tsx +62 -1
  75. package/corpus/templates/dispatch/changelog/2026-08-08-chat-first-navigation-keeps-conversations-central-while-open.md +6 -0
  76. package/corpus/templates/factory/.agents/skills/adding-a-feature/SKILL.md +9 -0
  77. package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +115 -68
  78. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +18 -6
  79. package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  80. package/corpus/templates/factory/AGENTS.md +40 -49
  81. package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +66 -0
  82. package/corpus/templates/factory/actions/evaluate-triage-item.ts +37 -0
  83. package/corpus/templates/factory/actions/get-slack-feedback-context.ts +20 -0
  84. package/corpus/templates/factory/actions/get-triage-item.ts +25 -4
  85. package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +55 -0
  86. package/corpus/templates/factory/actions/ingest-github-observation.ts +21 -0
  87. package/corpus/templates/factory/actions/list-factory-audit.ts +118 -0
  88. package/corpus/templates/factory/actions/list-factory-automations.ts +2 -1
  89. package/corpus/templates/factory/actions/list-triage-items.ts +24 -2
  90. package/corpus/templates/factory/actions/poll-github-sources.ts +60 -0
  91. package/corpus/templates/factory/actions/poll-sentry-errors.ts +39 -0
  92. package/corpus/templates/factory/actions/poll-slack-channel.ts +45 -1
  93. package/corpus/templates/factory/actions/run-factory-automation.ts +1 -1
  94. package/corpus/templates/factory/actions/save-factory-automation.ts +2 -2
  95. package/corpus/templates/factory/actions/start-builder-for-item.ts +51 -0
  96. package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +685 -0
  97. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +6 -0
  98. package/corpus/templates/factory/app/i18n/en-US.ts +25 -0
  99. package/corpus/templates/factory/app/routes/factory.tsx +203 -118
  100. package/corpus/templates/factory/changelog/2026-08-08-scheduler-health-moves-to-settings.md +6 -0
  101. package/corpus/templates/factory/server/db/schema.ts +37 -0
  102. package/corpus/templates/factory/server/lib/require-factory-automation.ts +5 -1
  103. package/corpus/templates/factory/server/plugins/factory-migrations.ts +29 -0
  104. package/corpus/templates/factory/server/triage/audit.ts +79 -0
  105. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +115 -68
  106. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +18 -6
  107. package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  108. package/corpus/templates/forms/app/i18n/index.ts +14 -29
  109. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +115 -68
  110. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +18 -6
  111. package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  112. package/corpus/templates/macros/app/i18n/index.ts +14 -29
  113. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +115 -68
  114. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +18 -6
  115. package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  116. package/corpus/templates/mail/app/i18n/index.ts +14 -29
  117. package/corpus/templates/plan/.agents/skills/adding-a-feature/SKILL.md +9 -0
  118. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +115 -68
  119. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +18 -6
  120. package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  121. package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +7 -7
  122. package/corpus/templates/plan/app/i18n/index.ts +14 -29
  123. package/corpus/templates/plan/app/lib/plan-document-title.ts +3 -1
  124. package/corpus/templates/plan/app/root.tsx +1 -0
  125. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +115 -68
  126. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +18 -6
  127. package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  128. package/corpus/templates/slides/actions/analyze-brand-assets.ts +15 -15
  129. package/corpus/templates/slides/actions/import-code.ts +2 -43
  130. package/corpus/templates/slides/app/i18n/index.ts +14 -29
  131. package/corpus/templates/slides/server/routes/api/preview-images.get.ts +4 -1
  132. package/corpus/templates/tasks/.agents/skills/adding-a-feature/SKILL.md +9 -0
  133. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +115 -68
  134. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +18 -6
  135. package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  136. package/corpus/templates/tasks/app/components/layout/HeaderActions.tsx +1 -84
  137. package/corpus/templates/tasks/app/hooks/use-mobile.tsx +1 -21
  138. package/corpus/templates/tasks/app/i18n/index.ts +14 -29
  139. package/dist/agent/engine/ai-sdk-engine.js +2 -1
  140. package/dist/agent/engine/builder-engine.d.ts +11 -1
  141. package/dist/agent/engine/builder-engine.js +11 -4
  142. package/dist/agent/engine/openai-compatible-endpoint.d.ts +2 -0
  143. package/dist/agent/engine/openai-compatible-endpoint.js +5 -1
  144. package/dist/agent/engine/provider-endpoint-validation.d.ts +9 -0
  145. package/dist/agent/engine/provider-endpoint-validation.js +26 -0
  146. package/dist/agent/engine/provider-env-vars.d.ts +1 -1
  147. package/dist/agent/engine/provider-env-vars.js +1 -1
  148. package/dist/agent/engine/registry.d.ts +8 -6
  149. package/dist/agent/engine/registry.js +37 -24
  150. package/dist/agent/tool-search.d.ts +1 -0
  151. package/dist/agent/tool-search.js +17 -1
  152. package/dist/automations/service.js +4 -10
  153. package/dist/brand-kit/brand-signals.d.ts +13 -0
  154. package/dist/brand-kit/brand-signals.js +15 -0
  155. package/dist/brand-kit/index.d.ts +2 -2
  156. package/dist/brand-kit/index.js +1 -1
  157. package/dist/chat-threads/store.d.ts +2 -0
  158. package/dist/chat-threads/store.js +1 -1
  159. package/dist/cli/agent-plugin.d.ts +5 -2
  160. package/dist/cli/agent-plugin.js +17 -9
  161. package/dist/cli/code-agent-executor.js +25 -5
  162. package/dist/cli/code-agent-mcp-config.d.ts +21 -0
  163. package/dist/cli/code-agent-mcp-config.js +103 -0
  164. package/dist/cli/plan-blocks.d.ts +3 -18
  165. package/dist/cli/plan-blocks.js +2 -95
  166. package/dist/cli/plan-publish-store.d.ts +2 -61
  167. package/dist/cli/plan-publish-store.js +2 -127
  168. package/dist/cli/skills-content/connection.d.ts +1 -1
  169. package/dist/cli/skills-content/connection.js +1 -52
  170. package/dist/cli/skills-content/visual-recap-skill.d.ts +1 -1
  171. package/dist/cli/skills-content/visual-recap-skill.js +1 -553
  172. package/dist/client/NewWorkspaceAppFlow.js +4 -1
  173. package/dist/client/agent-chat/index.d.ts +4 -1
  174. package/dist/client/agent-chat/index.js +4 -1
  175. package/dist/client/agent-engine-key.d.ts +14 -4
  176. package/dist/client/agent-engine-key.js +51 -5
  177. package/dist/client/agent-page/AgentTabsPage.d.ts +1 -0
  178. package/dist/client/agent-page/AgentTabsPage.js +4 -3
  179. package/dist/client/agent-provider-catalog.d.ts +20 -0
  180. package/dist/client/agent-provider-catalog.js +100 -0
  181. package/dist/client/app-providers.d.ts +3 -1
  182. package/dist/client/app-providers.js +50 -5
  183. package/dist/client/blocks/index.d.ts +1 -0
  184. package/dist/client/blocks/index.js +1 -0
  185. package/dist/client/chat/run-recovery.js +4 -48
  186. package/dist/client/chat/runtime.js +63 -0
  187. package/dist/client/chat-first/agents-pane.d.ts +2 -0
  188. package/dist/client/chat-first/agents-pane.js +6 -0
  189. package/dist/client/chat-first/app-pane.d.ts +2 -0
  190. package/dist/client/chat-first/app-pane.js +6 -0
  191. package/dist/client/chat-first/apps-rail.d.ts +2 -0
  192. package/dist/client/chat-first/apps-rail.js +103 -0
  193. package/dist/client/chat-first/browser-pane.d.ts +2 -0
  194. package/dist/client/chat-first/browser-pane.js +64 -0
  195. package/dist/client/chat-first/chat-history.d.ts +12 -0
  196. package/dist/client/chat-first/chat-history.js +10 -0
  197. package/dist/client/chat-first/copy.d.ts +2 -0
  198. package/dist/client/chat-first/copy.js +117 -0
  199. package/dist/client/chat-first/index.d.ts +13 -0
  200. package/dist/client/chat-first/index.js +12 -0
  201. package/dist/client/chat-first/primary-nav.d.ts +8 -0
  202. package/dist/client/chat-first/primary-nav.js +6 -0
  203. package/dist/client/chat-first/session-watch-pane.d.ts +2 -0
  204. package/dist/client/chat-first/session-watch-pane.js +9 -0
  205. package/dist/client/chat-first/surface-panel.d.ts +9 -0
  206. package/dist/client/chat-first/surface-panel.js +9 -0
  207. package/dist/client/chat-first/surface-tabs.d.ts +9 -0
  208. package/dist/client/chat-first/surface-tabs.js +74 -0
  209. package/dist/client/chat-first/types.d.ts +74 -0
  210. package/dist/client/chat-first/types.js +1 -0
  211. package/dist/client/chat-first-agent-activity.d.ts +11 -0
  212. package/dist/client/chat-first-agent-activity.js +51 -0
  213. package/dist/client/chat-first-surface-panel-toggle.d.ts +5 -0
  214. package/dist/client/chat-first-surface-panel-toggle.js +6 -0
  215. package/dist/client/chat-first.d.ts +208 -0
  216. package/dist/client/chat-first.js +817 -0
  217. package/dist/client/extensions/ExtensionViewer.js +8 -1
  218. package/dist/client/i18n.d.ts +14 -0
  219. package/dist/client/i18n.js +18 -0
  220. package/dist/client/resources/McpIntegrationDialog.js +3 -2
  221. package/dist/client/resources/McpServerDetail.js +6 -11
  222. package/dist/client/resources/index.d.ts +1 -0
  223. package/dist/client/resources/index.js +1 -0
  224. package/dist/client/resources/use-mcp-servers.d.ts +20 -0
  225. package/dist/client/resources/use-mcp-servers.js +82 -46
  226. package/dist/client/settings/ProviderSetupForm.d.ts +21 -0
  227. package/dist/client/settings/ProviderSetupForm.js +145 -0
  228. package/dist/client/settings/SettingsPanel.js +52 -49
  229. package/dist/client/settings/index.d.ts +3 -0
  230. package/dist/client/settings/index.js +3 -0
  231. package/dist/coding-tools/run-code.d.ts +5 -0
  232. package/dist/coding-tools/run-code.js +196 -36
  233. package/dist/coding-tools/tool-orchestration.d.ts +10 -0
  234. package/dist/coding-tools/tool-orchestration.js +107 -0
  235. package/dist/collab/routes.d.ts +1 -1
  236. package/dist/collab/struct-routes.d.ts +1 -1
  237. package/dist/jobs/background-automation-runner.js +19 -2
  238. package/dist/jobs/frontmatter.d.ts +9 -0
  239. package/dist/jobs/frontmatter.js +16 -0
  240. package/dist/jobs/scheduler-health.d.ts +20 -0
  241. package/dist/jobs/scheduler-health.js +86 -1
  242. package/dist/jobs/scheduler.js +46 -9
  243. package/dist/jobs/tools.d.ts +1 -1
  244. package/dist/jobs/tools.js +2 -7
  245. package/dist/localization/default-messages.d.ts +20 -0
  246. package/dist/localization/default-messages.js +25 -5
  247. package/dist/mcp-client/routes.d.ts +3 -0
  248. package/dist/mcp-client/routes.js +14 -0
  249. package/dist/notifications/routes.d.ts +1 -1
  250. package/dist/observability/routes.d.ts +3 -3
  251. package/dist/onboarding/app-profile.js +1 -1
  252. package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
  253. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  254. package/dist/scripts/agent-engines/test-agent-engine.js +34 -10
  255. package/dist/secrets/routes.d.ts +6 -6
  256. package/dist/server/agent-chat/run-code-tools.d.ts +1 -1
  257. package/dist/server/agent-chat/run-code-tools.js +3 -1
  258. package/dist/server/agent-chat-plugin.js +6 -6
  259. package/dist/server/agent-engine-api-key-route.js +43 -12
  260. package/dist/server/agents-bundle.d.ts +2 -0
  261. package/dist/server/agents-bundle.js +32 -2
  262. package/dist/server/auth.js +21 -5
  263. package/dist/server/credential-provider.d.ts +8 -2
  264. package/dist/server/credential-provider.js +14 -9
  265. package/dist/server/design-token-utils.d.ts +18 -0
  266. package/dist/server/design-token-utils.js +31 -0
  267. package/dist/server/onboarding-html.js +22 -2
  268. package/dist/server/transcribe-voice.d.ts +1 -1
  269. package/dist/shared/chat-first-app-creation.d.ts +19 -0
  270. package/dist/shared/chat-first-app-creation.js +75 -0
  271. package/dist/shared/document-title.d.ts +7 -0
  272. package/dist/shared/document-title.js +29 -0
  273. package/dist/shared/index.d.ts +2 -0
  274. package/dist/shared/index.js +2 -0
  275. package/dist/styles/chat-history-list.css +16 -0
  276. package/dist/templates/chat/.agents/skills/adding-a-feature/SKILL.md +9 -0
  277. package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
  278. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +18 -6
  279. package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  280. package/dist/templates/chat/app/i18n/index.ts +14 -29
  281. package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +9 -0
  282. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +115 -68
  283. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +18 -6
  284. package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  285. package/dist/templates/default/app/i18n/index.ts +14 -29
  286. package/dist/templates/default/react-router.config.ts +18 -0
  287. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +18 -6
  288. package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  289. package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +9 -0
  290. package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +22 -9
  291. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +115 -68
  292. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +18 -6
  293. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  294. package/dist/templates/workspace-core/.agents/skills/workspace-conventions/SKILL.md +7 -0
  295. package/dist/triggers/dispatcher.js +1 -8
  296. package/docs/content/environment-variables.mdx +39 -38
  297. package/package.json +5 -4
  298. package/src/templates/chat/.agents/skills/adding-a-feature/SKILL.md +9 -0
  299. package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
  300. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +18 -6
  301. package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  302. package/src/templates/chat/app/i18n/index.ts +14 -29
  303. package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +9 -0
  304. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +115 -68
  305. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +18 -6
  306. package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  307. package/src/templates/default/app/i18n/index.ts +14 -29
  308. package/src/templates/default/react-router.config.ts +18 -0
  309. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +18 -6
  310. package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  311. package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +9 -0
  312. package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +22 -9
  313. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +115 -68
  314. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +18 -6
  315. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +8 -1
  316. package/src/templates/workspace-core/.agents/skills/workspace-conventions/SKILL.md +7 -0
  317. package/corpus/templates/plan/app/components/plan/wireframe/sanitize-html.ts +0 -343
@@ -154,6 +154,31 @@ const messages = {
154
154
  metricRuns: "Runs",
155
155
  rulesTab: "Rules",
156
156
  automationsTab: "Automations",
157
+ auditTab: "Audit",
158
+ auditTitle: "Run audit",
159
+ auditDescription:
160
+ "See exactly which sources the agent inspected, what it decided, and which actions it took.",
161
+ auditRefresh: "Refresh",
162
+ auditLoadError: "Could not load the run audit.",
163
+ auditEmpty: "No automation runs have been recorded yet.",
164
+ auditRuns: "Recent runs",
165
+ auditEventsShort: "events",
166
+ auditActionsShort: "actions",
167
+ auditRunDetail: "Evidence and decisions recorded for this run.",
168
+ auditStarted: "Started",
169
+ auditFinished: "Finished",
170
+ auditItems: "Items",
171
+ auditRunError: "Run error",
172
+ auditEvidence: "Evidence and decisions",
173
+ auditNoEvents:
174
+ "No item-level audit events were recorded. Open the agent thread for the full trace.",
175
+ auditOpenThread: "Open thread",
176
+ auditOpenItem: "Open item",
177
+ auditOpenSource: "Open source",
178
+ auditInternal: "Factory",
179
+ auditClearBug: "Clear bug",
180
+ auditUxImpact: "UX impact",
181
+ auditOwnerArea: "Owner area",
157
182
  automationsTitle: "Automations",
158
183
  automationsDescription: "Schedule and run Factory prompts.",
159
184
  automationsLoading: "Loading automations...",
@@ -10,10 +10,12 @@ import {
10
10
  IconLoader2,
11
11
  IconPlayerPlay,
12
12
  IconPlus,
13
+ IconRefresh,
13
14
  } from "@tabler/icons-react";
14
15
  import { useEffect, useMemo, useState } from "react";
15
16
  import { useSearchParams } from "react-router";
16
17
 
18
+ import { FactoryAuditView } from "@/components/factory/FactoryAuditView";
17
19
  import {
18
20
  FactoryCanvas,
19
21
  type FactoryCanvasEdge,
@@ -111,7 +113,13 @@ type TriageConfig = {
111
113
  };
112
114
 
113
115
  type Verdict = "correct" | "incorrect" | "uncertain";
114
- type WorkspaceTab = "map" | "inbox" | "rules" | "automations" | "settings";
116
+ type WorkspaceTab =
117
+ | "map"
118
+ | "inbox"
119
+ | "rules"
120
+ | "automations"
121
+ | "audit"
122
+ | "settings";
115
123
 
116
124
  type FactoryAutomationRun = {
117
125
  id?: string;
@@ -164,6 +172,7 @@ export default function FactoryRoute() {
164
172
  const [dirty, setDirty] = useState(false);
165
173
  const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
166
174
  const [selectedEdgeId, setSelectedEdgeId] = useState<string | null>(null);
175
+ const [auditRefreshToken, setAuditRefreshToken] = useState(0);
167
176
 
168
177
  function setActiveTab(tab: WorkspaceTab) {
169
178
  setSearchParams(
@@ -418,41 +427,62 @@ export default function FactoryRoute() {
418
427
  </Button>
419
428
  </div>
420
429
  </div>
421
- <nav
422
- className="mt-4 flex items-center gap-1 overflow-x-auto"
423
- aria-label={t("factoryRoute.factoryViews")}
424
- >
425
- <TabButton
426
- active={activeTab === "map"}
427
- onClick={() => setActiveTab("map")}
428
- >
429
- Map
430
- </TabButton>
431
- <TabButton
432
- active={activeTab === "inbox"}
433
- onClick={() => setActiveTab("inbox")}
434
- >
435
- Inbox
436
- </TabButton>
437
- <TabButton
438
- active={activeTab === "rules"}
439
- onClick={() => setActiveTab("rules")}
440
- >
441
- {t("factoryRoute.rulesTab")}
442
- </TabButton>
443
- <TabButton
444
- active={activeTab === "automations"}
445
- onClick={() => setActiveTab("automations")}
430
+ <div className="mt-4 flex items-center gap-2">
431
+ <nav
432
+ className="flex min-w-0 flex-1 items-center gap-1 overflow-x-auto"
433
+ aria-label={t("factoryRoute.factoryViews")}
446
434
  >
447
- {t("factoryRoute.automationsTab")}
448
- </TabButton>
449
- <TabButton
450
- active={activeTab === "settings"}
451
- onClick={() => setActiveTab("settings")}
452
- >
453
- Settings
454
- </TabButton>
455
- </nav>
435
+ <TabButton
436
+ active={activeTab === "map"}
437
+ onClick={() => setActiveTab("map")}
438
+ >
439
+ Map
440
+ </TabButton>
441
+ <TabButton
442
+ active={activeTab === "inbox"}
443
+ onClick={() => setActiveTab("inbox")}
444
+ >
445
+ Inbox
446
+ </TabButton>
447
+ <TabButton
448
+ active={activeTab === "rules"}
449
+ onClick={() => setActiveTab("rules")}
450
+ >
451
+ {t("factoryRoute.rulesTab")}
452
+ </TabButton>
453
+ <TabButton
454
+ active={activeTab === "automations"}
455
+ onClick={() => setActiveTab("automations")}
456
+ >
457
+ {t("factoryRoute.automationsTab")}
458
+ </TabButton>
459
+ <TabButton
460
+ active={activeTab === "audit"}
461
+ onClick={() => setActiveTab("audit")}
462
+ >
463
+ {t("factoryRoute.auditTab")}
464
+ </TabButton>
465
+ <TabButton
466
+ active={activeTab === "settings"}
467
+ onClick={() => setActiveTab("settings")}
468
+ >
469
+ Settings
470
+ </TabButton>
471
+ </nav>
472
+ {activeTab === "audit" && (
473
+ <Button
474
+ type="button"
475
+ variant="ghost"
476
+ size="icon"
477
+ className="size-8 shrink-0 text-muted-foreground hover:text-foreground"
478
+ aria-label={t("factoryRoute.auditRefresh")}
479
+ title={t("factoryRoute.auditRefresh")}
480
+ onClick={() => setAuditRefreshToken((current) => current + 1)}
481
+ >
482
+ <IconRefresh className="size-4" />
483
+ </Button>
484
+ )}
485
+ </div>
456
486
  </header>
457
487
 
458
488
  <main className="min-h-0 flex-1 overflow-y-auto">
@@ -526,6 +556,11 @@ export default function FactoryRoute() {
526
556
  <RulesView t={t} />
527
557
  ) : activeTab === "automations" ? (
528
558
  <AutomationsView factoryId={factoryId} t={t} />
559
+ ) : activeTab === "audit" ? (
560
+ <FactoryAuditView
561
+ factoryId={factoryId}
562
+ refreshToken={auditRefreshToken}
563
+ />
529
564
  ) : (
530
565
  <SettingsView t={t} />
531
566
  )}
@@ -559,6 +594,7 @@ function parseWorkspaceTab(value: string | null): WorkspaceTab {
559
594
  return value === "inbox" ||
560
595
  value === "rules" ||
561
596
  value === "automations" ||
597
+ value === "audit" ||
562
598
  value === "settings"
563
599
  ? value
564
600
  : "map";
@@ -589,11 +625,6 @@ function AutomationsView({
589
625
  "list-factory-automations",
590
626
  { factoryId },
591
627
  );
592
- const healthQuery = useActionQuery<FactoryAutomationHealth>(
593
- "get-factory-automation-health",
594
- {},
595
- { refetchInterval: 60_000 },
596
- );
597
628
  const saveMutation = useActionMutation("save-factory-automation");
598
629
  const runMutation = useActionMutation("run-factory-automation");
599
630
  const {
@@ -684,85 +715,17 @@ function AutomationsView({
684
715
  schedule: draft.schedule ?? "",
685
716
  enabled: draft.enabled,
686
717
  });
687
- await Promise.all([automationsQuery.refetch(), healthQuery.refetch()]);
718
+ await automationsQuery.refetch();
688
719
  }
689
720
 
690
721
  async function runAutomation() {
691
722
  if (!draft) return;
692
723
  await runMutation.mutateAsync({ factoryId, automationId: draft.id });
693
- await Promise.all([automationsQuery.refetch(), healthQuery.refetch()]);
724
+ await automationsQuery.refetch();
694
725
  }
695
726
 
696
- const health = healthQuery.data;
697
- const healthLabel = health
698
- ? {
699
- healthy: t("factoryRoute.automationHealthHealthy"),
700
- stale: t("factoryRoute.automationHealthStale"),
701
- error: t("factoryRoute.automationHealthError"),
702
- "no-data": t("factoryRoute.automationHealthNoData"),
703
- }[health.status]
704
- : t("factoryRoute.automationHealthNoData");
705
-
706
727
  return (
707
728
  <div className="space-y-4 p-4 lg:p-6">
708
- <Card>
709
- <CardHeader className="pb-3">
710
- <CardTitle className="text-base">
711
- {t("factoryRoute.automationHealthTitle")}
712
- </CardTitle>
713
- <p className="text-sm text-muted-foreground">
714
- {t("factoryRoute.automationHealthDescription")}
715
- </p>
716
- </CardHeader>
717
- <CardContent className="space-y-2 pt-0 text-sm">
718
- <div className="flex flex-wrap items-center gap-2">
719
- <span className="rounded-full border px-2 py-1 font-medium">
720
- {healthLabel}
721
- </span>
722
- {health?.lastCheckedAt && (
723
- <span className="text-muted-foreground">
724
- {t("factoryRoute.automationLastCheck")}:{" "}
725
- {formatAutomationDate(health.lastCheckedAt)}
726
- </span>
727
- )}
728
- {health?.lastDispatchedAt && (
729
- <span className="text-muted-foreground">
730
- {t("factoryRoute.automationLastDispatch")}:{" "}
731
- {formatAutomationDate(health.lastDispatchedAt)}
732
- </span>
733
- )}
734
- {health?.runtime && (
735
- <span className="text-muted-foreground">
736
- {t("factoryRoute.automationRuntime")}: {health.runtime}
737
- </span>
738
- )}
739
- </div>
740
- {!health?.lastCheckedAt && (
741
- <p className="text-muted-foreground">
742
- {t("factoryRoute.automationHealthNoDataHint")}
743
- </p>
744
- )}
745
- {health?.status === "stale" && (
746
- <p className="text-destructive">
747
- {t("factoryRoute.automationHealthStaleHint")}
748
- </p>
749
- )}
750
- {health?.lastError && (
751
- <p className="text-destructive">
752
- {t("factoryRoute.automationHealthErrorDetail")}:{" "}
753
- {health.lastError}
754
- </p>
755
- )}
756
- {healthQuery.isError && (
757
- <p className="text-destructive">
758
- {t("factoryRoute.automationDiagnosticsLoadError")}{" "}
759
- {healthQuery.error instanceof Error
760
- ? healthQuery.error.message
761
- : String(healthQuery.error)}
762
- </p>
763
- )}
764
- </CardContent>
765
- </Card>
766
729
  <div className="grid gap-4 lg:grid-cols-[minmax(220px,.35fr)_minmax(0,1fr)]">
767
730
  <Card>
768
731
  <CardHeader>
@@ -802,7 +765,7 @@ function AutomationsView({
802
765
  <span className="block truncate text-sm font-medium">
803
766
  {automation.name}
804
767
  </span>
805
- <span className="mt-1 block text-xs text-muted-foreground">
768
+ <span className="mt-1 block truncate text-xs text-muted-foreground">
806
769
  {automation.enabled
807
770
  ? t("factoryRoute.automationEnabled")
808
771
  : t("factoryRoute.automationDisabled")}
@@ -1029,8 +992,11 @@ function formatModelName(model: string | null | undefined) {
1029
992
  }
1030
993
 
1031
994
  function InboxView({ t }: { t: ReturnType<typeof useT> }) {
995
+ const [searchParams, setSearchParams] = useSearchParams();
1032
996
  const [status, setStatus] = useState("");
1033
- const [selectedId, setSelectedId] = useState<string | null>(null);
997
+ const [selectedId, setSelectedId] = useState<string | null>(
998
+ searchParams.get("itemId"),
999
+ );
1034
1000
  const [feedbackNote, setFeedbackNote] = useState("");
1035
1001
  const [verdict, setVerdict] = useState<Verdict | null>(null);
1036
1002
  const listQuery = useActionQuery("list-triage-items", {
@@ -1062,6 +1028,24 @@ function InboxView({ t }: { t: ReturnType<typeof useT> }) {
1062
1028
  const normalizedItems = Array.isArray(items) ? items : (items.items ?? []);
1063
1029
  const selectedItem = detailQuery.data as TriageItem | undefined;
1064
1030
 
1031
+ useEffect(() => {
1032
+ setSelectedId(searchParams.get("itemId"));
1033
+ }, [searchParams]);
1034
+
1035
+ function selectItem(itemId: string) {
1036
+ setSelectedId(itemId);
1037
+ setVerdict(null);
1038
+ setFeedbackNote("");
1039
+ setSearchParams(
1040
+ (current) => {
1041
+ const next = new URLSearchParams(current);
1042
+ next.set("itemId", itemId);
1043
+ return next;
1044
+ },
1045
+ { replace: true },
1046
+ );
1047
+ }
1048
+
1065
1049
  return (
1066
1050
  <div className="grid gap-4 p-4 lg:grid-cols-[minmax(0,1.3fr)_minmax(320px,.7fr)] lg:p-6">
1067
1051
  <Card>
@@ -1119,11 +1103,7 @@ function InboxView({ t }: { t: ReturnType<typeof useT> }) {
1119
1103
  key={id}
1120
1104
  type="button"
1121
1105
  className={`grid w-full gap-3 p-4 text-left transition-colors hover:bg-muted/50 sm:grid-cols-[1.1fr_.7fr_.9fr_1.6fr] ${selectedId === id ? "bg-muted/60" : ""}`}
1122
- onClick={() => {
1123
- setSelectedId(id);
1124
- setVerdict(null);
1125
- setFeedbackNote("");
1126
- }}
1106
+ onClick={() => selectItem(id)}
1127
1107
  >
1128
1108
  <span className="min-w-0">
1129
1109
  <span className="block truncate text-sm font-medium">
@@ -1407,6 +1387,11 @@ function SettingsView({ t }: { t: ReturnType<typeof useT> }) {
1407
1387
  const [automationFailureAlertEmail, setAutomationFailureAlertEmail] =
1408
1388
  useState("");
1409
1389
  const query = useActionQuery("get-triage-config", {});
1390
+ const schedulerHealthQuery = useActionQuery<FactoryAutomationHealth>(
1391
+ "get-factory-automation-health",
1392
+ {},
1393
+ { refetchInterval: 60_000 },
1394
+ );
1410
1395
  const mutation = useActionMutation("save-triage-config");
1411
1396
  useEffect(() => {
1412
1397
  const data = query.data as TriageConfig | undefined;
@@ -1427,7 +1412,7 @@ function SettingsView({ t }: { t: ReturnType<typeof useT> }) {
1427
1412
  setAutomationFailureAlertEmail(data.automationFailureAlertEmail ?? "");
1428
1413
  }, [query.data]);
1429
1414
  return (
1430
- <div className="max-w-4xl p-4 lg:p-6">
1415
+ <div className="max-w-4xl space-y-4 p-4 lg:p-6">
1431
1416
  <Card>
1432
1417
  <CardHeader>
1433
1418
  <CardTitle className="text-base">
@@ -1621,10 +1606,110 @@ function SettingsView({ t }: { t: ReturnType<typeof useT> }) {
1621
1606
  </div>
1622
1607
  </CardContent>
1623
1608
  </Card>
1609
+ <SchedulerHealthStatus
1610
+ health={schedulerHealthQuery.data}
1611
+ isError={schedulerHealthQuery.isError}
1612
+ error={schedulerHealthQuery.error}
1613
+ t={t}
1614
+ />
1624
1615
  </div>
1625
1616
  );
1626
1617
  }
1627
1618
 
1619
+ function SchedulerHealthStatus({
1620
+ health,
1621
+ isError,
1622
+ error,
1623
+ t,
1624
+ }: {
1625
+ health?: FactoryAutomationHealth;
1626
+ isError: boolean;
1627
+ error: unknown;
1628
+ t: ReturnType<typeof useT>;
1629
+ }) {
1630
+ const healthLabel = isError
1631
+ ? t("factoryRoute.automationHealthError")
1632
+ : health
1633
+ ? {
1634
+ healthy: t("factoryRoute.automationHealthHealthy"),
1635
+ stale: t("factoryRoute.automationHealthStale"),
1636
+ error: t("factoryRoute.automationHealthError"),
1637
+ "no-data": t("factoryRoute.automationHealthNoData"),
1638
+ }[health.status]
1639
+ : t("factoryRoute.automationHealthNoData");
1640
+ const hasNoHeartbeat =
1641
+ !isError &&
1642
+ (!health || health.status === "no-data") &&
1643
+ !health?.lastCheckedAt;
1644
+ const hasDiagnostics =
1645
+ isError ||
1646
+ hasNoHeartbeat ||
1647
+ health?.status === "stale" ||
1648
+ health?.status === "error" ||
1649
+ Boolean(health?.lastError);
1650
+
1651
+ return (
1652
+ <section
1653
+ aria-labelledby="factory-scheduler-health"
1654
+ className="border-t px-1 pt-4"
1655
+ >
1656
+ <div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-2">
1657
+ <div className="flex flex-wrap items-center gap-2">
1658
+ <h2
1659
+ id="factory-scheduler-health"
1660
+ className="text-sm font-medium text-muted-foreground"
1661
+ >
1662
+ {t("factoryRoute.automationHealthTitle")}
1663
+ </h2>
1664
+ <span className="rounded-full border px-2 py-0.5 text-xs font-medium">
1665
+ {healthLabel}
1666
+ </span>
1667
+ </div>
1668
+ <div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
1669
+ {health?.lastCheckedAt && (
1670
+ <span>
1671
+ {t("factoryRoute.automationLastCheck")}:{" "}
1672
+ {formatAutomationDate(health.lastCheckedAt)}
1673
+ </span>
1674
+ )}
1675
+ {health?.lastDispatchedAt && (
1676
+ <span>
1677
+ {t("factoryRoute.automationLastDispatch")}:{" "}
1678
+ {formatAutomationDate(health.lastDispatchedAt)}
1679
+ </span>
1680
+ )}
1681
+ </div>
1682
+ </div>
1683
+ {hasDiagnostics && (
1684
+ <div className="mt-2 grid gap-1 text-xs">
1685
+ {hasNoHeartbeat && (
1686
+ <p className="text-muted-foreground">
1687
+ {t("factoryRoute.automationHealthNoDataHint")}
1688
+ </p>
1689
+ )}
1690
+ {health?.status === "stale" && (
1691
+ <p className="text-destructive">
1692
+ {t("factoryRoute.automationHealthStaleHint")}
1693
+ </p>
1694
+ )}
1695
+ {health?.lastError && (
1696
+ <p className="text-destructive">
1697
+ {t("factoryRoute.automationHealthErrorDetail")}:{" "}
1698
+ {health.lastError}
1699
+ </p>
1700
+ )}
1701
+ {isError && (
1702
+ <p className="text-destructive">
1703
+ {t("factoryRoute.automationDiagnosticsLoadError")}{" "}
1704
+ {error instanceof Error ? error.message : String(error)}
1705
+ </p>
1706
+ )}
1707
+ </div>
1708
+ )}
1709
+ </section>
1710
+ );
1711
+ }
1712
+
1628
1713
  function ErrorState({
1629
1714
  message,
1630
1715
  onRetry,
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-08
4
+ ---
5
+
6
+ Scheduler health now stays in Settings until diagnostics are needed.
@@ -132,6 +132,43 @@ export const triageConfig = table("factory_config", {
132
132
  orgId: text("org_id"),
133
133
  });
134
134
 
135
+ export const factoryAuditEvents = table(
136
+ "factory_audit_events",
137
+ {
138
+ id: text("id").primaryKey(),
139
+ automationRunId: text("automation_run_id"),
140
+ automationThreadId: text("automation_thread_id"),
141
+ automationName: text("automation_name"),
142
+ itemId: text("item_id"),
143
+ source: text("source"),
144
+ sourceUrl: text("source_url"),
145
+ action: text("action").notNull(),
146
+ kind: text("kind").notNull(),
147
+ status: text("status").notNull(),
148
+ summary: text("summary").notNull(),
149
+ detailsJson: text("details_json").notNull().default("{}"),
150
+ createdAt: text("created_at").notNull().default(now()),
151
+ ownerEmail: text("owner_email").notNull(),
152
+ orgId: text("org_id"),
153
+ },
154
+ (event) => ({
155
+ orgCreatedIdx: index("factory_audit_events_org_created_idx").on(
156
+ event.orgId,
157
+ event.createdAt,
158
+ ),
159
+ runCreatedIdx: index("factory_audit_events_run_created_idx").on(
160
+ event.orgId,
161
+ event.automationRunId,
162
+ event.createdAt,
163
+ ),
164
+ itemCreatedIdx: index("factory_audit_events_item_created_idx").on(
165
+ event.orgId,
166
+ event.itemId,
167
+ event.createdAt,
168
+ ),
169
+ }),
170
+ );
171
+
135
172
  export const factoryDefinitions = table(
136
173
  "factory_definitions",
137
174
  {
@@ -45,7 +45,11 @@ export async function requireFactoryAutomation(
45
45
 
46
46
  const definition = (
47
47
  await listAutomationDefinitions(
48
- { userEmail: identity.userEmail, orgId: identity.orgId },
48
+ {
49
+ userEmail: identity.userEmail,
50
+ orgId: identity.orgId,
51
+ appId: "factory",
52
+ },
49
53
  "organization",
50
54
  )
51
55
  ).find((entry) => entry.resource.id === lineage.triggerId);
@@ -258,6 +258,35 @@ const migrations = [
258
258
  ALTER TABLE factory_config ADD COLUMN last_automation_failure_alert_at TEXT;
259
259
  `,
260
260
  },
261
+ {
262
+ version: 18,
263
+ name: "factory-audit-events-table",
264
+ sql: `
265
+ CREATE TABLE IF NOT EXISTS factory_audit_events (
266
+ id TEXT PRIMARY KEY,
267
+ automation_run_id TEXT,
268
+ automation_thread_id TEXT,
269
+ automation_name TEXT,
270
+ item_id TEXT,
271
+ source TEXT,
272
+ source_url TEXT,
273
+ action TEXT NOT NULL,
274
+ kind TEXT NOT NULL,
275
+ status TEXT NOT NULL,
276
+ summary TEXT NOT NULL,
277
+ details_json TEXT NOT NULL DEFAULT '{}',
278
+ created_at TEXT NOT NULL,
279
+ owner_email TEXT NOT NULL,
280
+ org_id TEXT
281
+ );
282
+ CREATE INDEX IF NOT EXISTS factory_audit_events_org_created_idx
283
+ ON factory_audit_events (org_id, created_at);
284
+ CREATE INDEX IF NOT EXISTS factory_audit_events_run_created_idx
285
+ ON factory_audit_events (org_id, automation_run_id, created_at);
286
+ CREATE INDEX IF NOT EXISTS factory_audit_events_item_created_idx
287
+ ON factory_audit_events (org_id, item_id, created_at);
288
+ `,
289
+ },
261
290
  ];
262
291
 
263
292
  export const runFactoryMigrations = runMigrations(migrations, {
@@ -0,0 +1,79 @@
1
+ import { randomUUID } from "node:crypto";
2
+
3
+ import type { ActionRunContext } from "@agent-native/core/action";
4
+
5
+ import { getDb } from "../db/index.js";
6
+ import { factoryAuditEvents } from "../db/schema.js";
7
+
8
+ const MAX_SUMMARY_LENGTH = 500;
9
+ const MAX_DETAILS_LENGTH = 4_000;
10
+
11
+ export type FactoryAuditKind =
12
+ | "observed"
13
+ | "read"
14
+ | "decision"
15
+ | "external_action"
16
+ | "governance";
17
+
18
+ export type FactoryAuditStatus = "success" | "error" | "skipped";
19
+
20
+ export interface FactoryAuditInput {
21
+ action: string;
22
+ kind: FactoryAuditKind;
23
+ status?: FactoryAuditStatus;
24
+ itemId?: string | null;
25
+ source?: string | null;
26
+ sourceUrl?: string | null;
27
+ summary: string;
28
+ details?: Record<string, unknown>;
29
+ }
30
+
31
+ function boundedText(value: string, max: number): string {
32
+ const compact = value.replace(/\s+/g, " ").trim();
33
+ return compact.length > max ? `${compact.slice(0, max - 1)}…` : compact;
34
+ }
35
+
36
+ function boundedDetails(value: Record<string, unknown> | undefined): string {
37
+ if (!value) return "{}";
38
+ let serialized: string;
39
+ try {
40
+ serialized = JSON.stringify(value);
41
+ } catch {
42
+ throw new Error("Factory audit details are not serializable.");
43
+ }
44
+ return serialized.length > MAX_DETAILS_LENGTH
45
+ ? JSON.stringify({ truncated: true })
46
+ : serialized;
47
+ }
48
+
49
+ /**
50
+ * Persist a bounded, source-linked explanation of an automation action. The
51
+ * agent run id is the join key to core's automation history; no raw provider
52
+ * payload is copied into this table.
53
+ */
54
+ export async function recordFactoryAudit(
55
+ context: ActionRunContext | undefined,
56
+ identity: { userEmail: string; orgId: string },
57
+ input: FactoryAuditInput,
58
+ ): Promise<void> {
59
+ if (context?.caller !== "automation" || !context.runId) return;
60
+ await getDb()
61
+ .insert(factoryAuditEvents)
62
+ .values({
63
+ id: randomUUID(),
64
+ automationRunId: context.runId,
65
+ automationThreadId: context.threadId ?? null,
66
+ automationName: context.automation?.triggerName ?? null,
67
+ itemId: input.itemId ?? null,
68
+ source: input.source ?? null,
69
+ sourceUrl: input.sourceUrl ?? null,
70
+ action: boundedText(input.action, 120),
71
+ kind: input.kind,
72
+ status: input.status ?? "success",
73
+ summary: boundedText(input.summary, MAX_SUMMARY_LENGTH),
74
+ detailsJson: boundedDetails(input.details),
75
+ createdAt: new Date().toISOString(),
76
+ ownerEmail: identity.userEmail,
77
+ orgId: identity.orgId,
78
+ });
79
+ }