@agent-native/core 0.164.4 → 0.164.12

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 (268) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +84 -0
  3. package/corpus/templates/calendar/AGENTS.md +5 -0
  4. package/corpus/templates/calendar/actions/delete-event.ts +1 -1
  5. package/corpus/templates/calendar/actions/delete-events.ts +508 -0
  6. package/corpus/templates/calendar/actions/event-action-helpers.ts +1 -1
  7. package/corpus/templates/calendar/actions/list-events.ts +53 -0
  8. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +36 -47
  9. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +3 -0
  10. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +18 -1
  11. package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +256 -38
  12. package/corpus/templates/calendar/app/hooks/use-google-auth.ts +43 -3
  13. package/corpus/templates/calendar/app/i18n-data.ts +120 -0
  14. package/corpus/templates/calendar/app/lib/event-form-utils.ts +113 -0
  15. package/corpus/templates/calendar/changelog/2026-08-18-the-agent-can-now-remove-many-meetings-at-once-such-as-every.md +6 -0
  16. package/corpus/templates/calendar/server/handlers/google-auth.ts +119 -3
  17. package/corpus/templates/calendar/server/lib/event-weekday.ts +112 -0
  18. package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
  19. package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
  20. package/corpus/templates/calendar/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
  21. package/corpus/templates/calendar/server/routes/_agent-native/google/auth-url.post.ts +1 -0
  22. package/corpus/templates/clips/app/components/player/video-player.tsx +12 -0
  23. package/corpus/templates/clips/app/routes/_app.dictate.tsx +18 -4
  24. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +114 -20
  25. package/corpus/templates/clips/app/routes/share.$shareId.tsx +83 -78
  26. package/corpus/templates/clips/changelog/2026-08-19-clips-nightly-builds-are-available-for-trying-the-latest-upd.md +6 -0
  27. package/corpus/templates/clips/changelog/2026-08-19-social-preview-video-frames.md +1 -0
  28. package/corpus/templates/clips/desktop/README.md +19 -4
  29. package/corpus/templates/clips/desktop/src/app.tsx +78 -16
  30. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -2
  31. package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +1 -0
  32. package/corpus/templates/clips/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
  33. package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +41 -3
  34. package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.post.ts +1 -0
  35. package/corpus/templates/clips/server/routes/_agent-native/google/callback.get.ts +22 -1
  36. package/corpus/templates/clips/server/routes/api/clips-updater.json.get.ts +41 -22
  37. package/corpus/templates/factory/AGENTS.md +3 -2
  38. package/corpus/templates/factory/actions/get-factory-graph-version.ts +69 -0
  39. package/corpus/templates/factory/actions/list-factory-automations.ts +4 -0
  40. package/corpus/templates/factory/actions/list-factory-graph-versions.ts +82 -0
  41. package/corpus/templates/factory/actions/restore-factory-graph-version.ts +163 -0
  42. package/corpus/templates/factory/actions/save-factory-graph.ts +93 -60
  43. package/corpus/templates/factory/actions/start-builder-for-item.ts +19 -17
  44. package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +100 -36
  45. package/corpus/templates/factory/app/components/factory/FactoryHistoryView.tsx +709 -0
  46. package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +62 -2
  47. package/corpus/templates/factory/app/components/ui/alert-dialog.tsx +1 -0
  48. package/corpus/templates/factory/app/i18n/en-US.ts +47 -0
  49. package/corpus/templates/factory/app/routes/factory.tsx +324 -61
  50. package/corpus/templates/factory/server/connectors/slack.ts +44 -2
  51. package/corpus/templates/factory/server/plugins/agent-chat.ts +12 -8
  52. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +13 -0
  53. package/corpus/templates/factory/server/triage/slack-client.ts +5 -0
  54. package/corpus/templates/factory/server/triage/slack-poller.ts +72 -4
  55. package/corpus/templates/mail/actions/view-screen.ts +1 -1
  56. package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -18
  57. package/corpus/templates/mail/server/handlers/google-auth.ts +125 -24
  58. package/corpus/templates/mail/server/lib/gmail-query.ts +5 -2
  59. package/corpus/templates/mail/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
  60. package/corpus/templates/mail/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
  61. package/corpus/templates/mail/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
  62. package/corpus/templates/mail/server/routes/_agent-native/google/auth-url.post.ts +1 -0
  63. package/corpus/templates/plan/app/pages/PlansPage.tsx +0 -12
  64. package/corpus/templates/plan/changelog/2026-08-19-plan-prompts-now-guide-you-to-connect-ai-before-sending-and-.md +6 -0
  65. package/corpus/templates/slides/actions/approve-deck-access-request.ts +234 -0
  66. package/corpus/templates/slides/actions/get-deck-access-status.ts +35 -2
  67. package/corpus/templates/slides/actions/request-deck-access.ts +263 -64
  68. package/corpus/templates/slides/app/components/editor/MissingDeckAccessPane.tsx +196 -0
  69. package/corpus/templates/slides/app/components/editor/RequestDeckAccessDialog.tsx +101 -0
  70. package/corpus/templates/slides/app/hooks/use-deck-access.ts +7 -4
  71. package/corpus/templates/slides/app/i18n/en-US.ts +29 -6
  72. package/corpus/templates/slides/app/pages/DeckEditor.tsx +147 -169
  73. package/corpus/templates/slides/app/routes/access-request.approve.tsx +250 -0
  74. package/corpus/templates/slides/server/db/schema.ts +8 -0
  75. package/corpus/templates/slides/server/lib/access-request-email.ts +36 -0
  76. package/corpus/templates/slides/server/lib/emails.ts +26 -0
  77. package/corpus/templates/slides/server/plugins/agent-chat.ts +7 -0
  78. package/corpus/templates/slides/server/plugins/db.ts +50 -0
  79. package/corpus/templates/slides/shared/deck-access.ts +31 -0
  80. package/dist/a2a/task-store.d.ts +1 -0
  81. package/dist/a2a/task-store.js +1 -1
  82. package/dist/agent/observational-memory/store.d.ts +1 -0
  83. package/dist/agent/observational-memory/store.js +1 -1
  84. package/dist/agent/run-store.d.ts +1 -0
  85. package/dist/agent/run-store.js +1 -1
  86. package/dist/agent/tool-approval-store.d.ts +1 -0
  87. package/dist/agent/tool-approval-store.js +1 -1
  88. package/dist/application-state/store.d.ts +1 -0
  89. package/dist/application-state/store.js +1 -1
  90. package/dist/browser-sessions/store.d.ts +1 -0
  91. package/dist/browser-sessions/store.js +1 -1
  92. package/dist/checkpoints/store.d.ts +1 -0
  93. package/dist/checkpoints/store.js +1 -1
  94. package/dist/cli/code-agent-schedules.d.ts +11 -0
  95. package/dist/cli/code-agent-schedules.js +44 -5
  96. package/dist/client/AgentPanel.d.ts +5 -1
  97. package/dist/client/AgentPanel.js +2 -2
  98. package/dist/client/AssistantChat.d.ts +5 -1
  99. package/dist/client/AssistantChat.js +40 -6
  100. package/dist/client/EnvironmentBadge.d.ts +7 -13
  101. package/dist/client/EnvironmentBadge.js +43 -92
  102. package/dist/client/agent-chat/index.d.ts +1 -0
  103. package/dist/client/agent-chat/index.js +1 -0
  104. package/dist/client/agent-chat-adapter.js +76 -50
  105. package/dist/client/app-providers.js +4 -4
  106. package/dist/client/chat/message-components.d.ts +4 -0
  107. package/dist/client/chat/message-components.js +28 -1
  108. package/dist/client/chat/run-recovery.js +26 -0
  109. package/dist/client/chat-first/apps-rail.d.ts +1 -0
  110. package/dist/client/chat-first/apps-rail.js +28 -2
  111. package/dist/client/sse-event-processor.js +13 -8
  112. package/dist/client/use-active-agent-chat-run.d.ts +2 -0
  113. package/dist/client/use-active-agent-chat-run.js +22 -0
  114. package/dist/coding-tools/sandbox/executions-store.d.ts +1 -0
  115. package/dist/coding-tools/sandbox/executions-store.js +1 -1
  116. package/dist/collab/awareness-store.d.ts +1 -0
  117. package/dist/collab/awareness-store.js +1 -1
  118. package/dist/collab/routes.d.ts +1 -1
  119. package/dist/collab/storage.d.ts +1 -0
  120. package/dist/collab/storage.js +1 -1
  121. package/dist/collab/struct-routes.d.ts +1 -1
  122. package/dist/db/client.js +3 -3
  123. package/dist/db/ddl-guard.js +8 -0
  124. package/dist/db/migration-runtime.d.ts +23 -0
  125. package/dist/db/migration-runtime.js +41 -0
  126. package/dist/db/migrations.d.ts +1 -2
  127. package/dist/db/migrations.js +2 -24
  128. package/dist/email-catalog/log.d.ts +1 -0
  129. package/dist/email-catalog/log.js +1 -1
  130. package/dist/guards/index.d.ts +1 -0
  131. package/dist/guards/index.js +1 -0
  132. package/dist/guards/release-schema-complete.d.ts +22 -0
  133. package/dist/guards/release-schema-complete.js +144 -0
  134. package/dist/integrations/a2a-continuations-store.d.ts +1 -0
  135. package/dist/integrations/a2a-continuations-store.js +1 -1
  136. package/dist/integrations/awaiting-input-store.d.ts +1 -0
  137. package/dist/integrations/awaiting-input-store.js +1 -1
  138. package/dist/integrations/config-store.d.ts +1 -0
  139. package/dist/integrations/config-store.js +1 -1
  140. package/dist/integrations/controls-store.d.ts +1 -0
  141. package/dist/integrations/controls-store.js +1 -1
  142. package/dist/integrations/identity-links-store.d.ts +1 -0
  143. package/dist/integrations/identity-links-store.js +1 -1
  144. package/dist/integrations/installations-store.d.ts +1 -0
  145. package/dist/integrations/installations-store.js +1 -1
  146. package/dist/integrations/remote-commands-store.d.ts +1 -0
  147. package/dist/integrations/remote-commands-store.js +1 -1
  148. package/dist/integrations/remote-devices-store.d.ts +1 -0
  149. package/dist/integrations/remote-devices-store.js +1 -1
  150. package/dist/integrations/remote-push-store.d.ts +1 -0
  151. package/dist/integrations/remote-push-store.js +1 -1
  152. package/dist/integrations/remote-run-events-store.d.ts +1 -0
  153. package/dist/integrations/remote-run-events-store.js +1 -1
  154. package/dist/integrations/scope-store.d.ts +1 -0
  155. package/dist/integrations/scope-store.js +1 -1
  156. package/dist/integrations/thread-mapping-store.d.ts +1 -0
  157. package/dist/integrations/thread-mapping-store.js +1 -1
  158. package/dist/integrations/usage-budget-store.d.ts +1 -0
  159. package/dist/integrations/usage-budget-store.js +1 -1
  160. package/dist/jobs/background-automation-runner.js +111 -9
  161. package/dist/jobs/run-history.d.ts +1 -0
  162. package/dist/jobs/run-history.js +1 -1
  163. package/dist/jobs/scheduler-health.d.ts +1 -0
  164. package/dist/jobs/scheduler-health.js +1 -1
  165. package/dist/mcp/approval-store.d.ts +1 -0
  166. package/dist/mcp/approval-store.js +1 -1
  167. package/dist/mcp/connect-store.d.ts +1 -0
  168. package/dist/mcp/connect-store.js +1 -1
  169. package/dist/mcp/oauth-store.d.ts +1 -0
  170. package/dist/mcp/oauth-store.js +1 -1
  171. package/dist/notifications/store.d.ts +1 -0
  172. package/dist/notifications/store.js +1 -1
  173. package/dist/oauth-tokens/store.d.ts +1 -0
  174. package/dist/oauth-tokens/store.js +1 -1
  175. package/dist/observability/routes.d.ts +5 -5
  176. package/dist/progress/routes.d.ts +1 -1
  177. package/dist/progress/store.d.ts +1 -0
  178. package/dist/progress/store.js +1 -1
  179. package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
  180. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  181. package/dist/provider-api/corpus-jobs-store.d.ts +1 -0
  182. package/dist/provider-api/corpus-jobs-store.js +1 -1
  183. package/dist/provider-api/custom-registry.d.ts +1 -0
  184. package/dist/provider-api/custom-registry.js +1 -1
  185. package/dist/provider-api/quota-governor.d.ts +1 -0
  186. package/dist/provider-api/quota-governor.js +1 -1
  187. package/dist/provider-api/staged-datasets-store.d.ts +1 -0
  188. package/dist/provider-api/staged-datasets-store.js +1 -1
  189. package/dist/resources/handlers.d.ts +3 -3
  190. package/dist/resources/store.d.ts +1 -0
  191. package/dist/resources/store.js +1 -1
  192. package/dist/scripts/dev/index.js +15 -1
  193. package/dist/search/index.js +3 -0
  194. package/dist/secrets/routes.d.ts +9 -9
  195. package/dist/secrets/storage.d.ts +1 -0
  196. package/dist/secrets/storage.js +1 -1
  197. package/dist/server/agent-chat-plugin.js +38 -9
  198. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  199. package/dist/server/agent-teams-run-queue.d.ts +1 -0
  200. package/dist/server/agent-teams-run-queue.js +1 -1
  201. package/dist/server/auth-marketing.js +3 -1
  202. package/dist/server/auth.d.ts +18 -1
  203. package/dist/server/auth.js +370 -154
  204. package/dist/server/embed-session.d.ts +1 -0
  205. package/dist/server/embed-session.js +1 -1
  206. package/dist/server/google-auth-plugin.js +3 -1
  207. package/dist/server/google-oauth.d.ts +10 -0
  208. package/dist/server/google-oauth.js +25 -1
  209. package/dist/server/identity-sso-store.d.ts +1 -0
  210. package/dist/server/identity-sso-store.js +1 -1
  211. package/dist/server/index.d.ts +1 -1
  212. package/dist/server/index.js +1 -1
  213. package/dist/server/onboarding-html.js +200 -31
  214. package/dist/server/poll.d.ts +2 -0
  215. package/dist/server/poll.js +19 -1
  216. package/dist/server/realtime-token.d.ts +1 -1
  217. package/dist/server/release-migrations.js +5 -0
  218. package/dist/server/release-schema.d.ts +46 -0
  219. package/dist/server/release-schema.js +307 -0
  220. package/dist/settings/store.d.ts +1 -0
  221. package/dist/settings/store.js +1 -1
  222. package/dist/shared/environment-lanes.d.ts +28 -0
  223. package/dist/shared/environment-lanes.js +61 -0
  224. package/dist/sharing/actions/share-resource.js +22 -2
  225. package/dist/styles/agent-native.css +17 -15
  226. package/dist/triggers/routes.d.ts +11 -0
  227. package/dist/triggers/routes.js +10 -0
  228. package/dist/usage/alerts-store.d.ts +1 -0
  229. package/dist/usage/alerts-store.js +1 -1
  230. package/dist/usage/store.d.ts +1 -0
  231. package/dist/usage/store.js +1 -1
  232. package/docs/content/actions-access-control.mdx +136 -2
  233. package/docs/content/actions-agent-tools.mdx +101 -0
  234. package/docs/content/actions-defining.mdx +2 -2
  235. package/docs/content/audit-log.mdx +1 -1
  236. package/docs/content/authentication.mdx +1 -1
  237. package/docs/content/cloudflare.mdx +43 -0
  238. package/docs/content/creating-templates.mdx +1 -1
  239. package/docs/content/deploy-an-app.mdx +109 -0
  240. package/docs/content/deployment-environment-variables.mdx +101 -0
  241. package/docs/content/deployment.mdx +125 -792
  242. package/docs/content/docs-components.mdx +2 -2
  243. package/docs/content/environment-variables.mdx +41 -41
  244. package/docs/content/file-uploads.mdx +74 -40
  245. package/docs/content/harness-agents.mdx +2 -2
  246. package/docs/content/internationalization.mdx +61 -65
  247. package/docs/content/key-concepts.mdx +3 -3
  248. package/docs/content/multi-app-workspace.mdx +2 -2
  249. package/docs/content/netlify.mdx +47 -0
  250. package/docs/content/node-docker.mdx +138 -0
  251. package/docs/content/organizations-teams-permissions.mdx +5 -5
  252. package/docs/content/other-platforms.mdx +27 -0
  253. package/docs/content/ssr-caching.mdx +45 -0
  254. package/docs/content/template-assets-developers.mdx +2 -2
  255. package/docs/content/template-chat-developers.mdx +2 -2
  256. package/docs/content/template-content-developers.mdx +2 -2
  257. package/docs/content/{local-file-mode.mdx → template-content-local-files.mdx} +2 -1
  258. package/docs/content/template-content-sync.mdx +2 -2
  259. package/docs/content/toolkit-agent-ux.mdx +1 -1
  260. package/docs/content/toolkit-resources.mdx +2 -2
  261. package/docs/content/updating-ui-in-production.mdx +35 -0
  262. package/docs/content/vercel.mdx +38 -0
  263. package/docs/content/what-is-agent-native.mdx +1 -1
  264. package/docs/content/workspace-deployment.mdx +350 -0
  265. package/package.json +2 -2
  266. package/docs/content/agents.mdx +0 -9
  267. package/docs/content/database.mdx +0 -317
  268. package/docs/content/human-approval.mdx +0 -156
@@ -0,0 +1,163 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import { buildDeepLink } from "@agent-native/core/server";
3
+ import { and, eq } from "drizzle-orm";
4
+ import { z } from "zod";
5
+
6
+ import { getDb } from "../server/db/index.js";
7
+ import {
8
+ factoryDefinitions,
9
+ factoryGraphVersions,
10
+ } from "../server/db/schema.js";
11
+ import { normalizeFactoryGraph } from "../server/factory-graph/contracts.js";
12
+ import {
13
+ DEFAULT_FACTORY_ID,
14
+ parseFactoryGraph,
15
+ } from "../server/factory-graph/store.js";
16
+ import {
17
+ requireWorkspaceMember,
18
+ workspaceMemberIdentityFromContext,
19
+ } from "../server/lib/require-workspace-member.js";
20
+ import { stableId } from "../server/triage/ids.js";
21
+
22
+ export default defineAction({
23
+ description:
24
+ "Restore a Factory visual graph version. Restoring never deletes history: it validates the selected snapshot, appends it as a new version, and makes that new version current while preserving the Factory prompt.",
25
+ schema: z.object({
26
+ factoryId: z
27
+ .string()
28
+ .trim()
29
+ .min(1)
30
+ .max(120)
31
+ .regex(/^[a-z0-9][a-z0-9-]*$/)
32
+ .default(DEFAULT_FACTORY_ID),
33
+ versionId: z.string().trim().min(1).max(240),
34
+ }),
35
+ link: ({ result }) => ({
36
+ url: buildDeepLink({
37
+ app: "factory",
38
+ view: "factory",
39
+ params: { factoryId: result.factoryId, tab: "history" },
40
+ }),
41
+ label: `Open ${result.name} history in Factory`,
42
+ }),
43
+ run: async ({ factoryId, versionId }, context) => {
44
+ const { userEmail, orgId } = await requireWorkspaceMember(
45
+ workspaceMemberIdentityFromContext(context),
46
+ );
47
+ const db = getDb();
48
+
49
+ return db.transaction(async (tx) => {
50
+ const definition = (
51
+ await tx
52
+ .select()
53
+ .from(factoryDefinitions)
54
+ .where(
55
+ and(
56
+ eq(factoryDefinitions.id, factoryId),
57
+ eq(factoryDefinitions.orgId, orgId),
58
+ ),
59
+ )
60
+ .limit(1)
61
+ )[0];
62
+ if (!definition) {
63
+ throw new Error("Factory has no saved definition to restore.");
64
+ }
65
+
66
+ const version = (
67
+ await tx
68
+ .select()
69
+ .from(factoryGraphVersions)
70
+ .where(
71
+ and(
72
+ eq(factoryGraphVersions.id, versionId),
73
+ eq(factoryGraphVersions.factoryId, factoryId),
74
+ eq(factoryGraphVersions.orgId, orgId),
75
+ ),
76
+ )
77
+ .limit(1)
78
+ )[0];
79
+ if (!version) {
80
+ throw new Error("Factory graph version not found.");
81
+ }
82
+
83
+ const selectedGraph = parseFactoryGraph(version.graphJson);
84
+
85
+ if (version.version === definition.graphVersion) {
86
+ return {
87
+ ok: true,
88
+ alreadyCurrent: true,
89
+ factoryId,
90
+ versionId: version.id,
91
+ restoredFromVersion: version.version,
92
+ graphVersion: definition.graphVersion,
93
+ name: definition.name,
94
+ graph: selectedGraph,
95
+ source: "restore" as const,
96
+ };
97
+ }
98
+
99
+ const nextVersion = definition.graphVersion + 1;
100
+ const restoredGraph = normalizeFactoryGraph({
101
+ ...selectedGraph,
102
+ version: nextVersion,
103
+ });
104
+ const now = new Date().toISOString();
105
+ const nextVersionId = stableId(
106
+ "factory-graph",
107
+ orgId,
108
+ factoryId,
109
+ String(nextVersion),
110
+ );
111
+ const changeSummary = `Restored version ${version.version}.`;
112
+
113
+ const updated = await tx
114
+ .update(factoryDefinitions)
115
+ .set({
116
+ name: restoredGraph.name,
117
+ description: restoredGraph.description,
118
+ graphVersion: nextVersion,
119
+ graphJson: JSON.stringify(restoredGraph),
120
+ updatedAt: now,
121
+ ownerEmail: userEmail,
122
+ })
123
+ .where(
124
+ and(
125
+ eq(factoryDefinitions.id, factoryId),
126
+ eq(factoryDefinitions.orgId, orgId),
127
+ eq(factoryDefinitions.graphVersion, definition.graphVersion),
128
+ ),
129
+ )
130
+ .returning({ id: factoryDefinitions.id });
131
+ if (updated.length === 0) {
132
+ throw new Error(
133
+ "Factory changed while restoring. Refresh history and try again.",
134
+ );
135
+ }
136
+
137
+ await tx.insert(factoryGraphVersions).values({
138
+ id: nextVersionId,
139
+ factoryId,
140
+ version: nextVersion,
141
+ graphJson: JSON.stringify(restoredGraph),
142
+ source: "restore",
143
+ changeSummary,
144
+ createdAt: now,
145
+ createdBy: userEmail,
146
+ ownerEmail: userEmail,
147
+ orgId,
148
+ });
149
+
150
+ return {
151
+ ok: true,
152
+ alreadyCurrent: false,
153
+ factoryId,
154
+ versionId: nextVersionId,
155
+ restoredFromVersion: version.version,
156
+ graphVersion: nextVersion,
157
+ name: restoredGraph.name,
158
+ graph: restoredGraph,
159
+ source: "restore" as const,
160
+ };
161
+ });
162
+ },
163
+ });
@@ -12,10 +12,7 @@ import {
12
12
  factoryGraphSchema,
13
13
  normalizeFactoryGraph,
14
14
  } from "../server/factory-graph/contracts.js";
15
- import {
16
- DEFAULT_FACTORY_ID,
17
- readFactoryDefinition,
18
- } from "../server/factory-graph/store.js";
15
+ import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
19
16
  import {
20
17
  requireWorkspaceMember,
21
18
  workspaceMemberIdentityFromContext,
@@ -24,7 +21,7 @@ import { stableId } from "../server/triage/ids.js";
24
21
 
25
22
  export default defineAction({
26
23
  description:
27
- "Create or update a Factory's versioned visual graph. Use source=ai for an agent-proposed graph and source=manual for a direct editor save. This changes configuration only; it never starts provider work.",
24
+ "Create or update a Factory's versioned visual graph. Pass expectedGraphVersion from the graph you inspected so stale edits are rejected. Use source=ai for an agent-proposed graph and source=manual for a direct editor save. This changes configuration only; it never starts provider work.",
28
25
  schema: z.object({
29
26
  factoryId: z
30
27
  .string()
@@ -38,6 +35,7 @@ export default defineAction({
38
35
  prompt: z.string().trim().max(10_000).default(""),
39
36
  source: z.enum(["manual", "ai", "seed"]).default("manual"),
40
37
  changeSummary: z.string().trim().max(500).default(""),
38
+ expectedGraphVersion: z.coerce.number().int().nonnegative(),
41
39
  graph: factoryGraphSchema,
42
40
  }),
43
41
  link: ({ result }) => ({
@@ -49,81 +47,116 @@ export default defineAction({
49
47
  label: `Open ${result.name} in Factory`,
50
48
  }),
51
49
  run: async (
52
- { factoryId, name, description, prompt, source, changeSummary, graph },
50
+ {
51
+ factoryId,
52
+ name,
53
+ description,
54
+ prompt,
55
+ source,
56
+ changeSummary,
57
+ expectedGraphVersion,
58
+ graph,
59
+ },
53
60
  context,
54
61
  ) => {
55
62
  const { userEmail, orgId } = await requireWorkspaceMember(
56
63
  workspaceMemberIdentityFromContext(context),
57
64
  );
58
65
  const db = getDb();
59
- const existing = await readFactoryDefinition(orgId, factoryId);
60
- const nextVersion = (existing?.graphVersion ?? 0) + 1;
61
- const normalizedGraph = normalizeFactoryGraph({
62
- ...graph,
63
- version: nextVersion,
64
- name,
65
- description,
66
- });
67
- const now = new Date().toISOString();
68
- const versionId = stableId(
69
- "factory-graph",
70
- orgId,
71
- factoryId,
72
- String(nextVersion),
73
- );
74
66
 
75
- await db.insert(factoryGraphVersions).values({
76
- id: versionId,
77
- factoryId,
78
- version: nextVersion,
79
- graphJson: JSON.stringify(normalizedGraph),
80
- source,
81
- changeSummary,
82
- createdAt: now,
83
- createdBy: userEmail,
84
- ownerEmail: userEmail,
85
- orgId,
86
- });
67
+ return db.transaction(async (tx) => {
68
+ const existing = (
69
+ await tx
70
+ .select()
71
+ .from(factoryDefinitions)
72
+ .where(
73
+ and(
74
+ eq(factoryDefinitions.id, factoryId),
75
+ eq(factoryDefinitions.orgId, orgId),
76
+ ),
77
+ )
78
+ .limit(1)
79
+ )[0];
80
+ if ((existing?.graphVersion ?? 0) !== expectedGraphVersion) {
81
+ throw new Error(
82
+ "Factory changed while saving. Refresh the Factory and try again.",
83
+ );
84
+ }
85
+ const nextVersion = (existing?.graphVersion ?? 0) + 1;
86
+ const normalizedGraph = normalizeFactoryGraph({
87
+ ...graph,
88
+ version: nextVersion,
89
+ name,
90
+ description,
91
+ });
92
+ const now = new Date().toISOString();
93
+ const versionId = stableId(
94
+ "factory-graph",
95
+ orgId,
96
+ factoryId,
97
+ String(nextVersion),
98
+ );
87
99
 
88
- if (existing) {
89
- await db
90
- .update(factoryDefinitions)
91
- .set({
100
+ if (existing) {
101
+ const updated = await tx
102
+ .update(factoryDefinitions)
103
+ .set({
104
+ name,
105
+ description,
106
+ prompt,
107
+ graphVersion: nextVersion,
108
+ graphJson: JSON.stringify(normalizedGraph),
109
+ updatedAt: now,
110
+ ownerEmail: userEmail,
111
+ })
112
+ .where(
113
+ and(
114
+ eq(factoryDefinitions.id, factoryId),
115
+ eq(factoryDefinitions.orgId, orgId),
116
+ eq(factoryDefinitions.graphVersion, expectedGraphVersion),
117
+ ),
118
+ )
119
+ .returning({ id: factoryDefinitions.id });
120
+ if (updated.length === 0) {
121
+ throw new Error(
122
+ "Factory changed while saving. Refresh the Factory and try again.",
123
+ );
124
+ }
125
+ } else {
126
+ await tx.insert(factoryDefinitions).values({
127
+ id: factoryId,
92
128
  name,
93
129
  description,
94
130
  prompt,
95
131
  graphVersion: nextVersion,
96
132
  graphJson: JSON.stringify(normalizedGraph),
133
+ createdAt: now,
97
134
  updatedAt: now,
98
135
  ownerEmail: userEmail,
99
- })
100
- .where(
101
- and(
102
- eq(factoryDefinitions.id, factoryId),
103
- eq(factoryDefinitions.orgId, orgId),
104
- ),
105
- );
106
- } else {
107
- await db.insert(factoryDefinitions).values({
108
- id: factoryId,
109
- name,
110
- description,
111
- prompt,
112
- graphVersion: nextVersion,
136
+ orgId,
137
+ });
138
+ }
139
+
140
+ await tx.insert(factoryGraphVersions).values({
141
+ id: versionId,
142
+ factoryId,
143
+ version: nextVersion,
113
144
  graphJson: JSON.stringify(normalizedGraph),
145
+ source,
146
+ changeSummary,
114
147
  createdAt: now,
115
- updatedAt: now,
148
+ createdBy: userEmail,
116
149
  ownerEmail: userEmail,
117
150
  orgId,
118
151
  });
119
- }
120
152
 
121
- return {
122
- ok: true,
123
- factoryId,
124
- name,
125
- graphVersion: nextVersion,
126
- source,
127
- };
153
+ return {
154
+ ok: true,
155
+ factoryId,
156
+ name,
157
+ graphVersion: nextVersion,
158
+ source,
159
+ };
160
+ });
128
161
  },
129
162
  });
@@ -153,7 +153,7 @@ async function writeMetadata(
153
153
  .where(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)));
154
154
  }
155
155
 
156
- async function recordDecision(input: {
156
+ export async function recordAutomaticBuilderDecision(input: {
157
157
  itemId: string;
158
158
  userEmail: string;
159
159
  orgId: string;
@@ -167,6 +167,7 @@ async function recordDecision(input: {
167
167
  input.itemId,
168
168
  "automatic-builder",
169
169
  );
170
+ const now = new Date().toISOString();
170
171
  await getDb()
171
172
  .insert(triageDecisions)
172
173
  .values({
@@ -179,17 +180,26 @@ async function recordDecision(input: {
179
180
  guardResultsJson: JSON.stringify(input.guardResults),
180
181
  model: "factory-automation",
181
182
  promptVersion: 1,
182
- createdAt: new Date().toISOString(),
183
+ createdAt: now,
183
184
  ownerEmail: input.userEmail,
184
185
  orgId: input.orgId,
185
186
  })
186
- .onConflictDoNothing();
187
+ .onConflictDoUpdate({
188
+ target: triageDecisions.id,
189
+ set: {
190
+ outcome: input.outcome,
191
+ reason: input.reason,
192
+ guardResultsJson: JSON.stringify(input.guardResults),
193
+ createdAt: now,
194
+ ownerEmail: input.userEmail,
195
+ },
196
+ });
187
197
  return id;
188
198
  }
189
199
 
190
200
  export default defineAction({
191
201
  description:
192
- "Start the governed clear-bug Builder flow for a Factory item. Slack items are kept in-thread by adding 👀 and tagging @builder.io; grouped Slack repeats share one Builder thread. GitHub issues and Sentry errors use the Builder agent run API. Owner-managed Clips, Design, and Content items are always left for their owner.",
202
+ "Start the governed clear-bug Builder flow for a Factory item, or record a skip with a reason when clearBug is false. Slack items are kept in-thread by adding 👀 and tagging @builder.io; grouped Slack repeats share one Builder thread. GitHub issues and Sentry errors use the Builder agent run API. Owner-managed Clips, Design, and Content items are always left for their owner.",
193
203
  schema: z.object({
194
204
  itemId: z.string().min(1),
195
205
  clearBug: z.boolean(),
@@ -340,17 +350,12 @@ export default defineAction({
340
350
  : []),
341
351
  ];
342
352
  const blocked = guardResults.some((guard) => !guard.passed);
343
- const decisionId = await recordDecision({
353
+ const decisionId = await recordAutomaticBuilderDecision({
344
354
  itemId,
345
355
  userEmail,
346
356
  orgId,
347
357
  outcome: blocked ? "needs_manual" : "propose_fix",
348
- reason: blocked
349
- ? guardResults
350
- .filter((guard) => !guard.passed)
351
- .map((guard) => guard.reason)
352
- .join(" ")
353
- : reason,
358
+ reason,
354
359
  guardResults,
355
360
  });
356
361
  await recordFactoryAudit(
@@ -363,7 +368,7 @@ export default defineAction({
363
368
  source: item.source,
364
369
  sourceUrl: item.sourceUrl,
365
370
  status: blocked ? "skipped" : "success",
366
- summary: blocked ? "Factory kept this item for manual review." : reason,
371
+ summary: reason,
367
372
  details: {
368
373
  decisionId,
369
374
  clearBug,
@@ -383,10 +388,7 @@ export default defineAction({
383
388
  started: false,
384
389
  needsManual: true,
385
390
  decisionId,
386
- reason: guardResults
387
- .filter((guard) => !guard.passed)
388
- .map((guard) => guard.reason)
389
- .join(" "),
391
+ reason,
390
392
  };
391
393
  }
392
394
 
@@ -550,7 +552,7 @@ export default defineAction({
550
552
  .where(
551
553
  and(eq(triageItems.id, related.id), eq(triageItems.orgId, orgId)),
552
554
  );
553
- await recordDecision({
555
+ await recordAutomaticBuilderDecision({
554
556
  itemId: related.id,
555
557
  userEmail,
556
558
  orgId,
@@ -1,3 +1,4 @@
1
+ import { requestAgentChatThreadOpen } from "@agent-native/core/client/agent-chat";
1
2
  import { useActionQuery } from "@agent-native/core/client/hooks";
2
3
  import { useT } from "@agent-native/core/client/i18n";
3
4
  import {
@@ -8,7 +9,7 @@ import {
8
9
  IconExternalLink,
9
10
  IconSearch,
10
11
  } from "@tabler/icons-react";
11
- import { useEffect, useMemo } from "react";
12
+ import { useEffect, useMemo, useRef } from "react";
12
13
  import { useSearchParams } from "react-router";
13
14
 
14
15
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
@@ -70,6 +71,7 @@ export function FactoryAuditView({
70
71
  }) {
71
72
  const t = useT();
72
73
  const [searchParams, setSearchParams] = useSearchParams();
74
+ const viewRef = useRef<HTMLDivElement>(null);
73
75
  const selectedRunId = searchParams.get("auditRunId");
74
76
  const auditQuery = useActionQuery<FactoryAuditResponse>(
75
77
  "list-factory-audit",
@@ -109,8 +111,13 @@ export function FactoryAuditView({
109
111
  );
110
112
  }
111
113
 
114
+ useEffect(() => {
115
+ if (!selectedRunId) return;
116
+ viewRef.current?.scrollIntoView({ block: "start" });
117
+ }, [selectedRunId]);
118
+
112
119
  return (
113
- <div className="p-4 lg:p-6">
120
+ <div ref={viewRef} className="p-4 lg:p-6">
114
121
  {auditQuery.isError ? (
115
122
  <Card>
116
123
  <CardContent className="flex items-start gap-2 p-4 text-sm text-destructive">
@@ -144,30 +151,38 @@ export function FactoryAuditView({
144
151
  </CardHeader>
145
152
  <CardContent className="p-0">
146
153
  <div className="grid gap-1.5 p-2">
147
- {runs.map((run) => (
148
- <button
149
- key={run.id}
150
- type="button"
151
- className={`w-full cursor-pointer rounded-lg bg-muted/20 p-3 text-left transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring ${selectedRun?.id === run.id ? "bg-muted/60" : ""}`}
152
- onClick={() => selectRun(run.id)}
153
- >
154
- <div className="flex items-center justify-between gap-3">
155
- <span className="min-w-0 truncate text-sm font-medium">
156
- {formatAutomationName(run.automation)}
157
- </span>
158
- <AuditStatus status={run.status} />
159
- </div>
160
- <div className="mt-1 flex items-center justify-between gap-2 text-xs text-muted-foreground">
161
- <span className="shrink-0">
162
- {formatAuditAge(run.startedAt)}
163
- </span>
164
- <span className="shrink-0">
165
- {run.events.length}{" "}
166
- {formatAuditCountLabel(run.events.length)}
167
- </span>
168
- </div>
169
- </button>
170
- ))}
154
+ {runs.map((run) => {
155
+ const selected = selectedRun?.id === run.id;
156
+ return (
157
+ <button
158
+ key={run.id}
159
+ type="button"
160
+ aria-current={selected ? "true" : undefined}
161
+ className={`w-full cursor-pointer rounded-lg p-3 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring ${
162
+ selected
163
+ ? "bg-primary/10 ring-1 ring-inset ring-primary/40"
164
+ : "bg-muted/20 hover:bg-muted/50"
165
+ }`}
166
+ onClick={() => selectRun(run.id)}
167
+ >
168
+ <div className="flex items-center justify-between gap-3">
169
+ <span className="min-w-0 truncate text-sm font-medium">
170
+ {formatAutomationName(run.automation)}
171
+ </span>
172
+ <AuditStatus status={run.status} />
173
+ </div>
174
+ <div className="mt-1 flex items-center justify-between gap-2 text-xs text-muted-foreground">
175
+ <span className="shrink-0">
176
+ {formatAuditAge(run.startedAt)}
177
+ </span>
178
+ <span className="shrink-0">
179
+ {run.events.length}{" "}
180
+ {formatAuditCountLabel(run.events.length)}
181
+ </span>
182
+ </div>
183
+ </button>
184
+ );
185
+ })}
171
186
  </div>
172
187
  </CardContent>
173
188
  </Card>
@@ -190,7 +205,9 @@ export function FactoryAuditView({
190
205
  </div>
191
206
  </CardHeader>
192
207
  <CardContent className="pt-4">
193
- {selectedRun && <AuditRunDetail run={selectedRun} />}
208
+ {selectedRun && (
209
+ <AuditRunDetail run={selectedRun} factoryId={factoryId} />
210
+ )}
194
211
  </CardContent>
195
212
  </Card>
196
213
  </div>
@@ -199,7 +216,13 @@ export function FactoryAuditView({
199
216
  );
200
217
  }
201
218
 
202
- function AuditRunDetail({ run }: { run: FactoryAuditRun }) {
219
+ function AuditRunDetail({
220
+ run,
221
+ factoryId,
222
+ }: {
223
+ run: FactoryAuditRun;
224
+ factoryId: string;
225
+ }) {
203
226
  const t = useT();
204
227
  const groups = useMemo(() => groupAuditEvents(run.events), [run.events]);
205
228
  const itemCount = new Set(
@@ -229,10 +252,22 @@ function AuditRunDetail({ run }: { run: FactoryAuditRun }) {
229
252
  {run.threadId && (
230
253
  <a
231
254
  href={`/chat/${encodeURIComponent(run.threadId)}`}
232
- className="ml-auto inline-flex items-center gap-1 text-primary hover:underline"
255
+ className="ml-auto inline-flex items-center text-primary hover:underline"
256
+ onClick={(event) => {
257
+ if (
258
+ event.metaKey ||
259
+ event.ctrlKey ||
260
+ event.shiftKey ||
261
+ event.altKey ||
262
+ event.button !== 0
263
+ ) {
264
+ return;
265
+ }
266
+ event.preventDefault();
267
+ requestAgentChatThreadOpen({ threadId: run.threadId! });
268
+ }}
233
269
  >
234
270
  {t("factoryRoute.auditOpenThread")}
235
- <IconExternalLink className="size-3" />
236
271
  </a>
237
272
  )}
238
273
  </div>
@@ -260,7 +295,11 @@ function AuditRunDetail({ run }: { run: FactoryAuditRun }) {
260
295
  ) : (
261
296
  <div className="grid gap-1.5">
262
297
  {groups.map((group) => (
263
- <AuditItemRow key={group.key} group={group} />
298
+ <AuditItemRow
299
+ key={group.key}
300
+ group={group}
301
+ factoryId={factoryId}
302
+ />
264
303
  ))}
265
304
  </div>
266
305
  )}
@@ -269,15 +308,20 @@ function AuditRunDetail({ run }: { run: FactoryAuditRun }) {
269
308
  );
270
309
  }
271
310
 
272
- function AuditItemRow({ group }: { group: AuditItemGroup }) {
311
+ function AuditItemRow({
312
+ group,
313
+ factoryId,
314
+ }: {
315
+ group: AuditItemGroup;
316
+ factoryId: string;
317
+ }) {
273
318
  const t = useT();
274
319
  const decision = group.decision;
275
320
  const rationale = decision?.summary ?? group.externalAction?.summary ?? null;
276
- const sourceLink =
277
- group.sourceUrl ?? group.events.find((event) => event.sourceUrl)?.sourceUrl;
321
+ const sourceLink = resolveAuditSourceLink(group);
278
322
 
279
323
  return (
280
- <details className="group rounded-lg bg-muted/20">
324
+ <details className="group overflow-hidden rounded-lg border border-border bg-muted/20">
281
325
  <summary className="flex cursor-pointer list-none items-center gap-3 rounded-lg px-3 py-3 hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring [&::-webkit-details-marker]:hidden">
282
326
  <AuditSourceIcon source={group.source} />
283
327
  <div className="min-w-0 flex-1">
@@ -336,7 +380,7 @@ function AuditItemRow({ group }: { group: AuditItemGroup }) {
336
380
  <div className="mt-4 flex flex-wrap items-center gap-x-4 gap-y-2 bg-background/40 px-3 py-3 text-xs">
337
381
  {group.itemId && (
338
382
  <a
339
- href={`/factory?tab=inbox&itemId=${encodeURIComponent(group.itemId)}`}
383
+ href={`/factory?factoryId=${encodeURIComponent(factoryId)}&tab=inbox&itemId=${encodeURIComponent(group.itemId)}`}
340
384
  className="text-primary hover:underline"
341
385
  >
342
386
  {t("factoryRoute.auditOpenItem")}
@@ -579,6 +623,26 @@ function formatAuditSource(source: string | null): string {
579
623
  return source ? formatAuditLabel(source) : "Factory";
580
624
  }
581
625
 
626
+ function resolveAuditSourceLink(group: AuditItemGroup): string | null {
627
+ const storedUrl =
628
+ group.sourceUrl ??
629
+ group.events.find((event) => event.sourceUrl)?.sourceUrl ??
630
+ null;
631
+ if (storedUrl) return storedUrl;
632
+
633
+ for (const event of group.events) {
634
+ const channelId = readStringDetail(event.details, "channelId");
635
+ const threadTs = readStringDetail(event.details, "threadTs");
636
+ if (channelId && threadTs) return slackThreadUrl(channelId, threadTs);
637
+ }
638
+ return null;
639
+ }
640
+
641
+ function slackThreadUrl(channelId: string, threadTs: string): string {
642
+ const compactTs = threadTs.replace(".", "");
643
+ return `https://slack.com/archives/${encodeURIComponent(channelId)}/p${compactTs}?thread_ts=${encodeURIComponent(threadTs)}`;
644
+ }
645
+
582
646
  function formatAutomationName(value: string): string {
583
647
  const words = value
584
648
  .replace(/^factory-/, "")