@agent-native/core 0.176.0-nightly-20260828040116 → 0.176.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 (116) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/CHANGELOG.md +15 -0
  3. package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +19 -18
  4. package/corpus/templates/analytics/docs/schemas/tracked-events.md +5 -1
  5. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +8 -6
  6. package/corpus/templates/assets/.agents/skills/assets-navigation/SKILL.md +9 -6
  7. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +22 -21
  8. package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +1 -1
  9. package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +11 -9
  10. package/corpus/templates/assets/AGENTS.md +16 -11
  11. package/corpus/templates/assets/CHANGELOG.md +6 -0
  12. package/corpus/templates/assets/actions/_helpers.ts +15 -0
  13. package/corpus/templates/assets/actions/_template-access.ts +71 -0
  14. package/corpus/templates/assets/actions/_template-input.ts +51 -0
  15. package/corpus/templates/assets/actions/associate-template.ts +39 -0
  16. package/corpus/templates/assets/actions/create-generation-preset.ts +3 -68
  17. package/corpus/templates/assets/actions/create-generation-session.ts +7 -10
  18. package/corpus/templates/assets/actions/create-library-from-preset.ts +2 -2
  19. package/corpus/templates/assets/actions/create-library.ts +2 -2
  20. package/corpus/templates/assets/actions/create-template.ts +86 -0
  21. package/corpus/templates/assets/actions/delete-generation-preset.ts +3 -29
  22. package/corpus/templates/assets/actions/delete-library.ts +11 -2
  23. package/corpus/templates/assets/actions/delete-template.ts +36 -0
  24. package/corpus/templates/assets/actions/duplicate-library.ts +9 -5
  25. package/corpus/templates/assets/actions/duplicate-template.ts +81 -0
  26. package/corpus/templates/assets/actions/generate-asset.ts +6 -3
  27. package/corpus/templates/assets/actions/generate-image-batch.ts +6 -5
  28. package/corpus/templates/assets/actions/generate-image.ts +23 -18
  29. package/corpus/templates/assets/actions/get-generation-session.ts +14 -5
  30. package/corpus/templates/assets/actions/get-template.ts +25 -0
  31. package/corpus/templates/assets/actions/list-generation-presets.ts +8 -26
  32. package/corpus/templates/assets/actions/list-templates.ts +90 -0
  33. package/corpus/templates/assets/actions/merge-duplicate-templates.ts +178 -0
  34. package/corpus/templates/assets/actions/navigate.ts +4 -1
  35. package/corpus/templates/assets/actions/rerun-generation-run.ts +3 -5
  36. package/corpus/templates/assets/actions/update-generation-preset.ts +4 -88
  37. package/corpus/templates/assets/actions/update-template.ts +87 -0
  38. package/corpus/templates/assets/actions/view-screen.ts +10 -0
  39. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +6 -1
  40. package/corpus/templates/assets/app/components/library/CreateTemplateDialog.tsx +214 -0
  41. package/corpus/templates/assets/app/components/library/TemplatesPanel.tsx +116 -0
  42. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +11 -2
  43. package/corpus/templates/assets/app/i18n-data.ts +393 -0
  44. package/corpus/templates/assets/app/lib/asset-urls.ts +34 -0
  45. package/corpus/templates/assets/app/lib/picker-chat-handoff.ts +9 -5
  46. package/corpus/templates/assets/app/lib/template-composer-reference.ts +62 -0
  47. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +2 -2
  48. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +7 -2431
  49. package/corpus/templates/assets/app/routes/brand-kits.$id_.settings.tsx +4 -7
  50. package/corpus/templates/assets/app/routes/library.tsx +15 -31
  51. package/corpus/templates/assets/app/routes/templates.$templateId.tsx +2492 -0
  52. package/corpus/templates/assets/app/routes/templates._index.tsx +360 -0
  53. package/corpus/templates/assets/app/routes/templates.tsx +11 -0
  54. package/corpus/templates/assets/changelog/2026-08-26-templates-across-brand-kits.md +5 -0
  55. package/corpus/templates/assets/server/db/index.ts +10 -0
  56. package/corpus/templates/assets/server/db/schema.ts +25 -0
  57. package/corpus/templates/assets/server/lib/generation-presets.ts +94 -32
  58. package/corpus/templates/assets/server/lib/{preset-chat-context.ts → template-chat-context.ts} +48 -34
  59. package/corpus/templates/assets/server/plugins/agent-chat.ts +72 -64
  60. package/corpus/templates/assets/server/plugins/creative-context.ts +0 -2
  61. package/corpus/templates/assets/server/plugins/db.ts +87 -0
  62. package/corpus/templates/assets/shared/api.ts +12 -0
  63. package/corpus/templates/chat/CHANGELOG.md +11 -0
  64. package/corpus/templates/clips/app/components/library/search-bar.tsx +7 -1
  65. package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
  66. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +262 -6
  67. package/corpus/templates/design/CHANGELOG.md +28 -0
  68. package/corpus/templates/design/actions/update-file.ts +4 -6
  69. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +5 -2
  70. package/corpus/templates/design/app/components/design/EditPanel.tsx +8 -0
  71. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +397 -12
  72. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +15 -0
  73. package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +14 -0
  74. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +352 -111
  75. package/corpus/templates/design/app/components/design/inspector/design-icons.tsx +1 -0
  76. package/corpus/templates/design/app/pages/DesignEditor.tsx +85 -5
  77. package/corpus/templates/design/app/pages/design-editor/commands/apply-layout-flow.ts +78 -0
  78. package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +110 -14
  79. package/corpus/templates/design/changelog/2026-08-27-grid-auto-layout-now-lays-children-out-in-cells-and-shows-the-cell-grid.md +6 -0
  80. package/corpus/templates/design/changelog/2026-08-27-outermost-frames-now-show-their-name-on-the-canvas.md +6 -0
  81. package/corpus/templates/design/server/source-workspace.ts +1 -7
  82. package/corpus/templates/design/shared/code-layer.ts +46 -10
  83. package/corpus/templates/design/shared/locked-layers.ts +178 -64
  84. package/corpus/templates/factory/AGENTS.md +2 -1
  85. package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +757 -42
  86. package/corpus/templates/factory/actions/ingest-github-observation.ts +10 -1
  87. package/corpus/templates/factory/actions/reconcile-triage-run.ts +3 -0
  88. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +10 -1
  89. package/corpus/templates/factory/server/triage/ai-services-git.ts +6 -0
  90. package/corpus/templates/factory/server/triage/contracts.ts +1 -0
  91. package/corpus/templates/factory/server/triage/github-client.ts +93 -15
  92. package/corpus/templates/factory/server/triage/pr-monitor.ts +3 -0
  93. package/corpus/templates/factory/server/triage/pr-policy.ts +243 -31
  94. package/corpus/templates/factory/server/triage/review-skill-alignment.ts +16 -4
  95. package/corpus/templates/slides/CHANGELOG.md +15 -0
  96. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +7 -2
  97. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +37 -1
  98. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +941 -219
  99. package/corpus/templates/slides/app/components/editor/SlidesLayersPanel.tsx +209 -0
  100. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +194 -4
  101. package/corpus/templates/slides/app/components/editor/slide-style.ts +36 -0
  102. package/corpus/templates/slides/app/context/DeckContext.tsx +2 -0
  103. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  104. package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +26 -8
  105. package/corpus/templates/slides/app/lib/slide-image-replacement.ts +83 -2
  106. package/corpus/templates/slides/app/pages/DeckEditor.tsx +213 -51
  107. package/corpus/templates/slides/app/root.tsx +25 -6
  108. package/dist/observability/routes.d.ts +3 -3
  109. package/dist/observability/traces.d.ts +8 -0
  110. package/dist/observability/traces.js +27 -0
  111. package/dist/progress/routes.d.ts +1 -1
  112. package/dist/secrets/routes.d.ts +9 -9
  113. package/dist/templates/chat/CHANGELOG.md +11 -0
  114. package/package.json +3 -3
  115. package/src/templates/chat/CHANGELOG.md +11 -0
  116. package/corpus/templates/assets/app/components/library/GenerationPresetsPanel.tsx +0 -326
@@ -1,11 +1,6 @@
1
1
  import { defineAction } from "@agent-native/core/action";
2
- import { assertAccess } from "@agent-native/core/sharing";
3
- import { eq } from "drizzle-orm";
4
- import { nanoid } from "nanoid";
5
2
  import { z } from "zod";
6
3
 
7
- import { getDb, schema } from "../server/db/index.js";
8
- import { nowIso, stringifyJson } from "../server/lib/json.js";
9
4
  import {
10
5
  ASPECT_RATIOS,
11
6
  GENERATION_PRESET_REFERENCE_POLICIES,
@@ -14,16 +9,11 @@ import {
14
9
  IMAGE_SIZES,
15
10
  } from "../shared/api.js";
16
11
  import { generationPresetSettingsSchema } from "./_generation-preset-settings.js";
17
- import { serializeGenerationPreset } from "./_helpers.js";
18
- import {
19
- assertPresetReferenceAssetsValid,
20
- assertPresetReferenceModelCompatible,
21
- assertPresetSkeletonAssetsValid,
22
- } from "./_preset-skeleton-validation.js";
12
+ import createTemplate from "./create-template.js";
23
13
 
24
14
  export default defineAction({
25
15
  description:
26
- "Create a reusable deliverable preset for an asset library. Use presets for social images, blog heroes, diagrams, and other repeatable output formats.",
16
+ "Deprecated — use create-template. Create a reusable deliverable preset for an asset library.",
27
17
  schema: z.object({
28
18
  libraryId: z.string(),
29
19
  collectionId: z.string().nullable().optional(),
@@ -47,60 +37,5 @@ export default defineAction({
47
37
  settings: generationPresetSettingsSchema.optional(),
48
38
  sortOrder: z.coerce.number().optional(),
49
39
  }),
50
- run: async (args) => {
51
- const db = getDb();
52
- await assertAccess("asset-library", args.libraryId, "editor");
53
- if (args.collectionId) {
54
- const [collection] = await db
55
- .select()
56
- .from(schema.assetCollections)
57
- .where(eq(schema.assetCollections.id, args.collectionId))
58
- .limit(1);
59
- if (!collection || collection.libraryId !== args.libraryId) {
60
- throw new Error("Collection does not belong to this asset library.");
61
- }
62
- }
63
- const settings = {
64
- ...(args.settings ?? {}),
65
- ...(args.includeLogo !== undefined
66
- ? { includeLogo: args.includeLogo }
67
- : {}),
68
- };
69
- await assertPresetSkeletonAssetsValid({
70
- db,
71
- libraryId: args.libraryId,
72
- settings,
73
- });
74
- await assertPresetReferenceAssetsValid({
75
- db,
76
- libraryId: args.libraryId,
77
- settings,
78
- });
79
- assertPresetReferenceModelCompatible({
80
- model: args.model,
81
- settings,
82
- });
83
- const now = nowIso();
84
- const row = {
85
- id: nanoid(),
86
- libraryId: args.libraryId,
87
- collectionId: args.collectionId ?? null,
88
- title: args.title,
89
- description: args.description ?? null,
90
- category: args.category,
91
- mediaType: "image",
92
- promptTemplate: args.promptTemplate ?? null,
93
- aspectRatio: args.aspectRatio,
94
- imageSize: args.imageSize,
95
- model: args.model,
96
- textPolicy: args.textPolicy,
97
- referencePolicy: args.referencePolicy,
98
- settings: stringifyJson(settings),
99
- sortOrder: args.sortOrder ?? 100,
100
- createdAt: now,
101
- updatedAt: now,
102
- };
103
- await db.insert(schema.assetGenerationPresets).values(row);
104
- return serializeGenerationPreset(row);
105
- },
40
+ run: async (args) => createTemplate.run(args),
106
41
  });
@@ -13,6 +13,7 @@ import { z } from "zod";
13
13
  import { getDb, schema } from "../server/db/index.js";
14
14
  import { nowIso, stringifyJson } from "../server/lib/json.js";
15
15
  import { serializeGenerationSession } from "./_helpers.js";
16
+ import { resolveTemplateAccess } from "./_template-access.js";
16
17
 
17
18
  export default defineAction({
18
19
  description:
@@ -60,23 +61,19 @@ export default defineAction({
60
61
  throw new Error("Collection does not belong to this asset library.");
61
62
  }
62
63
  }
63
- let preset: typeof schema.assetGenerationPresets.$inferSelect | null = null;
64
+ let preset: typeof schema.assetTemplates.$inferSelect | null = null;
64
65
  if (args.presetId) {
65
- const [row] = await db
66
- .select()
67
- .from(schema.assetGenerationPresets)
68
- .where(eq(schema.assetGenerationPresets.id, args.presetId))
69
- .limit(1);
70
- preset = row ?? null;
71
- if (!preset || preset.libraryId !== args.libraryId) {
72
- throw new Error("Generation preset does not belong to this library.");
66
+ preset = (await resolveTemplateAccess(args.presetId, "viewer")).resource;
67
+ if (!preset) throw new Error("Template not found.");
68
+ if (preset.libraryId && preset.libraryId !== args.libraryId) {
69
+ throw new Error("Template is associated to a different brand kit.");
73
70
  }
74
71
  if (
75
72
  args.collectionId &&
76
73
  preset.collectionId &&
77
74
  preset.collectionId !== args.collectionId
78
75
  ) {
79
- throw new Error("Generation preset belongs to a different collection.");
76
+ throw new Error("Template is associated to a different collection.");
80
77
  }
81
78
  }
82
79
  const assetIds = [...new Set(args.assetIds ?? [])];
@@ -13,7 +13,7 @@ import { z } from "zod";
13
13
 
14
14
  import { getDb, schema } from "../server/db/index.js";
15
15
  import { createAssetFromBuffer } from "../server/lib/assets.js";
16
- import { seedDefaultGenerationPresets } from "../server/lib/generation-presets.js";
16
+ import { ensureDefaultTemplates } from "../server/lib/generation-presets.js";
17
17
  import { nowIso, stringifyJson } from "../server/lib/json.js";
18
18
  import {
19
19
  DEFAULT_LIBRARY_PRESET_VERSION,
@@ -141,7 +141,7 @@ export default defineAction({
141
141
 
142
142
  const db = getDb();
143
143
  await db.insert(schema.assetLibraries).values(row);
144
- await seedDefaultGenerationPresets({ db, libraryId: row.id, now });
144
+ await ensureDefaultTemplates({ db, ownerEmail, orgId: row.orgId, now });
145
145
 
146
146
  const referenceAssets = [];
147
147
  const referenceSeedErrors = [];
@@ -7,7 +7,7 @@ import { nanoid } from "nanoid";
7
7
  import { z } from "zod";
8
8
 
9
9
  import { getDb, schema } from "../server/db/index.js";
10
- import { seedDefaultGenerationPresets } from "../server/lib/generation-presets.js";
10
+ import { ensureDefaultTemplates } from "../server/lib/generation-presets.js";
11
11
  import { nowIso, stringifyJson } from "../server/lib/json.js";
12
12
  import { serializeLibrary } from "./_helpers.js";
13
13
 
@@ -57,7 +57,7 @@ export default defineAction({
57
57
  };
58
58
  const db = getDb();
59
59
  await db.insert(schema.assetLibraries).values(row);
60
- await seedDefaultGenerationPresets({ db, libraryId: row.id, now });
60
+ await ensureDefaultTemplates({ db, ownerEmail, orgId: row.orgId, now });
61
61
  return serializeLibrary(row);
62
62
  },
63
63
  });
@@ -0,0 +1,86 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import {
3
+ getRequestOrgId,
4
+ getRequestUserEmail,
5
+ } from "@agent-native/core/server/request-context";
6
+ import { eq } from "drizzle-orm";
7
+ import { nanoid } from "nanoid";
8
+ import { z } from "zod";
9
+
10
+ import { getDb, schema } from "../server/db/index.js";
11
+ import { nowIso, stringifyJson } from "../server/lib/json.js";
12
+ import { serializeTemplate } from "./_helpers.js";
13
+ import {
14
+ assertPresetReferenceAssetsValid,
15
+ assertPresetReferenceModelCompatible,
16
+ assertPresetSkeletonAssetsValid,
17
+ } from "./_preset-skeleton-validation.js";
18
+ import { assertTemplateTargetLibraryAccess } from "./_template-access.js";
19
+ import { templateFieldsSchema, templateHasPins } from "./_template-input.js";
20
+
21
+ export default defineAction({
22
+ description:
23
+ "Create a reusable asset template. A template may be global or associated with one Brand Kit.",
24
+ schema: templateFieldsSchema.extend({ title: z.string().min(1) }),
25
+ run: async (args) => {
26
+ const ownerEmail = getRequestUserEmail();
27
+ if (!ownerEmail) throw new Error("Not authenticated");
28
+ const libraryId = args.libraryId ?? null;
29
+ await assertTemplateTargetLibraryAccess(libraryId);
30
+ const db = getDb();
31
+ if (args.collectionId) {
32
+ if (!libraryId)
33
+ throw new Error("A collection requires an associated Brand Kit.");
34
+ const [collection] = await db
35
+ .select()
36
+ .from(schema.assetCollections)
37
+ .where(eq(schema.assetCollections.id, args.collectionId))
38
+ .limit(1);
39
+ if (!collection || collection.libraryId !== libraryId)
40
+ throw new Error("Collection does not belong to this asset library.");
41
+ }
42
+ const settings = {
43
+ ...(args.settings ?? {}),
44
+ ...(args.includeLogo === undefined
45
+ ? {}
46
+ : { includeLogo: args.includeLogo }),
47
+ };
48
+ if (!libraryId && templateHasPins(settings).length)
49
+ throw new Error(
50
+ "Global templates cannot pin images, use a skeleton, or composite a canonical logo.",
51
+ );
52
+ if (libraryId) {
53
+ await assertPresetSkeletonAssetsValid({ db, libraryId, settings });
54
+ await assertPresetReferenceAssetsValid({ db, libraryId, settings });
55
+ }
56
+ assertPresetReferenceModelCompatible({
57
+ model: args.model ?? "gemini-3.1-flash-image",
58
+ settings,
59
+ });
60
+ const now = nowIso();
61
+ const row = {
62
+ id: nanoid(),
63
+ libraryId,
64
+ collectionId: args.collectionId ?? null,
65
+ title: args.title,
66
+ description: args.description ?? null,
67
+ category: args.category ?? "style-only",
68
+ mediaType: "image",
69
+ promptTemplate: args.promptTemplate ?? null,
70
+ aspectRatio: args.aspectRatio ?? "16:9",
71
+ imageSize: args.imageSize ?? "2K",
72
+ model: args.model ?? "gemini-3.1-flash-image",
73
+ textPolicy: args.textPolicy ?? "",
74
+ referencePolicy: args.referencePolicy ?? "auto",
75
+ settings: stringifyJson(settings),
76
+ sortOrder: args.sortOrder ?? 100,
77
+ ownerEmail,
78
+ orgId: getRequestOrgId(),
79
+ visibility: "private" as const,
80
+ createdAt: now,
81
+ updatedAt: now,
82
+ };
83
+ await db.insert(schema.assetTemplates).values(row);
84
+ return serializeTemplate(row);
85
+ },
86
+ });
@@ -1,36 +1,10 @@
1
1
  import { defineAction } from "@agent-native/core/action";
2
- import { assertAccess } from "@agent-native/core/sharing";
3
- import { eq } from "drizzle-orm";
4
2
  import { z } from "zod";
5
3
 
6
- import { getDb, schema } from "../server/db/index.js";
4
+ import deleteTemplate from "./delete-template.js";
7
5
 
8
6
  export default defineAction({
9
- description:
10
- "Delete a generation preset from an asset library. Existing generation runs keep their captured settings.",
7
+ description: "Deprecated — use delete-template. Delete a generation preset.",
11
8
  schema: z.object({ id: z.string() }),
12
- run: async ({ id }) => {
13
- const db = getDb();
14
- const [preset] = await db
15
- .select()
16
- .from(schema.assetGenerationPresets)
17
- .where(eq(schema.assetGenerationPresets.id, id))
18
- .limit(1);
19
- if (!preset) throw new Error("Generation preset not found.");
20
- await assertAccess("asset-library", preset.libraryId, "editor");
21
- const [referencingSession] = await db
22
- .select({ id: schema.assetGenerationSessions.id })
23
- .from(schema.assetGenerationSessions)
24
- .where(eq(schema.assetGenerationSessions.presetId, id))
25
- .limit(1);
26
- if (referencingSession) {
27
- throw new Error(
28
- "Generation preset is used by an existing handoff session and cannot be deleted.",
29
- );
30
- }
31
- await db
32
- .delete(schema.assetGenerationPresets)
33
- .where(eq(schema.assetGenerationPresets.id, id));
34
- return { id, deleted: true };
35
- },
9
+ run: async (args) => deleteTemplate.run(args),
36
10
  });
@@ -16,6 +16,10 @@ export default defineAction({
16
16
  .select({ id: schema.assetGenerationSessions.id })
17
17
  .from(schema.assetGenerationSessions)
18
18
  .where(eq(schema.assetGenerationSessions.libraryId, id));
19
+ const templates = await db
20
+ .select({ id: schema.assetTemplates.id })
21
+ .from(schema.assetTemplates)
22
+ .where(eq(schema.assetTemplates.libraryId, id));
19
23
  for (const session of sessions) {
20
24
  await db
21
25
  .delete(schema.assetGenerationSessionItems)
@@ -24,9 +28,14 @@ export default defineAction({
24
28
  await db
25
29
  .delete(schema.assetGenerationSessions)
26
30
  .where(eq(schema.assetGenerationSessions.libraryId, id));
31
+ for (const template of templates) {
32
+ await db
33
+ .delete(schema.assetTemplateShares)
34
+ .where(eq(schema.assetTemplateShares.resourceId, template.id));
35
+ }
27
36
  await db
28
- .delete(schema.assetGenerationPresets)
29
- .where(eq(schema.assetGenerationPresets.libraryId, id));
37
+ .delete(schema.assetTemplates)
38
+ .where(eq(schema.assetTemplates.libraryId, id));
30
39
  await db.delete(schema.assets).where(eq(schema.assets.libraryId, id));
31
40
  await db
32
41
  .delete(schema.assetGenerationRuns)
@@ -0,0 +1,36 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import { eq } from "drizzle-orm";
3
+ import { z } from "zod";
4
+
5
+ import { getDb, schema } from "../server/db/index.js";
6
+ import { resolveTemplateAccess } from "./_template-access.js";
7
+
8
+ export default defineAction({
9
+ description:
10
+ "Delete an asset template when no handoff session references it.",
11
+ schema: z.object({ id: z.string() }),
12
+ run: async ({ id }) => {
13
+ await resolveTemplateAccess(id, "editor");
14
+ const db = getDb();
15
+ const [[session], [run]] = await Promise.all([
16
+ db
17
+ .select({ id: schema.assetGenerationSessions.id })
18
+ .from(schema.assetGenerationSessions)
19
+ .where(eq(schema.assetGenerationSessions.presetId, id))
20
+ .limit(1),
21
+ db
22
+ .select({ id: schema.assetGenerationRuns.id })
23
+ .from(schema.assetGenerationRuns)
24
+ .where(eq(schema.assetGenerationRuns.presetId, id))
25
+ .limit(1),
26
+ ]);
27
+ if (session || run) throw new Error("template-in-use");
28
+ await db
29
+ .delete(schema.assetTemplateShares)
30
+ .where(eq(schema.assetTemplateShares.resourceId, id));
31
+ await db
32
+ .delete(schema.assetTemplates)
33
+ .where(eq(schema.assetTemplates.id, id));
34
+ return { id, deleted: true };
35
+ },
36
+ });
@@ -15,7 +15,7 @@ import { normalizePresetReferences } from "../server/lib/preset-references.js";
15
15
  import { normalizePresetSkeletonSpec } from "../server/lib/preset-skeleton.js";
16
16
  import {
17
17
  serializeAsset,
18
- serializeGenerationPreset,
18
+ serializeTemplate,
19
19
  serializeLibrary,
20
20
  } from "./_helpers.js";
21
21
 
@@ -117,6 +117,7 @@ export default defineAction({
117
117
 
118
118
  const ownerEmail = getRequestUserEmail();
119
119
  if (!ownerEmail) throw new Error("no authenticated user");
120
+ const orgId = getRequestOrgId();
120
121
 
121
122
  const db = getDb();
122
123
  const [source] = await db
@@ -141,8 +142,8 @@ export default defineAction({
141
142
  .where(eq(schema.assetFolders.libraryId, id)),
142
143
  db
143
144
  .select()
144
- .from(schema.assetGenerationPresets)
145
- .where(eq(schema.assetGenerationPresets.libraryId, id)),
145
+ .from(schema.assetTemplates)
146
+ .where(eq(schema.assetTemplates.libraryId, id)),
146
147
  db
147
148
  .select()
148
149
  .from(schema.assets)
@@ -231,6 +232,9 @@ export default defineAction({
231
232
  referencePolicy: preset.referencePolicy,
232
233
  settings: remapJsonText(preset.settings, allIds),
233
234
  sortOrder: preset.sortOrder,
235
+ ownerEmail,
236
+ orgId,
237
+ visibility: "private" as const,
234
238
  createdAt: now,
235
239
  updatedAt: now,
236
240
  }));
@@ -296,7 +300,7 @@ export default defineAction({
296
300
  await tx.insert(schema.assetFolders).values(copiedFolders);
297
301
  }
298
302
  if (copiedPresets.length) {
299
- await tx.insert(schema.assetGenerationPresets).values(copiedPresets);
303
+ await tx.insert(schema.assetTemplates).values(copiedPresets);
300
304
  }
301
305
  if (copiedAssets.length) {
302
306
  await tx.insert(schema.assets).values(copiedAssets);
@@ -314,7 +318,7 @@ export default defineAction({
314
318
  },
315
319
  collections: copiedCollections,
316
320
  folders: copiedFolders,
317
- generationPresets: copiedPresets.map(serializeGenerationPreset),
321
+ generationPresets: copiedPresets.map(serializeTemplate),
318
322
  assets: copiedAssets.map(serializeAsset),
319
323
  };
320
324
  },
@@ -0,0 +1,81 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import {
3
+ getRequestOrgId,
4
+ getRequestUserEmail,
5
+ } from "@agent-native/core/server/request-context";
6
+ import { nanoid } from "nanoid";
7
+ import { z } from "zod";
8
+
9
+ import { getDb, schema } from "../server/db/index.js";
10
+ import { nowIso, parseJson, stringifyJson } from "../server/lib/json.js";
11
+ import { serializeTemplate } from "./_helpers.js";
12
+ import {
13
+ assertTemplateTargetLibraryAccess,
14
+ resolveTemplateAccess,
15
+ } from "./_template-access.js";
16
+ import { templateHasPins } from "./_template-input.js";
17
+
18
+ export default defineAction({
19
+ description: "Copy an asset template into global scope or a Brand Kit.",
20
+ schema: z.object({
21
+ id: z.string(),
22
+ libraryId: z.string().nullable().optional(),
23
+ title: z.string().min(1).optional(),
24
+ dropPins: z.boolean().optional(),
25
+ }),
26
+ run: async ({ id, libraryId, title, dropPins }) => {
27
+ const source = (await resolveTemplateAccess(id, "viewer")).resource;
28
+ const ownerEmail = getRequestUserEmail();
29
+ if (!ownerEmail) throw new Error("Not authenticated");
30
+ const targetLibraryId =
31
+ libraryId === undefined ? source.libraryId : libraryId;
32
+ await assertTemplateTargetLibraryAccess(targetLibraryId);
33
+ const settings = parseJson<Record<string, unknown>>(source.settings, {});
34
+ const pins = templateHasPins(settings);
35
+ if (!targetLibraryId && pins.length && !dropPins)
36
+ throw new Error(
37
+ `Copying this template globally requires dropPins: true (${pins.join(", ")}).`,
38
+ );
39
+ const copiedSettings =
40
+ !targetLibraryId && pins.length
41
+ ? {
42
+ ...settings,
43
+ includeLogo: false,
44
+ skeletonSpec: null,
45
+ presetReferences: Array.isArray(settings.presetReferences)
46
+ ? settings.presetReferences.map((entry: any) => ({
47
+ ...entry,
48
+ assetIds: [],
49
+ }))
50
+ : settings.presetReferences,
51
+ }
52
+ : settings;
53
+ if (
54
+ targetLibraryId &&
55
+ source.libraryId &&
56
+ source.libraryId !== targetLibraryId &&
57
+ pins.length
58
+ ) {
59
+ throw new Error(
60
+ "Copying pinned templates into another Brand Kit is not supported until its pinned assets are copied.",
61
+ );
62
+ }
63
+ const now = nowIso();
64
+ const row = {
65
+ ...source,
66
+ id: nanoid(),
67
+ libraryId: targetLibraryId ?? null,
68
+ collectionId:
69
+ targetLibraryId === source.libraryId ? source.collectionId : null,
70
+ title: title ?? `${source.title} (copy)`,
71
+ settings: stringifyJson(copiedSettings),
72
+ ownerEmail,
73
+ orgId: getRequestOrgId(),
74
+ visibility: "private" as const,
75
+ createdAt: now,
76
+ updatedAt: now,
77
+ };
78
+ await getDb().insert(schema.assetTemplates).values(row);
79
+ return serializeTemplate(row);
80
+ },
81
+ });
@@ -41,10 +41,13 @@ const schema = z.object({
41
41
  "Brand, campaign, or use-case hint used when matching a library.",
42
42
  ),
43
43
  aspectRatio: z.enum(ASPECT_RATIOS).default("16:9"),
44
- presetId: z
44
+ templateId: z
45
45
  .string()
46
46
  .optional()
47
- .describe("Optional generation preset ID from the selected library."),
47
+ .describe(
48
+ "Optional template ID from the selected Brand Kit or global templates.",
49
+ ),
50
+ presetId: z.string().optional().describe("Deprecated — use templateId."),
48
51
  count: z.coerce
49
52
  .number()
50
53
  .int()
@@ -199,7 +202,7 @@ const action = defineAction({
199
202
  libraryId: args.libraryId,
200
203
  libraryHint: args.libraryHint,
201
204
  aspectRatio: args.aspectRatio,
202
- presetId: args.presetId,
205
+ presetId: args.templateId ?? args.presetId,
203
206
  count: args.count,
204
207
  autoGenerate: true,
205
208
  tier: args.tier,
@@ -40,6 +40,7 @@ const imageBatchAgentInputSchema = z.object({
40
40
  .describe("Brand kit/library ID to use for every generated image."),
41
41
  collectionId: z.string().optional(),
42
42
  presetId: z.string().optional(),
43
+ templateId: z.string().optional(),
43
44
  presetReferenceFills: z.array(presetReferenceFillSchema).max(6).optional(),
44
45
  sessionId: z.string().optional(),
45
46
  slots: z
@@ -78,12 +79,11 @@ export default defineAction({
78
79
  "Brand kit/library ID. Pass the refId from a brand-kit @mention, or choose a kit from view-screen/list-libraries.",
79
80
  ),
80
81
  collectionId: z.string().optional(),
81
- presetId: z
82
+ templateId: z
82
83
  .string()
83
84
  .optional()
84
- .describe(
85
- "Generation preset ID (from a @preset mention or list-generation-presets). The preset already defines aspectRatio, imageSize, model, tier, and category. When you set presetId, OMIT each slot's aspectRatio/imageSize and the top-level model/tier so the preset's values are used; only pass one when the user explicitly asks for a value that differs from the preset.",
86
- ),
85
+ .describe("Template ID from a @template mention or list-templates."),
86
+ presetId: z.string().optional().describe("Deprecated — use templateId."),
87
87
  presetReferenceFills: z
88
88
  .array(presetReferenceFillSchema)
89
89
  .max(6)
@@ -231,7 +231,7 @@ export default defineAction({
231
231
  batchId: variantBatchId,
232
232
  libraryId: base.libraryId,
233
233
  collectionId: base.collectionId ?? null,
234
- presetId: base.presetId ?? null,
234
+ presetId: base.templateId ?? base.presetId ?? null,
235
235
  sessionId: base.sessionId ?? null,
236
236
  threadId: context?.threadId ?? null,
237
237
  variantScopeId: base.variantScopeId ?? null,
@@ -249,6 +249,7 @@ export default defineAction({
249
249
  {
250
250
  libraryId: base.libraryId,
251
251
  collectionId: base.collectionId,
252
+ templateId: base.templateId,
252
253
  presetId: base.presetId,
253
254
  presetReferenceFills: base.presetReferenceFills,
254
255
  sessionId: base.sessionId,
@@ -74,6 +74,7 @@ import {
74
74
  serializeAsset,
75
75
  } from "./_helpers.js";
76
76
  import { readImageModelDefault } from "./_image-model-default.js";
77
+ import { resolveTemplateAccess } from "./_template-access.js";
77
78
  import { withToolActivity } from "./_tool-activity.js";
78
79
  import { upsertVariantSlot, wasVariantSlotDismissed } from "./variant-slots.js";
79
80
 
@@ -84,6 +85,15 @@ const presetReferenceFillSchema = z.object({
84
85
  assetIds: z.array(z.string().min(1)).min(1).max(4),
85
86
  });
86
87
 
88
+ export function assertTemplateMatchesLibrary(
89
+ template: { libraryId: string | null } | null,
90
+ libraryId: string,
91
+ ) {
92
+ if (template?.libraryId && template.libraryId !== libraryId) {
93
+ throw new Error("Template is associated to a different brand kit.");
94
+ }
95
+ }
96
+
87
97
  const imageGenerationAgentInputSchema = z.object({
88
98
  libraryId: z
89
99
  .string()
@@ -91,6 +101,7 @@ const imageGenerationAgentInputSchema = z.object({
91
101
  .describe("Brand kit/library ID to use for this image."),
92
102
  collectionId: z.string().optional(),
93
103
  presetId: z.string().optional(),
104
+ templateId: z.string().optional(),
94
105
  sessionId: z.string().optional(),
95
106
  prompt: z.string().min(1),
96
107
  embeddedText: z.string().optional(),
@@ -121,12 +132,11 @@ export default defineAction({
121
132
  "Brand kit/library ID. Pass the refId from a brand-kit @mention, or choose a kit from view-screen/list-libraries.",
122
133
  ),
123
134
  collectionId: z.string().optional(),
124
- presetId: z
135
+ templateId: z
125
136
  .string()
126
137
  .optional()
127
- .describe(
128
- "Generation preset ID (from a @preset mention or list-generation-presets). A preset already defines aspectRatio, imageSize, model, tier, and category. When you set presetId, OMIT those args so the preset's values are used; only pass one of them when the user explicitly asks for a value that differs from the preset.",
129
- ),
138
+ .describe("Template ID from a @template mention or list-templates."),
139
+ presetId: z.string().optional().describe("Deprecated — use templateId."),
130
140
  sessionId: z.string().optional(),
131
141
  prompt: z.string().min(1),
132
142
  embeddedText: z
@@ -419,8 +429,8 @@ export default defineAction({
419
429
  ];
420
430
  if (
421
431
  session?.presetId &&
422
- args.presetId &&
423
- args.presetId !== session.presetId
432
+ (args.templateId ?? args.presetId) &&
433
+ (args.templateId ?? args.presetId) !== session.presetId
424
434
  ) {
425
435
  throw new Error("Generation preset does not match this session.");
426
436
  }
@@ -431,20 +441,15 @@ export default defineAction({
431
441
  ) {
432
442
  throw new Error("Collection does not match this session.");
433
443
  }
434
- const resolvedPresetId = session?.presetId ?? args.presetId ?? undefined;
435
- const [preset] = resolvedPresetId
436
- ? await db
437
- .select()
438
- .from(schema.assetGenerationPresets)
439
- .where(eq(schema.assetGenerationPresets.id, resolvedPresetId))
440
- .limit(1)
441
- : [null];
444
+ const resolvedPresetId =
445
+ session?.presetId ?? args.templateId ?? args.presetId ?? undefined;
446
+ const preset = resolvedPresetId
447
+ ? (await resolveTemplateAccess(resolvedPresetId, "viewer")).resource
448
+ : null;
442
449
  if (resolvedPresetId && !preset) {
443
- throw new Error("Generation preset not found.");
444
- }
445
- if (preset && preset.libraryId !== args.libraryId) {
446
- throw new Error("Generation preset does not belong to this library.");
450
+ throw new Error("Template not found.");
447
451
  }
452
+ assertTemplateMatchesLibrary(preset, args.libraryId);
448
453
  if (
449
454
  session?.collectionId &&
450
455
  preset?.collectionId &&