@agent-native/core 0.131.7 → 0.132.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 (320) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +123 -0
  3. package/corpus/core/docs/content/durable-background-runs.mdx +15 -6
  4. package/corpus/core/docs/design/durable-agent-runs.md +15 -11
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/a2a/activity.ts +127 -10
  7. package/corpus/core/src/a2a/index.ts +3 -0
  8. package/corpus/core/src/agent/durable-background.ts +38 -28
  9. package/corpus/core/src/agent/harness/background.ts +12 -2
  10. package/corpus/core/src/agent/production-agent.ts +18 -15
  11. package/corpus/core/src/agent/run-store.ts +45 -3
  12. package/corpus/core/src/application-state/handlers.ts +45 -0
  13. package/corpus/core/src/application-state/index.ts +3 -0
  14. package/corpus/core/src/application-state/store.ts +38 -34
  15. package/corpus/core/src/audit/redact.ts +104 -18
  16. package/corpus/core/src/cli/create.ts +115 -16
  17. package/corpus/core/src/client/AgentPanel.tsx +2 -7
  18. package/corpus/core/src/client/AssistantChat.tsx +10 -2
  19. package/corpus/core/src/client/MultiTabAssistantChat.tsx +3 -0
  20. package/corpus/core/src/client/RunStuckBanner.tsx +29 -6
  21. package/corpus/core/src/client/agent-chat.ts +4 -9
  22. package/corpus/core/src/client/application-state.ts +115 -12
  23. package/corpus/core/src/client/dynamic-suggestions.ts +4 -12
  24. package/corpus/core/src/client/frame-protocol.ts +5 -1
  25. package/corpus/core/src/client/guided-questions.tsx +12 -23
  26. package/corpus/core/src/db/client.ts +148 -28
  27. package/corpus/core/src/db/create-get-db.ts +39 -6
  28. package/corpus/core/src/db/runtime-diagnostics.ts +41 -6
  29. package/corpus/core/src/deploy/build.ts +26 -33
  30. package/corpus/core/src/feature-flags/store.ts +10 -5
  31. package/corpus/core/src/integrations/a2a-continuations-store.ts +11 -0
  32. package/corpus/core/src/integrations/config-store.ts +13 -0
  33. package/corpus/core/src/integrations/google-docs-poller.ts +26 -2
  34. package/corpus/core/src/mcp-client/routes.ts +33 -2
  35. package/corpus/core/src/onboarding/plugin.ts +31 -25
  36. package/corpus/core/src/org/accept-pending.ts +2 -0
  37. package/corpus/core/src/org/auto-join-domain.ts +2 -0
  38. package/corpus/core/src/org/context.ts +35 -10
  39. package/corpus/core/src/org/handlers.ts +6 -0
  40. package/corpus/core/src/org/request-org-cache.ts +71 -0
  41. package/corpus/core/src/resources/store.ts +10 -9
  42. package/corpus/core/src/scripts/runner.ts +6 -3
  43. package/corpus/core/src/secrets/storage.ts +97 -5
  44. package/corpus/core/src/server/action-change.ts +33 -0
  45. package/corpus/core/src/server/action-routes.ts +11 -9
  46. package/corpus/core/src/server/agent-teams.ts +10 -2
  47. package/corpus/core/src/server/better-auth-instance.ts +39 -12
  48. package/corpus/core/src/server/core-routes-plugin.ts +19 -2
  49. package/corpus/core/src/server/credential-provider.ts +57 -14
  50. package/corpus/core/src/server/http-response-telemetry.ts +1 -0
  51. package/corpus/core/src/server/poll.ts +95 -57
  52. package/corpus/core/src/server/voice-providers-status.ts +19 -4
  53. package/corpus/core/src/workspace-files/tool.ts +18 -11
  54. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +38 -0
  55. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
  56. package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +299 -0
  57. package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +34 -0
  58. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +82 -0
  59. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +2 -2
  60. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +40 -34
  61. package/corpus/templates/analytics/app/hooks/use-user-pref.ts +3 -3
  62. package/corpus/templates/analytics/app/i18n-data.ts +40 -0
  63. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +18 -10
  64. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +14 -0
  65. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +1 -0
  66. package/corpus/templates/analytics/app/routes/chart.tsx +3 -0
  67. package/corpus/templates/analytics/changelog/2026-07-30-analytics-dashboards-now-expose-native-funnel-heatmap-callou.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-07-30-analytics-now-ships-reusable-native-v2-dashboard-manifests-w.md +6 -0
  69. package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +81 -1
  70. package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +457 -0
  71. package/corpus/templates/analytics/server/lib/report-chart-svg.ts +73 -2
  72. package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
  73. package/corpus/templates/analytics/shared/dashboard-funnel.ts +83 -0
  74. package/corpus/templates/clips/actions/generate-workflow.ts +6 -4
  75. package/corpus/templates/clips/actions/reconcile-workflow-generation.ts +257 -0
  76. package/corpus/templates/clips/app/hooks/use-auto-title.ts +166 -4
  77. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -1
  78. package/corpus/templates/clips/changelog/2026-07-27-stopped-agent-runs-no-longer-leave-generated-workflow-cards-.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-07-30-workflow-generation-now-stops-retrying-after-repeated-failur.md +6 -0
  80. package/corpus/templates/clips/shared/workflow.ts +5 -0
  81. package/corpus/templates/content/changelog/2026-07-30-long-running-delegated-requests-now-continue-reliably.md +6 -0
  82. package/corpus/templates/content/netlify.toml +3 -0
  83. package/corpus/templates/content/server/plugins/agent-chat.ts +1 -0
  84. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +2 -1
  85. package/corpus/templates/slides/actions/patch-deck.ts +25 -1
  86. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +17 -4
  87. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +9 -1
  88. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  89. package/corpus/templates/slides/app/context/DeckContext.tsx +11 -3
  90. package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +39 -11
  91. package/corpus/templates/slides/app/pages/Index.tsx +2 -2
  92. package/corpus/templates/slides/changelog/2026-07-30-slide-previews-keep-their-aspect-ratio-isolate-embedded-styl.md +6 -0
  93. package/corpus/templates/slides/changelog/2026-07-30-slides-no-longer-lose-decks-while-organization-access-is-loa.md +6 -0
  94. package/corpus/toolkit/CHANGELOG.md +8 -0
  95. package/corpus/toolkit/package.json +1 -1
  96. package/corpus/toolkit/src/composer/runtime-adapters.tsx +16 -11
  97. package/dist/a2a/activity.d.ts +22 -4
  98. package/dist/a2a/activity.d.ts.map +1 -1
  99. package/dist/a2a/activity.js +97 -11
  100. package/dist/a2a/activity.js.map +1 -1
  101. package/dist/a2a/index.d.ts +1 -1
  102. package/dist/a2a/index.d.ts.map +1 -1
  103. package/dist/a2a/index.js +1 -1
  104. package/dist/a2a/index.js.map +1 -1
  105. package/dist/agent/durable-background.d.ts +13 -8
  106. package/dist/agent/durable-background.d.ts.map +1 -1
  107. package/dist/agent/durable-background.js +36 -28
  108. package/dist/agent/durable-background.js.map +1 -1
  109. package/dist/agent/harness/background.d.ts.map +1 -1
  110. package/dist/agent/harness/background.js +12 -2
  111. package/dist/agent/harness/background.js.map +1 -1
  112. package/dist/agent/production-agent.d.ts.map +1 -1
  113. package/dist/agent/production-agent.js +18 -15
  114. package/dist/agent/production-agent.js.map +1 -1
  115. package/dist/agent/run-store.d.ts +2 -0
  116. package/dist/agent/run-store.d.ts.map +1 -1
  117. package/dist/agent/run-store.js +44 -3
  118. package/dist/agent/run-store.js.map +1 -1
  119. package/dist/application-state/handlers.d.ts +15 -0
  120. package/dist/application-state/handlers.d.ts.map +1 -1
  121. package/dist/application-state/handlers.js +42 -2
  122. package/dist/application-state/handlers.js.map +1 -1
  123. package/dist/application-state/index.d.ts +2 -2
  124. package/dist/application-state/index.d.ts.map +1 -1
  125. package/dist/application-state/index.js +2 -2
  126. package/dist/application-state/index.js.map +1 -1
  127. package/dist/application-state/store.d.ts +11 -0
  128. package/dist/application-state/store.d.ts.map +1 -1
  129. package/dist/application-state/store.js +37 -40
  130. package/dist/application-state/store.js.map +1 -1
  131. package/dist/audit/redact.d.ts +25 -2
  132. package/dist/audit/redact.d.ts.map +1 -1
  133. package/dist/audit/redact.js +85 -21
  134. package/dist/audit/redact.js.map +1 -1
  135. package/dist/cli/create.d.ts +8 -1
  136. package/dist/cli/create.d.ts.map +1 -1
  137. package/dist/cli/create.js +98 -16
  138. package/dist/cli/create.js.map +1 -1
  139. package/dist/client/AgentPanel.d.ts.map +1 -1
  140. package/dist/client/AgentPanel.js +2 -7
  141. package/dist/client/AgentPanel.js.map +1 -1
  142. package/dist/client/AssistantChat.d.ts.map +1 -1
  143. package/dist/client/AssistantChat.js +10 -2
  144. package/dist/client/AssistantChat.js.map +1 -1
  145. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  146. package/dist/client/MultiTabAssistantChat.js +1 -1
  147. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  148. package/dist/client/RunStuckBanner.d.ts +11 -1
  149. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  150. package/dist/client/RunStuckBanner.js +20 -7
  151. package/dist/client/RunStuckBanner.js.map +1 -1
  152. package/dist/client/agent-chat.d.ts.map +1 -1
  153. package/dist/client/agent-chat.js +4 -6
  154. package/dist/client/agent-chat.js.map +1 -1
  155. package/dist/client/application-state.d.ts +11 -0
  156. package/dist/client/application-state.d.ts.map +1 -1
  157. package/dist/client/application-state.js +87 -9
  158. package/dist/client/application-state.js.map +1 -1
  159. package/dist/client/dynamic-suggestions.d.ts.map +1 -1
  160. package/dist/client/dynamic-suggestions.js +4 -13
  161. package/dist/client/dynamic-suggestions.js.map +1 -1
  162. package/dist/client/frame-protocol.d.ts +1 -0
  163. package/dist/client/frame-protocol.d.ts.map +1 -1
  164. package/dist/client/frame-protocol.js.map +1 -1
  165. package/dist/client/guided-questions.d.ts.map +1 -1
  166. package/dist/client/guided-questions.js +6 -22
  167. package/dist/client/guided-questions.js.map +1 -1
  168. package/dist/collab/awareness.d.ts +2 -2
  169. package/dist/collab/awareness.d.ts.map +1 -1
  170. package/dist/collab/routes.d.ts +1 -1
  171. package/dist/db/client.d.ts +57 -10
  172. package/dist/db/client.d.ts.map +1 -1
  173. package/dist/db/client.js +122 -31
  174. package/dist/db/client.js.map +1 -1
  175. package/dist/db/create-get-db.d.ts.map +1 -1
  176. package/dist/db/create-get-db.js +31 -7
  177. package/dist/db/create-get-db.js.map +1 -1
  178. package/dist/db/runtime-diagnostics.d.ts +1 -0
  179. package/dist/db/runtime-diagnostics.d.ts.map +1 -1
  180. package/dist/db/runtime-diagnostics.js +31 -6
  181. package/dist/db/runtime-diagnostics.js.map +1 -1
  182. package/dist/deploy/build.d.ts +5 -20
  183. package/dist/deploy/build.d.ts.map +1 -1
  184. package/dist/deploy/build.js +18 -30
  185. package/dist/deploy/build.js.map +1 -1
  186. package/dist/feature-flags/store.d.ts.map +1 -1
  187. package/dist/feature-flags/store.js +10 -5
  188. package/dist/feature-flags/store.js.map +1 -1
  189. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  190. package/dist/integrations/a2a-continuations-store.js +12 -0
  191. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  192. package/dist/integrations/config-store.d.ts +1 -0
  193. package/dist/integrations/config-store.d.ts.map +1 -1
  194. package/dist/integrations/config-store.js +11 -0
  195. package/dist/integrations/config-store.js.map +1 -1
  196. package/dist/integrations/google-docs-poller.d.ts.map +1 -1
  197. package/dist/integrations/google-docs-poller.js +19 -2
  198. package/dist/integrations/google-docs-poller.js.map +1 -1
  199. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  200. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
  201. package/dist/mcp-client/routes.d.ts.map +1 -1
  202. package/dist/mcp-client/routes.js +30 -2
  203. package/dist/mcp-client/routes.js.map +1 -1
  204. package/dist/notifications/routes.d.ts +6 -6
  205. package/dist/onboarding/plugin.d.ts.map +1 -1
  206. package/dist/onboarding/plugin.js +12 -8
  207. package/dist/onboarding/plugin.js.map +1 -1
  208. package/dist/org/accept-pending.d.ts.map +1 -1
  209. package/dist/org/accept-pending.js +2 -0
  210. package/dist/org/accept-pending.js.map +1 -1
  211. package/dist/org/auto-join-domain.d.ts.map +1 -1
  212. package/dist/org/auto-join-domain.js +2 -0
  213. package/dist/org/auto-join-domain.js.map +1 -1
  214. package/dist/org/context.d.ts.map +1 -1
  215. package/dist/org/context.js +29 -9
  216. package/dist/org/context.js.map +1 -1
  217. package/dist/org/handlers.d.ts.map +1 -1
  218. package/dist/org/handlers.js +5 -0
  219. package/dist/org/handlers.js.map +1 -1
  220. package/dist/org/request-org-cache.d.ts +16 -0
  221. package/dist/org/request-org-cache.d.ts.map +1 -0
  222. package/dist/org/request-org-cache.js +62 -0
  223. package/dist/org/request-org-cache.js.map +1 -0
  224. package/dist/progress/routes.d.ts +1 -1
  225. package/dist/resources/handlers.d.ts +1 -1
  226. package/dist/resources/store.d.ts.map +1 -1
  227. package/dist/resources/store.js +11 -9
  228. package/dist/resources/store.js.map +1 -1
  229. package/dist/scripts/runner.d.ts.map +1 -1
  230. package/dist/scripts/runner.js +3 -3
  231. package/dist/scripts/runner.js.map +1 -1
  232. package/dist/secrets/storage.d.ts.map +1 -1
  233. package/dist/secrets/storage.js +82 -5
  234. package/dist/secrets/storage.js.map +1 -1
  235. package/dist/server/action-change.d.ts +20 -0
  236. package/dist/server/action-change.d.ts.map +1 -1
  237. package/dist/server/action-change.js +29 -0
  238. package/dist/server/action-change.js.map +1 -1
  239. package/dist/server/action-routes.d.ts.map +1 -1
  240. package/dist/server/action-routes.js +7 -8
  241. package/dist/server/action-routes.js.map +1 -1
  242. package/dist/server/agent-teams.d.ts.map +1 -1
  243. package/dist/server/agent-teams.js +10 -2
  244. package/dist/server/agent-teams.js.map +1 -1
  245. package/dist/server/better-auth-instance.d.ts.map +1 -1
  246. package/dist/server/better-auth-instance.js +31 -14
  247. package/dist/server/better-auth-instance.js.map +1 -1
  248. package/dist/server/core-routes-plugin.d.ts +1 -0
  249. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  250. package/dist/server/core-routes-plugin.js +18 -4
  251. package/dist/server/core-routes-plugin.js.map +1 -1
  252. package/dist/server/credential-provider.d.ts +16 -0
  253. package/dist/server/credential-provider.d.ts.map +1 -1
  254. package/dist/server/credential-provider.js +49 -13
  255. package/dist/server/credential-provider.js.map +1 -1
  256. package/dist/server/http-response-telemetry.d.ts.map +1 -1
  257. package/dist/server/http-response-telemetry.js +1 -0
  258. package/dist/server/http-response-telemetry.js.map +1 -1
  259. package/dist/server/poll.d.ts +6 -0
  260. package/dist/server/poll.d.ts.map +1 -1
  261. package/dist/server/poll.js +84 -47
  262. package/dist/server/poll.js.map +1 -1
  263. package/dist/server/realtime-token.d.ts +1 -1
  264. package/dist/server/voice-providers-status.d.ts.map +1 -1
  265. package/dist/server/voice-providers-status.js +12 -3
  266. package/dist/server/voice-providers-status.js.map +1 -1
  267. package/dist/workspace-files/tool.d.ts.map +1 -1
  268. package/dist/workspace-files/tool.js +18 -11
  269. package/dist/workspace-files/tool.js.map +1 -1
  270. package/docs/content/durable-background-runs.mdx +15 -6
  271. package/docs/design/durable-agent-runs.md +15 -11
  272. package/package.json +3 -3
  273. package/src/a2a/activity.ts +127 -10
  274. package/src/a2a/index.ts +3 -0
  275. package/src/agent/durable-background.ts +38 -28
  276. package/src/agent/harness/background.ts +12 -2
  277. package/src/agent/production-agent.ts +18 -15
  278. package/src/agent/run-store.ts +45 -3
  279. package/src/application-state/handlers.ts +45 -0
  280. package/src/application-state/index.ts +3 -0
  281. package/src/application-state/store.ts +38 -34
  282. package/src/audit/redact.ts +104 -18
  283. package/src/cli/create.ts +115 -16
  284. package/src/client/AgentPanel.tsx +2 -7
  285. package/src/client/AssistantChat.tsx +10 -2
  286. package/src/client/MultiTabAssistantChat.tsx +3 -0
  287. package/src/client/RunStuckBanner.tsx +29 -6
  288. package/src/client/agent-chat.ts +4 -9
  289. package/src/client/application-state.ts +115 -12
  290. package/src/client/dynamic-suggestions.ts +4 -12
  291. package/src/client/frame-protocol.ts +5 -1
  292. package/src/client/guided-questions.tsx +12 -23
  293. package/src/db/client.ts +148 -28
  294. package/src/db/create-get-db.ts +39 -6
  295. package/src/db/runtime-diagnostics.ts +41 -6
  296. package/src/deploy/build.ts +26 -33
  297. package/src/feature-flags/store.ts +10 -5
  298. package/src/integrations/a2a-continuations-store.ts +11 -0
  299. package/src/integrations/config-store.ts +13 -0
  300. package/src/integrations/google-docs-poller.ts +26 -2
  301. package/src/mcp-client/routes.ts +33 -2
  302. package/src/onboarding/plugin.ts +31 -25
  303. package/src/org/accept-pending.ts +2 -0
  304. package/src/org/auto-join-domain.ts +2 -0
  305. package/src/org/context.ts +35 -10
  306. package/src/org/handlers.ts +6 -0
  307. package/src/org/request-org-cache.ts +71 -0
  308. package/src/resources/store.ts +10 -9
  309. package/src/scripts/runner.ts +6 -3
  310. package/src/secrets/storage.ts +97 -5
  311. package/src/server/action-change.ts +33 -0
  312. package/src/server/action-routes.ts +11 -9
  313. package/src/server/agent-teams.ts +10 -2
  314. package/src/server/better-auth-instance.ts +39 -12
  315. package/src/server/core-routes-plugin.ts +19 -2
  316. package/src/server/credential-provider.ts +57 -14
  317. package/src/server/http-response-telemetry.ts +1 -0
  318. package/src/server/poll.ts +95 -57
  319. package/src/server/voice-providers-status.ts +19 -4
  320. package/src/workspace-files/tool.ts +18 -11
@@ -0,0 +1,257 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import {
3
+ compareAndSetAppState,
4
+ compareAndSetManyAppState,
5
+ readAppState,
6
+ } from "@agent-native/core/application-state";
7
+ import { assertAccess } from "@agent-native/core/sharing";
8
+ import { z } from "zod";
9
+
10
+ import { WorkflowKindSchema } from "../shared/workflow.js";
11
+
12
+ const WorkflowStateSchema = z
13
+ .object({
14
+ kind: WorkflowKindSchema.optional(),
15
+ status: z.string().optional(),
16
+ content: z.string().optional(),
17
+ recordingId: z.string().optional(),
18
+ requestedAt: z.string().optional(),
19
+ tabId: z.string().optional(),
20
+ claimedAt: z.string().optional(),
21
+ })
22
+ .passthrough();
23
+
24
+ const WorkflowRequestSchema = z
25
+ .object({
26
+ requestedAt: z.string(),
27
+ deliveredAt: z.string().optional(),
28
+ deliveredTabId: z.string().optional(),
29
+ })
30
+ .passthrough();
31
+
32
+ const CLAIM_LEASE_MS = 30_000;
33
+
34
+ export default defineAction({
35
+ description:
36
+ "Track and reconcile the agent run responsible for a generated workflow.",
37
+ agentTool: false,
38
+ schema: z.object({
39
+ operation: z.enum([
40
+ "track",
41
+ "release",
42
+ "mark-delivered",
43
+ "consume",
44
+ "stop",
45
+ ]),
46
+ recordingId: z.string().min(1),
47
+ requestedAt: z.string().min(1),
48
+ tabId: z.string().min(1),
49
+ }),
50
+ run: async ({ operation, recordingId, requestedAt, tabId }) => {
51
+ await assertAccess("recording", recordingId, "viewer");
52
+
53
+ const requestKey = `clips-ai-request-${recordingId}`;
54
+ if (operation === "mark-delivered" || operation === "consume") {
55
+ const rawRequest = await readAppState(requestKey);
56
+ if (rawRequest === null) {
57
+ return operation === "consume"
58
+ ? { reconciled: false, consumed: true, reason: "missing" as const }
59
+ : { reconciled: false, delivered: true, reason: "missing" as const };
60
+ }
61
+ const parsedRequest = WorkflowRequestSchema.safeParse(rawRequest);
62
+ if (!parsedRequest.success) {
63
+ throw new Error(`Invalid workflow request state for ${recordingId}`);
64
+ }
65
+ if (parsedRequest.data.requestedAt !== requestedAt) {
66
+ return operation === "consume"
67
+ ? {
68
+ reconciled: false,
69
+ consumed: true,
70
+ reason: "newer-request" as const,
71
+ }
72
+ : {
73
+ reconciled: false,
74
+ delivered: true,
75
+ reason: "newer-request" as const,
76
+ };
77
+ }
78
+ if (operation === "mark-delivered") {
79
+ if (parsedRequest.data.deliveredTabId === tabId) {
80
+ return { reconciled: false, delivered: true };
81
+ }
82
+ if (parsedRequest.data.deliveredTabId) {
83
+ return {
84
+ reconciled: false,
85
+ delivered: false,
86
+ reason: "different-run" as const,
87
+ };
88
+ }
89
+
90
+ const stateKey = `clips-workflow-${recordingId}`;
91
+ const rawState = await readAppState(stateKey);
92
+ if (rawState === null) {
93
+ return { reconciled: false, delivered: false, reason: "missing" };
94
+ }
95
+ const parsedState = WorkflowStateSchema.safeParse(rawState);
96
+ if (!parsedState.success) {
97
+ throw new Error(
98
+ `Invalid generated workflow state for ${recordingId}`,
99
+ );
100
+ }
101
+ if (parsedState.data.status !== "generating") {
102
+ const consumed = await compareAndSetAppState(
103
+ requestKey,
104
+ rawRequest,
105
+ null,
106
+ );
107
+ return consumed
108
+ ? {
109
+ reconciled: false,
110
+ delivered: true,
111
+ consumed: true,
112
+ reason: "terminal",
113
+ }
114
+ : {
115
+ reconciled: false,
116
+ delivered: false,
117
+ consumed: false,
118
+ reason: "stale",
119
+ };
120
+ }
121
+ if (
122
+ parsedState.data.requestedAt !== requestedAt ||
123
+ parsedState.data.tabId !== tabId
124
+ ) {
125
+ return {
126
+ reconciled: false,
127
+ delivered: false,
128
+ reason: "different-run",
129
+ };
130
+ }
131
+
132
+ const deliveredAt = new Date().toISOString();
133
+ const delivered = await compareAndSetManyAppState([
134
+ {
135
+ key: stateKey,
136
+ expectedValue: rawState,
137
+ nextValue: { ...rawState, claimedAt: deliveredAt },
138
+ },
139
+ {
140
+ key: requestKey,
141
+ expectedValue: rawRequest,
142
+ nextValue: { ...rawRequest, deliveredAt, deliveredTabId: tabId },
143
+ },
144
+ ]);
145
+ return delivered
146
+ ? { reconciled: false, delivered: true }
147
+ : { reconciled: false, delivered: false, reason: "stale" as const };
148
+ }
149
+ if (parsedRequest.data.deliveredTabId !== tabId) {
150
+ return {
151
+ reconciled: false,
152
+ consumed: false,
153
+ reason: "not-delivered" as const,
154
+ };
155
+ }
156
+ const consumed = await compareAndSetAppState(
157
+ requestKey,
158
+ rawRequest,
159
+ null,
160
+ );
161
+ return consumed
162
+ ? { reconciled: false, consumed: true }
163
+ : { reconciled: false, consumed: false, reason: "stale" as const };
164
+ }
165
+
166
+ const stateKey = `clips-workflow-${recordingId}`;
167
+ const rawState = await readAppState(stateKey);
168
+ if (rawState === null) {
169
+ return { reconciled: false, reason: "missing" as const };
170
+ }
171
+
172
+ const parsedState = WorkflowStateSchema.safeParse(rawState);
173
+ if (!parsedState.success) {
174
+ throw new Error(`Invalid generated workflow state for ${recordingId}`);
175
+ }
176
+
177
+ const state = parsedState.data;
178
+ if (state.status !== "generating") {
179
+ return { reconciled: false, reason: "terminal" as const };
180
+ }
181
+ if (state.requestedAt !== requestedAt) {
182
+ return { reconciled: false, reason: "newer-request" as const };
183
+ }
184
+
185
+ if (operation === "track") {
186
+ if (state.tabId === tabId) {
187
+ return { reconciled: false, tracked: true };
188
+ }
189
+ if (state.tabId) {
190
+ const claimedAt = Date.parse(
191
+ state.claimedAt ?? state.requestedAt ?? "",
192
+ );
193
+ if (
194
+ !Number.isFinite(claimedAt) ||
195
+ Date.now() - claimedAt < CLAIM_LEASE_MS
196
+ ) {
197
+ return {
198
+ reconciled: false,
199
+ tracked: false,
200
+ reason: "claimed" as const,
201
+ };
202
+ }
203
+ }
204
+ const rawRequest = await readAppState(requestKey);
205
+ if (rawRequest === null) {
206
+ return { reconciled: false, reason: "request-missing" as const };
207
+ }
208
+ const parsedRequest = WorkflowRequestSchema.safeParse(rawRequest);
209
+ if (!parsedRequest.success) {
210
+ throw new Error(`Invalid workflow request state for ${recordingId}`);
211
+ }
212
+ if (parsedRequest.data.requestedAt !== requestedAt) {
213
+ return { reconciled: false, reason: "newer-request" as const };
214
+ }
215
+
216
+ const tracked = await compareAndSetAppState(stateKey, rawState, {
217
+ ...rawState,
218
+ tabId,
219
+ claimedAt: new Date().toISOString(),
220
+ });
221
+ return tracked
222
+ ? { reconciled: false, tracked: true }
223
+ : { reconciled: false, tracked: false, reason: "stale" as const };
224
+ }
225
+ if (state.tabId !== tabId) {
226
+ return operation === "release"
227
+ ? {
228
+ reconciled: false,
229
+ released: true,
230
+ reason: "different-run" as const,
231
+ }
232
+ : { reconciled: false, reason: "different-run" as const };
233
+ }
234
+ if (operation === "release") {
235
+ const untrackedState = { ...rawState };
236
+ delete untrackedState.tabId;
237
+ delete untrackedState.claimedAt;
238
+ const released = await compareAndSetAppState(
239
+ stateKey,
240
+ rawState,
241
+ untrackedState,
242
+ );
243
+ return released
244
+ ? { reconciled: false, released: true }
245
+ : { reconciled: false, released: false, reason: "stale" as const };
246
+ }
247
+
248
+ const reconciled = await compareAndSetAppState(stateKey, rawState, {
249
+ ...rawState,
250
+ status: "failed",
251
+ failedAt: new Date().toISOString(),
252
+ });
253
+ return reconciled
254
+ ? { reconciled: true }
255
+ : { reconciled: false, reason: "stale" as const };
256
+ },
257
+ });
@@ -1,5 +1,7 @@
1
1
  import {
2
+ generateTabId,
2
3
  sendToAgentChat,
4
+ sendToAgentChatAndConfirm,
3
5
  type AgentChatMessage,
4
6
  } from "@agent-native/core/client/agent-chat";
5
7
  import { agentNativePath } from "@agent-native/core/client/api-path";
@@ -11,6 +13,8 @@ import { useRecordings, type RecordingSummary } from "./use-library";
11
13
 
12
14
  const DEFAULT_TITLE = "Untitled recording";
13
15
  const TWO_MINUTES_MS = 2 * 60 * 1000;
16
+ export const WORKFLOW_ACTION_MAX_ATTEMPTS = 5;
17
+ const WORKFLOW_ACTION_RETRY_DELAY_MS = 1000;
14
18
 
15
19
  /** True when `title` is blank or equal to the server-seeded default. */
16
20
  export function isDefaultTitle(title: string | null | undefined): boolean {
@@ -45,6 +49,8 @@ interface AiRequest {
45
49
  message?: string;
46
50
  includeFullVideoInAi?: boolean;
47
51
  openInChat?: boolean;
52
+ deliveredAt?: string;
53
+ deliveredTabId?: string;
48
54
  }
49
55
 
50
56
  const DISPATCHABLE_REQUESTS = new Set([
@@ -99,6 +105,36 @@ export function useAutoTitleBridge(): void {
99
105
  const dispatched = useRef<Set<string>>(new Set());
100
106
  const inflight = useRef<boolean>(false);
101
107
 
108
+ useEffect(() => {
109
+ const handleChatRunning = (event: Event) => {
110
+ const detail = (event as CustomEvent).detail;
111
+ if (
112
+ detail?.isRunning !== false ||
113
+ (detail.reason !== "stopped" && detail.reason !== "failed") ||
114
+ typeof detail.tabId !== "string"
115
+ )
116
+ return;
117
+
118
+ const recordingId = recordingIdFromTab(detail.tabId);
119
+ const requestedAt = requestedAtFromTab(detail.tabId);
120
+ if (!recordingId || !requestedAt) return;
121
+
122
+ void retryWorkflowAction(
123
+ {
124
+ operation: "stop",
125
+ recordingId,
126
+ requestedAt,
127
+ tabId: detail.tabId,
128
+ },
129
+ "reconciled",
130
+ );
131
+ };
132
+
133
+ window.addEventListener("agentNative.chatRunning", handleChatRunning);
134
+ return () =>
135
+ window.removeEventListener("agentNative.chatRunning", handleChatRunning);
136
+ }, []);
137
+
102
138
  const readyRecordings = recordings.filter((r) => r.status === "ready");
103
139
  const readyRecordingsKey = readyRecordings
104
140
  .map(
@@ -144,7 +180,6 @@ export function useAutoTitleBridge(): void {
144
180
  request.requestedAt ?? "0"
145
181
  }`;
146
182
  if (dispatched.current.has(dispatchKey)) continue;
147
- dispatched.current.add(dispatchKey);
148
183
  if (
149
184
  request.kind === "generate-metadata" ||
150
185
  request.kind === "regenerate-title"
@@ -155,8 +190,67 @@ export function useAutoTitleBridge(): void {
155
190
  dispatched.current.add(`${rec.id}:fallback`);
156
191
  }
157
192
 
158
- dispatchAiRequest(rec, request);
193
+ if (
194
+ request.kind === "generate-workflow" &&
195
+ typeof request.requestedAt === "string"
196
+ ) {
197
+ if (request.deliveredTabId) {
198
+ dispatched.current.add(dispatchKey);
199
+ void consumeWorkflowRequest({
200
+ recordingId: rec.id,
201
+ requestedAt: request.requestedAt,
202
+ tabId: request.deliveredTabId,
203
+ });
204
+ continue;
205
+ }
159
206
 
207
+ const tabId = workflowTabId(rec.id, request.requestedAt);
208
+ try {
209
+ const result = (await callAction(
210
+ "reconcile-workflow-generation" as any,
211
+ {
212
+ operation: "track",
213
+ recordingId: rec.id,
214
+ requestedAt: request.requestedAt,
215
+ tabId,
216
+ } as any,
217
+ )) as { tracked?: boolean };
218
+ if (result.tracked !== true) {
219
+ fallbackTimer = setTimeout(() => void tick(), 1000);
220
+ continue;
221
+ }
222
+ } catch {
223
+ fallbackTimer = setTimeout(() => void tick(), 1000);
224
+ continue;
225
+ }
226
+ const delivery = await sendToAgentChatAndConfirm({
227
+ ...buildAiRequestChatOptions(rec, request),
228
+ tabId,
229
+ chatTarget: "local",
230
+ });
231
+ if (!delivery.delivered) {
232
+ await retryWorkflowAction(
233
+ {
234
+ operation: "release",
235
+ recordingId: rec.id,
236
+ requestedAt: request.requestedAt,
237
+ tabId,
238
+ },
239
+ "released",
240
+ );
241
+ fallbackTimer = setTimeout(() => void tick(), 1000);
242
+ continue;
243
+ }
244
+ dispatched.current.add(dispatchKey);
245
+ void persistAndConsumeWorkflowRequest({
246
+ recordingId: rec.id,
247
+ requestedAt: request.requestedAt,
248
+ tabId,
249
+ });
250
+ continue;
251
+ }
252
+ dispatchAiRequest(rec, request);
253
+ dispatched.current.add(dispatchKey);
160
254
  void clearRequest(rec.id);
161
255
  } else if (isAutoTitleReplaceable(rec.title, rec.titleSource)) {
162
256
  // No server-queued delegation. Only dispatch the fallback for
@@ -294,8 +388,76 @@ export function buildAiRequestChatOptions(
294
388
  };
295
389
  }
296
390
 
297
- function dispatchAiRequest(rec: RecordingSummary, request: AiRequest) {
298
- sendToAgentChat(buildAiRequestChatOptions(rec, request));
391
+ interface WorkflowRunRequest {
392
+ recordingId: string;
393
+ requestedAt: string;
394
+ tabId: string;
395
+ }
396
+
397
+ export async function retryWorkflowAction(
398
+ request: WorkflowRunRequest & { operation: string },
399
+ successKey: string,
400
+ ): Promise<boolean> {
401
+ for (let attempt = 0; attempt < WORKFLOW_ACTION_MAX_ATTEMPTS; attempt += 1) {
402
+ try {
403
+ const result = (await callAction(
404
+ "reconcile-workflow-generation" as any,
405
+ request as any,
406
+ )) as Record<string, unknown>;
407
+ if (result[successKey] === true) return true;
408
+ if (typeof result.reason === "string" && result.reason !== "stale") {
409
+ return false;
410
+ }
411
+ } catch {}
412
+
413
+ if (attempt === WORKFLOW_ACTION_MAX_ATTEMPTS - 1) return false;
414
+ await new Promise((resolve) =>
415
+ setTimeout(resolve, WORKFLOW_ACTION_RETRY_DELAY_MS * 2 ** attempt),
416
+ );
417
+ }
418
+
419
+ return false;
420
+ }
421
+
422
+ async function consumeWorkflowRequest(
423
+ request: WorkflowRunRequest,
424
+ ): Promise<boolean> {
425
+ return retryWorkflowAction({ ...request, operation: "consume" }, "consumed");
426
+ }
427
+
428
+ async function persistAndConsumeWorkflowRequest(
429
+ request: WorkflowRunRequest,
430
+ ): Promise<void> {
431
+ const delivered = await retryWorkflowAction(
432
+ { ...request, operation: "mark-delivered" },
433
+ "delivered",
434
+ );
435
+ if (delivered) await consumeWorkflowRequest(request);
436
+ }
437
+
438
+ function workflowTabId(recordingId: string, requestedAt: string) {
439
+ return `clips-workflow:${recordingId}:${encodeURIComponent(requestedAt)}:${generateTabId()}`;
440
+ }
441
+
442
+ function recordingIdFromTab(tabId: string) {
443
+ const match = /^clips-workflow:([^:]+):/.exec(tabId);
444
+ return match?.[1];
445
+ }
446
+
447
+ function requestedAtFromTab(tabId: string) {
448
+ const match = /^clips-workflow:[^:]+:([^:]+):/.exec(tabId);
449
+ return match ? decodeURIComponent(match[1]) : undefined;
450
+ }
451
+
452
+ function dispatchAiRequest(
453
+ rec: RecordingSummary,
454
+ request: AiRequest,
455
+ tabId?: string,
456
+ ) {
457
+ return sendToAgentChat({
458
+ ...buildAiRequestChatOptions(rec, request),
459
+ ...(tabId ? { tabId } : {}),
460
+ });
299
461
  }
300
462
 
301
463
  function parseJsonArray(raw: string | undefined): unknown[] {
@@ -27,6 +27,7 @@ import {
27
27
  DASHBOARD_REDIRECT_VALUE,
28
28
  REF_PARAM,
29
29
  } from "@shared/share-attribution";
30
+ import type { WorkflowKind } from "@shared/workflow";
30
31
  import {
31
32
  IconShare3,
32
33
  IconArrowLeft,
@@ -109,7 +110,6 @@ export function meta() {
109
110
  }
110
111
 
111
112
  type SidePanel = "transcript" | "comments" | "insights" | "agent" | "settings";
112
- type WorkflowKind = "pr" | "sop" | "ticket" | "email";
113
113
 
114
114
  const WORKFLOW_MENU_ITEMS: Array<{
115
115
  kind: WorkflowKind;
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-27
4
+ ---
5
+
6
+ Stopped agent runs no longer leave generated workflow cards spinning indefinitely.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-30
4
+ ---
5
+
6
+ Workflow generation now stops retrying after repeated failures instead of remaining active indefinitely.
@@ -0,0 +1,5 @@
1
+ import { z } from "zod";
2
+
3
+ export const WORKFLOW_KINDS = ["pr", "sop", "ticket", "email"] as const;
4
+ export const WorkflowKindSchema = z.enum(WORKFLOW_KINDS);
5
+ export type WorkflowKind = z.infer<typeof WorkflowKindSchema>;
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-30
4
+ ---
5
+
6
+ Long-running delegated requests now continue reliably.
@@ -10,6 +10,9 @@ GTM_CONTAINER_ID = "GTM-N3WSTXZ"
10
10
  NITRO_PRESET = "netlify"
11
11
  NPM_CONFIG_PRODUCTION = "false"
12
12
  AGENT_RUN_SOFT_TIMEOUT_MS = "55000"
13
+ # Content agent turns can delegate long-running work to sibling apps. Emit and
14
+ # use the async worker so those calls are not cut off by the foreground wall.
15
+ AGENT_CHAT_DURABLE_BACKGROUND = "true"
13
16
 
14
17
  # A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
15
18
  # The framework does not impose a default agent-run timeout; if a template sets
@@ -31,6 +31,7 @@ const INITIAL_TOOL_NAMES = [
31
31
 
32
32
  export default createAgentChatPlugin({
33
33
  appId: "content",
34
+ durableBackgroundRuns: true,
34
35
  actions: loadActionsFromStaticRegistry(actionsRegistry),
35
36
  initialToolNames: INITIAL_TOOL_NAMES,
36
37
  anonymousOwner: resolvePublicViewerOwner,
@@ -23,7 +23,8 @@ description: How to create a new deck with slides from scratch. Read this before
23
23
  When the UI has already created the empty deck, keep its id and rename it before
24
24
  adding the first slide. Call `patch-deck` with a `patch-deck-fields` operation
25
25
  whose `fields.title` is the generated title. Do not leave the pre-created deck's
26
- placeholder title in place.
26
+ placeholder title in place. Include only `title` in that operation's `fields`;
27
+ omit all other optional fields.
27
28
 
28
29
  Follow the creative-context reuse ladder before inventing a slide language:
29
30
  reuse an approved native template unchanged, compose approved pieces, lightly
@@ -141,6 +141,27 @@ export const OperationSchema = z.discriminatedUnion("op", [
141
141
 
142
142
  export type Operation = z.infer<typeof OperationSchema>;
143
143
 
144
+ // The browser uses the full operation union above, but the agent only needs
145
+ // this action to rename the empty deck created by the UI. Keeping the
146
+ // advertised schema sparse prevents the model from filling editor-only
147
+ // metadata fields with empty defaults that the runtime correctly rejects.
148
+ const AgentPatchDeckInputSchema = z.object({
149
+ deckId: z.string().describe("Deck ID"),
150
+ operations: z
151
+ .array(
152
+ z.object({
153
+ op: z.literal("patch-deck-fields"),
154
+ fields: z.object({
155
+ title: z
156
+ .string()
157
+ .describe("The concise, specific title to apply to the deck"),
158
+ }),
159
+ }),
160
+ )
161
+ .min(1)
162
+ .describe("Rename the pre-created deck before adding its first slide"),
163
+ });
164
+
144
165
  const CreativeContextReuseLabelSchema = z.object({
145
166
  itemId: z.string().min(1).optional(),
146
167
  itemVersionId: z.string().min(1).optional(),
@@ -323,7 +344,9 @@ export default defineAction({
323
344
  description:
324
345
  "Granular deck patch used by the browser editor for concurrent-safe writes. " +
325
346
  "Each operation touches only the target slide or field — concurrent writers " +
326
- "on different slides never overwrite each other's work.",
347
+ "on different slides never overwrite each other's work. When called by the " +
348
+ "agent, use it only to set the generated deck title and include no other " +
349
+ "fields.",
327
350
  schema: z.object({
328
351
  deckId: z.string().describe("Deck ID"),
329
352
  operations: z
@@ -344,6 +367,7 @@ export default defineAction({
344
367
  "Optional exact Creative Context provenance for context-backed slide patch operations.",
345
368
  ),
346
369
  }),
370
+ agentInputSchema: AgentPatchDeckInputSchema,
347
371
  run: async ({ deckId, operations, creativeContext }) => {
348
372
  await assertAccess("deck", deckId, "editor");
349
373
 
@@ -23,6 +23,7 @@ import {
23
23
  DropdownMenuSeparator,
24
24
  } from "@/components/ui/dropdown-menu";
25
25
  import type { Deck } from "@/context/DeckContext";
26
+ import { getAspectRatioDims } from "@/lib/aspect-ratios";
26
27
 
27
28
  import SlideRenderer from "./SlideRenderer";
28
29
 
@@ -51,6 +52,7 @@ export default function DeckCard({
51
52
  }: DeckCardProps) {
52
53
  const t = useT();
53
54
  const firstSlide = deck.slides?.[0];
55
+ const previewDims = getAspectRatioDims(deck.aspectRatio);
54
56
  const [isRenaming, setIsRenaming] = useState(false);
55
57
  const [menuOpen, setMenuOpen] = useState(false);
56
58
  const [renameValue, setRenameValue] = useState(deck.title);
@@ -104,7 +106,12 @@ export default function DeckCard({
104
106
  }}
105
107
  >
106
108
  {/* Slide Preview */}
107
- <div className="overflow-hidden relative">
109
+ <div
110
+ className="relative overflow-hidden bg-muted/30"
111
+ style={{
112
+ aspectRatio: `${previewDims.width} / ${previewDims.height}`,
113
+ }}
114
+ >
108
115
  {firstSlide && (
109
116
  <SlideRenderer
110
117
  slide={firstSlide}
@@ -112,7 +119,7 @@ export default function DeckCard({
112
119
  aspectRatio={deck.aspectRatio}
113
120
  />
114
121
  )}
115
- <div className="absolute inset-0 bg-gradient-to-t from-[hsl(240,5%,8%)] via-transparent to-transparent opacity-60" />
122
+ <div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-[hsl(240,5%,8%)] via-transparent to-transparent opacity-60" />
116
123
  </div>
117
124
 
118
125
  {/* Info */}
@@ -171,7 +178,9 @@ export default function DeckCard({
171
178
  onToggleStar(deck.id, !deck.starred);
172
179
  }}
173
180
  className={`rounded-md border border-border bg-black/60 p-2 backdrop-blur-sm hover:bg-black/80 sm:p-1.5 ${
174
- deck.starred ? "" : "sm:opacity-0 sm:group-hover:opacity-100"
181
+ deck.starred
182
+ ? ""
183
+ : "sm:invisible sm:pointer-events-none sm:opacity-0 sm:group-hover:visible sm:group-hover:pointer-events-auto sm:group-hover:opacity-100 sm:group-focus-within:visible sm:group-focus-within:pointer-events-auto sm:group-focus-within:opacity-100"
175
184
  }`}
176
185
  aria-label={
177
186
  deck.starred ? t("home.unstarDeck") : t("home.starDeck")
@@ -192,7 +201,11 @@ export default function DeckCard({
192
201
  e.preventDefault();
193
202
  e.stopPropagation();
194
203
  }}
195
- className="p-2 sm:p-1.5 rounded-md bg-black/60 backdrop-blur-sm border border-border hover:bg-black/80 sm:opacity-0 sm:group-hover:opacity-100"
204
+ className={`rounded-md border border-border bg-black/60 p-2 backdrop-blur-sm hover:bg-black/80 sm:p-1.5 ${
205
+ menuOpen
206
+ ? "sm:visible sm:pointer-events-auto sm:opacity-100"
207
+ : "sm:invisible sm:pointer-events-none sm:opacity-0 sm:group-hover:visible sm:group-hover:pointer-events-auto sm:group-hover:opacity-100 sm:group-focus-within:visible sm:group-focus-within:pointer-events-auto sm:group-focus-within:opacity-100"
208
+ }`}
196
209
  aria-label={t("raw.deckOptions")}
197
210
  >
198
211
  <IconDots className="w-3.5 h-3.5 text-foreground/70" />