@agent-native/core 0.146.0 → 0.146.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +4 -0
  3. package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  4. package/corpus/templates/analytics/actions/compose-dashboard.ts +2 -27
  5. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +7 -8
  6. package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +18 -2
  7. package/corpus/templates/analytics/actions/mutate-dashboard.ts +16 -24
  8. package/corpus/templates/analytics/actions/rename-dashboard.ts +2 -24
  9. package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +2 -24
  10. package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +2 -25
  11. package/corpus/templates/analytics/actions/update-dashboard.ts +4 -32
  12. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +1 -40
  13. package/corpus/templates/analytics/changelog/2026-08-07-language-selection-now-lives-in-settings-instead-of-the-comm.md +6 -0
  14. package/corpus/templates/analytics/netlify.toml +7 -0
  15. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +13 -13
  16. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +4 -2
  17. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +0 -35
  18. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +5 -3
  19. package/corpus/templates/analytics/server/lib/dashboard-collab-sync.ts +77 -0
  20. package/corpus/templates/analytics/server/lib/demo-dashboards.ts +2 -23
  21. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +28 -74
  22. package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +5 -9
  23. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +4 -0
  24. package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  25. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +4 -0
  26. package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  27. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +4 -0
  28. package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  29. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +4 -0
  30. package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  31. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +4 -0
  32. package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  33. package/corpus/templates/clips/app/components/player/video-player.tsx +1 -1
  34. package/corpus/templates/clips/changelog/2026-08-07-fixed-trimmed-clips-durations-so-the-main-player-display-mat.md +6 -0
  35. package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +1 -1
  36. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  37. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +4 -0
  38. package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  39. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +4 -0
  40. package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  41. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +4 -0
  42. package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  43. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +4 -0
  44. package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  45. package/corpus/templates/dispatch/changelog/2026-08-07-language-selection-now-lives-in-settings-instead-of-the-disp.md +6 -0
  46. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +4 -0
  47. package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  48. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +59 -22
  49. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +4 -0
  50. package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  51. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +4 -0
  52. package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  53. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +4 -0
  54. package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  55. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +4 -0
  56. package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  57. package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +10 -1
  58. package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +10 -3
  59. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +4 -0
  60. package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  61. package/corpus/templates/slides/actions/generate-image-api.ts +123 -0
  62. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -2
  63. package/corpus/templates/slides/app/components/editor/ImageGenPanel.tsx +1 -1
  64. package/corpus/templates/slides/changelog/2026-08-07-deleting-a-deck-no-longer-freezes-the-slides-browser.md +6 -0
  65. package/corpus/templates/slides/changelog/2026-08-07-slides-verify-generated-images-are-persisted-on-the-target-s.md +6 -0
  66. package/corpus/templates/slides/server/lib/slide-image-insertion.ts +141 -0
  67. package/corpus/templates/slides/server/plugins/agent-chat.ts +2 -0
  68. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +4 -0
  69. package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  70. package/dist/client/AgentPanel.d.ts +10 -4
  71. package/dist/client/AgentPanel.js +29 -82
  72. package/dist/collab/awareness.d.ts +2 -2
  73. package/dist/collab/struct-routes.d.ts +1 -1
  74. package/dist/db/migrations.js +22 -4
  75. package/dist/deploy/build.d.ts +1 -0
  76. package/dist/deploy/build.js +27 -22
  77. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  78. package/dist/notifications/routes.d.ts +1 -1
  79. package/dist/observability/routes.d.ts +1 -1
  80. package/dist/resources/handlers.d.ts +1 -1
  81. package/dist/server/builder-browser.d.ts +34 -0
  82. package/dist/server/builder-browser.js +179 -0
  83. package/dist/server/core-routes-plugin.d.ts +5 -0
  84. package/dist/server/core-routes-plugin.js +24 -8
  85. package/dist/server/index.d.ts +1 -1
  86. package/dist/server/index.js +1 -1
  87. package/dist/shared/cache-control.d.ts +2 -2
  88. package/dist/shared/cache-control.js +2 -2
  89. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +4 -0
  90. package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  91. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +4 -0
  92. package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  93. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +4 -0
  94. package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  95. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +4 -0
  96. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  97. package/dist/vite/client.js +13 -1
  98. package/docs/content/drop-in-agent.mdx +3 -3
  99. package/docs/content/getting-started-actions.mdx +1 -2
  100. package/docs/content/getting-started-pages.mdx +1 -2
  101. package/docs/content/getting-started.mdx +0 -4
  102. package/docs/content/what-is-agent-native.mdx +4 -4
  103. package/package.json +3 -3
  104. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +4 -0
  105. package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  106. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +4 -0
  107. package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  108. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +4 -0
  109. package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  110. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +4 -0
  111. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  112. package/docs/content/getting-started-database.mdx +0 -173
@@ -50,6 +50,14 @@ skills. For a ChatGPT Project or any host that exposes only the MCP connector,
50
50
  also place the skill instructions or this reference in the Project
51
51
  instructions/knowledge files. Then add and authenticate the Dispatch MCP
52
52
  connector from the packaged `adapters/chatgpt-mcp/connector.json`.
53
+ This single connector is enough: Dispatch provisions or reuses the Builder
54
+ project through the Builder Projects API, so do not ask the user to add a
55
+ separate Builder CMS MCP for app creation.
56
+ Builder's separate Fusion MCP is available as a custom remote connector at
57
+ `https://mcp.builder.io/mcp/fusion` for direct Builder work. It can run an
58
+ existing project, but it cannot provision the repo-backed Agent-Native
59
+ workspace project, so it is optional and not a replacement for the Dispatch
60
+ handoff in this skill. Do not add Builder's CMS MCP for this workflow.
53
61
 
54
62
  For Claude Web, ChatGPT Web, and their web Projects, the host is an
55
63
  orchestrator, not the build environment. After forming the source brief, call
@@ -169,9 +169,10 @@ const AUTOMATION_SEEDS: AutomationSeed[] = [
169
169
  # Factory Slack feedback triage
170
170
 
171
171
  Read the Factory configuration. When Slack polling is enabled and a channel is
172
- configured, call poll-slack-channel first. Then list a bounded page of
173
- received Slack items and call get-slack-feedback-context for each item before
174
- classifying it.
172
+ configured, call poll-slack-channel first. Then list at most 2 received Slack
173
+ items by passing limit 2, process them sequentially, and call
174
+ get-slack-feedback-context for each item before classifying it. Never use the
175
+ action's default page size.
175
176
 
176
177
  Start work only for a clear bug: a concrete broken behavior, reproducible
177
178
  failure, error, regression, stuck run, incorrect result, or a report with a
@@ -203,9 +204,9 @@ PR, merge, or fix unless an action returned that state.
203
204
  # Factory Sentry error triage
204
205
 
205
206
  Read the Factory configuration. When Sentry polling is enabled and a Sentry
206
- organization is configured, call poll-sentry-errors. List a bounded page of
207
- received Sentry items and inspect the title, culprit, level, event count, and
208
- errorReport metadata.
207
+ organization is configured, call poll-sentry-errors. List at most 3 received
208
+ Sentry items by passing limit 3 and inspect the title, culprit, level, event
209
+ count, and errorReport metadata. Never use the action's default page size.
209
210
 
210
211
  Only classify a concrete unresolved error as a clear bug when the Sentry
211
212
  evidence is sufficient to investigate. Do not dispatch on noise, expected
@@ -226,7 +227,8 @@ run callback or PR observation confirms it.
226
227
 
227
228
  Read the Factory configuration. When GitHub source polling is enabled and a
228
229
  repository is configured, call poll-github-sources with includeIssues true and
229
- includePullRequests false. List a bounded page of received github_issue items.
230
+ includePullRequests false. List at most 3 received github_issue items by
231
+ passing limit 3. Never use the action's default page size.
230
232
 
231
233
  Treat an issue as a clear bug only when it has a concrete error report,
232
234
  reproduction, incorrect behavior, regression, or specific failing path. Do
@@ -247,7 +249,8 @@ confirmation.
247
249
 
248
250
  Read the Factory configuration. When GitHub polling is enabled and a repository
249
251
  is configured, call poll-github-sources with includeIssues false and
250
- includePullRequests true. List a bounded page of github pull-request items.
252
+ includePullRequests true. List at most 3 github pull-request items by passing
253
+ limit 3. Never use the action's default page size.
251
254
 
252
255
  For each open agent-native PR, inspect the item and classify whether it is a
253
256
  clear bug fix or has product or UX implications. Call
@@ -275,7 +278,8 @@ governance action confirms it.
275
278
 
276
279
  Read the Factory configuration. When GitHub polling is enabled and a repository
277
280
  is configured, call poll-github-sources with includeIssues false and
278
- includePullRequests true. List a bounded page of open github pull-request items.
281
+ includePullRequests true. List at most 3 open github pull-request items by
282
+ passing limit 3. Never use the action's default page size.
279
283
 
280
284
  For each item, call babysit-agent-native-pull-request. That action fetches fresh
281
285
  GitHub and ai-services evidence and is the only place allowed to decide whether
@@ -312,6 +316,22 @@ function defaultGithubPollingEnabled(): 0 | 1 {
312
316
  : 0;
313
317
  }
314
318
 
319
+ function automationPromptGuard(name: string): string | undefined {
320
+ switch (name) {
321
+ case "factory-slack-feedback":
322
+ return "Runtime safety bound: pass limit 2 to list-triage-items, process at most two Slack items sequentially, and never use the default page size.";
323
+ case "factory-sentry-errors":
324
+ return "Runtime safety bound: pass limit 3 to list-triage-items and process at most three Sentry items.";
325
+ case "factory-github-issues":
326
+ return "Runtime safety bound: pass limit 3 to list-triage-items and process at most three GitHub issue items.";
327
+ case "factory-pr-governance":
328
+ case "factory-pr-babysit":
329
+ return "Runtime safety bound: pass limit 3 to list-triage-items and process at most three pull-request items.";
330
+ default:
331
+ return undefined;
332
+ }
333
+ }
334
+
315
335
  function setFrontmatterField(
316
336
  content: string,
317
337
  key: string,
@@ -388,6 +408,10 @@ async function ensureOrganizationAutomations(
388
408
  repaired = setFrontmatterField(repaired, "orgId", orgId);
389
409
  repaired = setFrontmatterField(repaired, "createdBy", ownerEmail);
390
410
  repaired = setFrontmatterField(repaired, "runAs", "creator");
411
+ const promptGuard = automationPromptGuard(seed.name);
412
+ if (promptGuard && !repaired.includes(promptGuard)) {
413
+ repaired = `${repaired.trimEnd()}\n\n${promptGuard}\n`;
414
+ }
391
415
  if (repaired === existing.content) return;
392
416
 
393
417
  const updated = await resourcePutIfCurrent({
@@ -415,11 +439,16 @@ async function ensureDefaultTriageConfig(
415
439
  const db = getDb();
416
440
  const existing = (
417
441
  await db
418
- .select({ id: triageConfig.id })
442
+ .select({
443
+ id: triageConfig.id,
444
+ })
419
445
  .from(triageConfig)
420
446
  .where(and(eq(triageConfig.id, orgId), eq(triageConfig.orgId, orgId)))
421
447
  .limit(1)
422
448
  )[0];
449
+ const repository = defaultRepository();
450
+ // Existing rows are operator-owned. An empty repository plus disabled
451
+ // polling can be an intentional choice, so do not infer bootstrap state.
423
452
  if (existing) return;
424
453
  const now = new Date().toISOString();
425
454
  await db.insert(triageConfig).values({
@@ -432,7 +461,7 @@ async function ensureDefaultTriageConfig(
432
461
  sentryPollingEnabled: 0,
433
462
  lastSlackTs: null,
434
463
  slackHistoryCursor: null,
435
- repository: defaultRepository(),
464
+ repository,
436
465
  sentryOrgSlug: null,
437
466
  sentryProjectSlug: null,
438
467
  sentryEnvironment: null,
@@ -445,17 +474,25 @@ async function ensureDefaultTriageConfig(
445
474
  }
446
475
 
447
476
  async function ensureSchedulerJobs(): Promise<void> {
448
- const ownerEmail = workspaceOwnerEmail();
449
- if (!ownerEmail) {
450
- throw new Error(
451
- "WORKSPACE_OWNER_EMAIL is required to seed Factory automations",
452
- );
453
- }
454
- const orgId = await resolveOrgIdForEmail(ownerEmail);
455
- if (!orgId) {
456
- throw new Error(
457
- "The Factory deployment owner must belong to an organization before automations can be seeded",
458
- );
477
+ let ownerEmail = workspaceOwnerEmail();
478
+ let orgId = ownerEmail ? await resolveOrgIdForEmail(ownerEmail) : undefined;
479
+ if (!ownerEmail || !orgId) {
480
+ const existingConfigs = await getDb()
481
+ .select({
482
+ id: triageConfig.id,
483
+ ownerEmail: triageConfig.ownerEmail,
484
+ orgId: triageConfig.orgId,
485
+ })
486
+ .from(triageConfig)
487
+ .limit(2);
488
+ if (existingConfigs.length !== 1) {
489
+ throw new Error(
490
+ "WORKSPACE_OWNER_EMAIL is required to seed Factory automations when the Factory organization is not uniquely configured",
491
+ );
492
+ }
493
+ const existingConfig = existingConfigs[0];
494
+ ownerEmail = existingConfig.ownerEmail.trim().toLowerCase();
495
+ orgId = existingConfig.orgId?.trim() || existingConfig.id;
459
496
  }
460
497
  await ensureDefaultTriageConfig(ownerEmail, orgId);
461
498
  await ensureOrganizationAutomations(ownerEmail, orgId);
@@ -27,6 +27,10 @@ The build location depends on the host. Treat this as a hard routing rule:
27
27
  `resourceIds` when available. This is the Builder handoff.
28
28
  - Do not substitute the generic `create_workspace_app` MCP tool in an online
29
29
  host. That tool is a local workspace scaffolder, not the Builder handoff.
30
+ - Connect the Agent-Native Dispatch MCP connector only. Dispatch uses the
31
+ authenticated Builder Projects API to reuse or provision the workspace
32
+ project before starting the Builder Cloud Agent; a separate Builder CMS MCP
33
+ connection is not required for this workflow.
30
34
  - If `start-workspace-app-creation` is not available or Dispatch is not
31
35
  authenticated, stop with the connector setup needed. Do not fall back to a
32
36
  local build or claim that the app exists.
@@ -50,6 +50,14 @@ skills. For a ChatGPT Project or any host that exposes only the MCP connector,
50
50
  also place the skill instructions or this reference in the Project
51
51
  instructions/knowledge files. Then add and authenticate the Dispatch MCP
52
52
  connector from the packaged `adapters/chatgpt-mcp/connector.json`.
53
+ This single connector is enough: Dispatch provisions or reuses the Builder
54
+ project through the Builder Projects API, so do not ask the user to add a
55
+ separate Builder CMS MCP for app creation.
56
+ Builder's separate Fusion MCP is available as a custom remote connector at
57
+ `https://mcp.builder.io/mcp/fusion` for direct Builder work. It can run an
58
+ existing project, but it cannot provision the repo-backed Agent-Native
59
+ workspace project, so it is optional and not a replacement for the Dispatch
60
+ handoff in this skill. Do not add Builder's CMS MCP for this workflow.
53
61
 
54
62
  For Claude Web, ChatGPT Web, and their web Projects, the host is an
55
63
  orchestrator, not the build environment. After forming the source brief, call
@@ -27,6 +27,10 @@ The build location depends on the host. Treat this as a hard routing rule:
27
27
  `resourceIds` when available. This is the Builder handoff.
28
28
  - Do not substitute the generic `create_workspace_app` MCP tool in an online
29
29
  host. That tool is a local workspace scaffolder, not the Builder handoff.
30
+ - Connect the Agent-Native Dispatch MCP connector only. Dispatch uses the
31
+ authenticated Builder Projects API to reuse or provision the workspace
32
+ project before starting the Builder Cloud Agent; a separate Builder CMS MCP
33
+ connection is not required for this workflow.
30
34
  - If `start-workspace-app-creation` is not available or Dispatch is not
31
35
  authenticated, stop with the connector setup needed. Do not fall back to a
32
36
  local build or claim that the app exists.
@@ -50,6 +50,14 @@ skills. For a ChatGPT Project or any host that exposes only the MCP connector,
50
50
  also place the skill instructions or this reference in the Project
51
51
  instructions/knowledge files. Then add and authenticate the Dispatch MCP
52
52
  connector from the packaged `adapters/chatgpt-mcp/connector.json`.
53
+ This single connector is enough: Dispatch provisions or reuses the Builder
54
+ project through the Builder Projects API, so do not ask the user to add a
55
+ separate Builder CMS MCP for app creation.
56
+ Builder's separate Fusion MCP is available as a custom remote connector at
57
+ `https://mcp.builder.io/mcp/fusion` for direct Builder work. It can run an
58
+ existing project, but it cannot provision the repo-backed Agent-Native
59
+ workspace project, so it is optional and not a replacement for the Dispatch
60
+ handoff in this skill. Do not add Builder's CMS MCP for this workflow.
53
61
 
54
62
  For Claude Web, ChatGPT Web, and their web Projects, the host is an
55
63
  orchestrator, not the build environment. After forming the source brief, call
@@ -27,6 +27,10 @@ The build location depends on the host. Treat this as a hard routing rule:
27
27
  `resourceIds` when available. This is the Builder handoff.
28
28
  - Do not substitute the generic `create_workspace_app` MCP tool in an online
29
29
  host. That tool is a local workspace scaffolder, not the Builder handoff.
30
+ - Connect the Agent-Native Dispatch MCP connector only. Dispatch uses the
31
+ authenticated Builder Projects API to reuse or provision the workspace
32
+ project before starting the Builder Cloud Agent; a separate Builder CMS MCP
33
+ connection is not required for this workflow.
30
34
  - If `start-workspace-app-creation` is not available or Dispatch is not
31
35
  authenticated, stop with the connector setup needed. Do not fall back to a
32
36
  local build or claim that the app exists.
@@ -50,6 +50,14 @@ skills. For a ChatGPT Project or any host that exposes only the MCP connector,
50
50
  also place the skill instructions or this reference in the Project
51
51
  instructions/knowledge files. Then add and authenticate the Dispatch MCP
52
52
  connector from the packaged `adapters/chatgpt-mcp/connector.json`.
53
+ This single connector is enough: Dispatch provisions or reuses the Builder
54
+ project through the Builder Projects API, so do not ask the user to add a
55
+ separate Builder CMS MCP for app creation.
56
+ Builder's separate Fusion MCP is available as a custom remote connector at
57
+ `https://mcp.builder.io/mcp/fusion` for direct Builder work. It can run an
58
+ existing project, but it cannot provision the repo-backed Agent-Native
59
+ workspace project, so it is optional and not a replacement for the Dispatch
60
+ handoff in this skill. Do not add Builder's CMS MCP for this workflow.
53
61
 
54
62
  For Claude Web, ChatGPT Web, and their web Projects, the host is an
55
63
  orchestrator, not the build environment. After forming the source brief, call
@@ -27,6 +27,10 @@ The build location depends on the host. Treat this as a hard routing rule:
27
27
  `resourceIds` when available. This is the Builder handoff.
28
28
  - Do not substitute the generic `create_workspace_app` MCP tool in an online
29
29
  host. That tool is a local workspace scaffolder, not the Builder handoff.
30
+ - Connect the Agent-Native Dispatch MCP connector only. Dispatch uses the
31
+ authenticated Builder Projects API to reuse or provision the workspace
32
+ project before starting the Builder Cloud Agent; a separate Builder CMS MCP
33
+ connection is not required for this workflow.
30
34
  - If `start-workspace-app-creation` is not available or Dispatch is not
31
35
  authenticated, stop with the connector setup needed. Do not fall back to a
32
36
  local build or claim that the app exists.
@@ -50,6 +50,14 @@ skills. For a ChatGPT Project or any host that exposes only the MCP connector,
50
50
  also place the skill instructions or this reference in the Project
51
51
  instructions/knowledge files. Then add and authenticate the Dispatch MCP
52
52
  connector from the packaged `adapters/chatgpt-mcp/connector.json`.
53
+ This single connector is enough: Dispatch provisions or reuses the Builder
54
+ project through the Builder Projects API, so do not ask the user to add a
55
+ separate Builder CMS MCP for app creation.
56
+ Builder's separate Fusion MCP is available as a custom remote connector at
57
+ `https://mcp.builder.io/mcp/fusion` for direct Builder work. It can run an
58
+ existing project, but it cannot provision the repo-backed Agent-Native
59
+ workspace project, so it is optional and not a replacement for the Dispatch
60
+ handoff in this skill. Do not add Builder's CMS MCP for this workflow.
53
61
 
54
62
  For Claude Web, ChatGPT Web, and their web Projects, the host is an
55
63
  orchestrator, not the build environment. After forming the source brief, call
@@ -53,6 +53,14 @@ Call the action with the destination so Assets can ground the generation:
53
53
  generate-image-api { prompt, deckId, slideId, slideContent }
54
54
  ```
55
55
 
56
+ For direct insertion, add `insertIntoSlide: true`. This requires both IDs and
57
+ only returns `inserted: true` after Slides writes the transformed HTML through
58
+ `update-slide` and re-reads it through `get-deck` to find the image source.
59
+ Never say the image was added based on `url`, `previewUrl`, or a completed
60
+ Assets reply alone. For preview-only variations, leave `insertIntoSlide` false;
61
+ after choosing one, use `update-slide` and verify the persisted source with
62
+ `get-deck` before claiming insertion.
63
+
56
64
  Do **not** reach for the generic `call-agent` tool to ask Assets for an image.
57
65
  It talks to the same app, so it looks equivalent, but it skips the slide
58
66
  grounding, the completed-vs-failed task handling, and the ready-to-render
@@ -65,7 +73,8 @@ generation settings, and provenance. Include the active design system's
65
73
  image-style guidance in the prompt context, but do not ask Assets to invent a
66
74
  competing brand direction.
67
75
 
68
- Drop the returned `previewUrl` into the slide HTML's `<img src="...">`.
76
+ Use the returned `previewUrl` for previews. Do not drop it into slide HTML
77
+ without the verified insertion workflow above.
69
78
 
70
79
  ## Showing the result in chat
71
80
 
@@ -34,8 +34,13 @@ legacy image action directly:
34
34
  image syntax (`![Variation 1](url)`), not a plain link (`[Variation 1](url)`)
35
35
  — the chat renders `![]()` as an actual image but `[]()` as a bare link.
36
36
  4. Preserve the returned `assetId`, `runId`, `previewUrl`, and `downloadUrl`.
37
- 5. Insert the chosen image into the slide content through the normal action.
38
- 6. For feedback, refine the same asset rather than starting an unrelated run.
37
+ 5. For direct insertion, call `generate-image-api` with `insertIntoSlide: true`,
38
+ `deckId`, and `slideId`. Only say the image was added when it returns
39
+ `inserted: true`; a preview URL is not proof of a slide write.
40
+ 6. For preview-only variations, place the chosen URL with `update-slide`, then
41
+ re-read that slide with `get-deck` and confirm the persisted HTML contains
42
+ the image source before reporting success.
43
+ 7. For feedback, refine the same asset rather than starting an unrelated run.
39
44
 
40
45
  ### Context to pass
41
46
 
@@ -72,4 +77,6 @@ pnpm action search-images --q "Acme logo transparent" --count 5
72
77
  - Use `.fmd-img-placeholder` divs in slides before real images are generated
73
78
  - Use one canonical provider action per conceptual search; do not loop legacy
74
79
  provider scripts or manually guess provider URLs
75
- - After inserting an image, update the deck via the API
80
+ - Never claim image insertion from a generation response alone; require the
81
+ verified `inserted: true` response, or an `update-slide` plus `get-deck`
82
+ verification for preview-only placement
@@ -27,6 +27,10 @@ The build location depends on the host. Treat this as a hard routing rule:
27
27
  `resourceIds` when available. This is the Builder handoff.
28
28
  - Do not substitute the generic `create_workspace_app` MCP tool in an online
29
29
  host. That tool is a local workspace scaffolder, not the Builder handoff.
30
+ - Connect the Agent-Native Dispatch MCP connector only. Dispatch uses the
31
+ authenticated Builder Projects API to reuse or provision the workspace
32
+ project before starting the Builder Cloud Agent; a separate Builder CMS MCP
33
+ connection is not required for this workflow.
30
34
  - If `start-workspace-app-creation` is not available or Dispatch is not
31
35
  authenticated, stop with the connector setup needed. Do not fall back to a
32
36
  local build or claim that the app exists.
@@ -50,6 +50,14 @@ skills. For a ChatGPT Project or any host that exposes only the MCP connector,
50
50
  also place the skill instructions or this reference in the Project
51
51
  instructions/knowledge files. Then add and authenticate the Dispatch MCP
52
52
  connector from the packaged `adapters/chatgpt-mcp/connector.json`.
53
+ This single connector is enough: Dispatch provisions or reuses the Builder
54
+ project through the Builder Projects API, so do not ask the user to add a
55
+ separate Builder CMS MCP for app creation.
56
+ Builder's separate Fusion MCP is available as a custom remote connector at
57
+ `https://mcp.builder.io/mcp/fusion` for direct Builder work. It can run an
58
+ existing project, but it cannot provision the repo-backed Agent-Native
59
+ workspace project, so it is optional and not a replacement for the Dispatch
60
+ handoff in this skill. Do not add Builder's CMS MCP for this workflow.
53
61
 
54
62
  For Claude Web, ChatGPT Web, and their web Projects, the host is an
55
63
  orchestrator, not the build environment. After forming the source brief, call
@@ -8,16 +8,31 @@ import {
8
8
  extractAssetUrl,
9
9
  imagePreviewMarkdown,
10
10
  } from "../server/lib/assets-image-delegation.js";
11
+ import {
12
+ insertImageIntoSlideHtml,
13
+ slideHtmlContainsImageSource,
14
+ } from "../server/lib/slide-image-insertion.js";
11
15
  import {
12
16
  DEFAULT_STYLE_REFERENCE_URLS,
13
17
  normalizeReferenceUrls,
14
18
  } from "../shared/api.js";
19
+ import getDeckAction from "./get-deck.js";
20
+ import updateSlideAction from "./update-slide.js";
15
21
 
16
22
  interface ReferenceImage {
17
23
  data: string; // base64
18
24
  mimeType: string;
19
25
  }
20
26
 
27
+ interface DeckSlide {
28
+ id?: string;
29
+ content?: unknown;
30
+ }
31
+
32
+ interface DeckWithSlides {
33
+ slides?: DeckSlide[];
34
+ }
35
+
21
36
  async function urlToReferenceImage(
22
37
  url: string,
23
38
  ): Promise<ReferenceImage | null> {
@@ -33,6 +48,83 @@ async function urlToReferenceImage(
33
48
  }
34
49
  }
35
50
 
51
+ function parseGeneratedImageUrl(url: string | undefined): string {
52
+ if (!url) {
53
+ throw new Error(
54
+ "Image generation did not return a parseable image URL for insertion",
55
+ );
56
+ }
57
+ try {
58
+ const parsed = new URL(url);
59
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
60
+ throw new Error("unsupported protocol");
61
+ }
62
+ return parsed.toString();
63
+ } catch {
64
+ throw new Error(
65
+ "Image generation did not return a parseable image URL for insertion",
66
+ );
67
+ }
68
+ }
69
+
70
+ async function insertGeneratedImage({
71
+ deckId,
72
+ slideId,
73
+ prompt,
74
+ url,
75
+ }: {
76
+ deckId: string | undefined;
77
+ slideId: string | undefined;
78
+ prompt: string;
79
+ url: string | undefined;
80
+ }): Promise<{ inserted: true; url: string }> {
81
+ if (!deckId || !slideId) {
82
+ throw new Error(
83
+ "deckId and slideId are required when insertIntoSlide is true",
84
+ );
85
+ }
86
+
87
+ const imageUrl = parseGeneratedImageUrl(url);
88
+ const deck = (await getDeckAction.run({ id: deckId })) as DeckWithSlides;
89
+ const slide = deck.slides?.find((candidate) => candidate.id === slideId);
90
+ if (!slide || typeof slide.content !== "string") {
91
+ throw new Error(
92
+ `Slide ${slideId} was not found in deck ${deckId} for image insertion`,
93
+ );
94
+ }
95
+
96
+ const fullContent = insertImageIntoSlideHtml(slide.content, imageUrl, {
97
+ alt: prompt,
98
+ });
99
+ const update = await updateSlideAction.run({
100
+ deckId,
101
+ slideId,
102
+ fullContent,
103
+ preserveSource: true,
104
+ });
105
+ if (!update.ok || !("applied" in update) || !update.applied) {
106
+ throw new Error(`Image insertion was not applied to slide ${slideId}`);
107
+ }
108
+
109
+ const verifiedDeck = (await getDeckAction.run({
110
+ id: deckId,
111
+ })) as DeckWithSlides;
112
+ const verifiedSlide = verifiedDeck.slides?.find(
113
+ (candidate) => candidate.id === slideId,
114
+ );
115
+ if (
116
+ !verifiedSlide ||
117
+ typeof verifiedSlide.content !== "string" ||
118
+ !slideHtmlContainsImageSource(verifiedSlide.content, imageUrl)
119
+ ) {
120
+ throw new Error(
121
+ `Image insertion could not be verified on slide ${slideId}`,
122
+ );
123
+ }
124
+
125
+ return { inserted: true, url: imageUrl };
126
+ }
127
+
36
128
  export default defineAction({
37
129
  description:
38
130
  "Generate a slide image. Delegates to the Assets app over A2A so generations use the brand library, presets, and audit log; falls back to a local Gemini/OpenAI key only when Assets is unreachable. Show the returned `url` to the user as an inline markdown image (![alt](url)) so it renders in chat, never as a bare link.",
@@ -46,6 +138,13 @@ export default defineAction({
46
138
  ),
47
139
  deckId: z.string().optional().describe("Deck the image is destined for"),
48
140
  slideId: z.string().optional().describe("Slide the image is destined for"),
141
+ insertIntoSlide: z
142
+ .boolean()
143
+ .optional()
144
+ .default(false)
145
+ .describe(
146
+ "Insert the generated image into deckId/slideId and verify it persisted",
147
+ ),
49
148
  slideContent: z
50
149
  .string()
51
150
  .optional()
@@ -60,6 +159,11 @@ export default defineAction({
60
159
  if (!prompt?.trim()) {
61
160
  throw new Error("Prompt is required");
62
161
  }
162
+ if (args.insertIntoSlide && (!args.deckId || !args.slideId)) {
163
+ throw new Error(
164
+ "deckId and slideId are required when insertIntoSlide is true",
165
+ );
166
+ }
63
167
 
64
168
  const delegation = await delegateImageGenerationToAssets({
65
169
  prompt,
@@ -73,6 +177,14 @@ export default defineAction({
73
177
  const url = extractAssetUrl(delegation.reply, {
74
178
  baseUrl: delegation.target,
75
179
  });
180
+ const insertion = args.insertIntoSlide
181
+ ? await insertGeneratedImage({
182
+ deckId: args.deckId,
183
+ slideId: args.slideId,
184
+ prompt,
185
+ url: url ?? undefined,
186
+ })
187
+ : {};
76
188
  return {
77
189
  source: "assets-a2a" as const,
78
190
  prompt,
@@ -80,6 +192,7 @@ export default defineAction({
80
192
  // rather than guessing at URLs it did not return.
81
193
  reply: delegation.reply,
82
194
  ...(url ? { url, showToUser: imagePreviewMarkdown(prompt, url) } : {}),
195
+ ...insertion,
83
196
  };
84
197
  }
85
198
 
@@ -128,6 +241,15 @@ export default defineAction({
128
241
  );
129
242
  }
130
243
 
244
+ const insertion = args.insertIntoSlide
245
+ ? await insertGeneratedImage({
246
+ deckId: args.deckId,
247
+ slideId: args.slideId,
248
+ prompt,
249
+ url: uploaded.url,
250
+ })
251
+ : {};
252
+
131
253
  return {
132
254
  source: "slides-fallback" as const,
133
255
  fallbackReason: delegation.reason,
@@ -135,6 +257,7 @@ export default defineAction({
135
257
  url: uploaded.url,
136
258
  model: result.model,
137
259
  prompt,
260
+ ...insertion,
138
261
  };
139
262
  },
140
263
  });
@@ -59,6 +59,7 @@ export default function DeckCard({
59
59
  const [contextOpen, setContextOpen] = useState(false);
60
60
  const inputRef = useRef<HTMLInputElement>(null);
61
61
  const pendingRenameRef = useRef(false);
62
+ const pendingDeleteRef = useRef(false);
62
63
  const pendingWorkspaceDefaultRef = useRef(false);
63
64
 
64
65
  useEffect(() => {
@@ -227,6 +228,11 @@ export default function DeckCard({
227
228
  pendingWorkspaceDefaultRef.current = false;
228
229
  onSetWorkspaceDefault?.(deck.id, !isWorkspaceDefault);
229
230
  }
231
+ if (pendingDeleteRef.current) {
232
+ e.preventDefault();
233
+ pendingDeleteRef.current = false;
234
+ setTimeout(() => onDelete(deck.id), 0);
235
+ }
230
236
  }}
231
237
  >
232
238
  <DropdownMenuItem
@@ -268,8 +274,10 @@ export default function DeckCard({
268
274
  )}
269
275
  <DropdownMenuSeparator />
270
276
  <DropdownMenuItem
271
- onSelect={() => {
272
- onDelete(deck.id);
277
+ onSelect={(event) => {
278
+ event.preventDefault();
279
+ pendingDeleteRef.current = true;
280
+ setMenuOpen(false);
273
281
  }}
274
282
  className="text-red-400 focus:text-red-400"
275
283
  >
@@ -80,7 +80,7 @@ export function buildImageGenerationContext({
80
80
  }
81
81
 
82
82
  contextParts.push(
83
- '\nGenerate 3 variations. Show each as an inline rendered image preview using markdown image syntax (![Variation 1](url)), not a plain text link — the chat renders "![]()" as an actual image but "[]()" as a bare link. Let the user pick their favorite, then insert the chosen generated image into the slide content in the right place.',
83
+ '\nGenerate 3 preview-only variations. Show each as an inline rendered image preview using markdown image syntax (![Variation 1](url)), not a plain text link — the chat renders "![]()" as an actual image but "[]()" as a bare link. After the user chooses one, place that preview URL with update-slide and then get-deck to verify the persisted slide HTML contains it before claiming success. For a direct one-image request instead, call `generate-image-api` with insertIntoSlide: true plus deckId and slideId; claim it was added only if the action returns inserted: true.',
84
84
  );
85
85
 
86
86
  return contextParts.join("\n");
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-07
4
+ ---
5
+
6
+ Deleting a deck no longer freezes the Slides browser
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-07
4
+ ---
5
+
6
+ Slides verify generated images are persisted on the target slide