@agent-native/core 0.80.9 → 0.80.11

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 (148) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +13 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +108 -15
  5. package/corpus/core/src/cli/design-connect.ts +28 -2
  6. package/corpus/core/src/cli/skills.ts +45 -24
  7. package/corpus/core/src/client/AssistantChat.tsx +123 -26
  8. package/corpus/core/src/client/sse-event-processor.ts +9 -0
  9. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +9 -2
  10. package/corpus/templates/assets/AGENTS.md +4 -0
  11. package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
  12. package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
  13. package/corpus/templates/assets/actions/generate-image.ts +118 -67
  14. package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
  15. package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
  16. package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -0
  17. package/corpus/templates/assets/app/i18n/zh-TW.ts +7 -0
  18. package/corpus/templates/assets/app/i18n-data.ts +61 -0
  19. package/corpus/templates/assets/app/routes/_index.tsx +4 -0
  20. package/corpus/templates/assets/app/routes/library.tsx +145 -38
  21. package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
  22. package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
  23. package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
  24. package/corpus/templates/assets/server/lib/generation.ts +135 -11
  25. package/corpus/templates/assets/shared/api.ts +4 -0
  26. package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
  27. package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
  28. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +717 -0
  29. package/corpus/templates/design/actions/add-breakpoint.ts +143 -0
  30. package/corpus/templates/design/actions/add-localhost-screens.ts +5 -0
  31. package/corpus/templates/design/actions/apply-a11y-fix.ts +317 -0
  32. package/corpus/templates/design/actions/apply-component-prop-edit.ts +441 -0
  33. package/corpus/templates/design/actions/apply-design-state.ts +213 -0
  34. package/corpus/templates/design/actions/apply-design-token-edit.ts +202 -0
  35. package/corpus/templates/design/actions/apply-motion-edit.ts +413 -0
  36. package/corpus/templates/design/actions/apply-shader-fill.ts +404 -0
  37. package/corpus/templates/design/actions/apply-visual-edit.ts +191 -5
  38. package/corpus/templates/design/actions/capture-design-state.ts +224 -0
  39. package/corpus/templates/design/actions/connect-builder-app.ts +162 -0
  40. package/corpus/templates/design/actions/create-component.ts +447 -0
  41. package/corpus/templates/design/actions/create-design-branch.ts +263 -0
  42. package/corpus/templates/design/actions/create-design-state.ts +162 -0
  43. package/corpus/templates/design/actions/delete-design-state.ts +55 -0
  44. package/corpus/templates/design/actions/delete-design.ts +20 -0
  45. package/corpus/templates/design/actions/deploy-design-preview.ts +275 -0
  46. package/corpus/templates/design/actions/get-component-details.ts +251 -0
  47. package/corpus/templates/design/actions/get-design-branch-diff.ts +362 -0
  48. package/corpus/templates/design/actions/get-design-review.ts +314 -0
  49. package/corpus/templates/design/actions/get-design-surface-index.ts +582 -0
  50. package/corpus/templates/design/actions/get-motion-timeline.ts +99 -0
  51. package/corpus/templates/design/actions/index-components.ts +258 -0
  52. package/corpus/templates/design/actions/index-design-tokens.ts +277 -0
  53. package/corpus/templates/design/actions/list-design-extensions.ts +309 -0
  54. package/corpus/templates/design/actions/list-design-source-capabilities.ts +153 -0
  55. package/corpus/templates/design/actions/list-design-states.ts +90 -0
  56. package/corpus/templates/design/actions/migrate-inline-design-to-app.ts +298 -0
  57. package/corpus/templates/design/actions/open-component-source.ts +242 -0
  58. package/corpus/templates/design/actions/preview-component-prop-edit.ts +260 -0
  59. package/corpus/templates/design/actions/preview-design-token-edit.ts +113 -0
  60. package/corpus/templates/design/actions/preview-shader-fill.ts +187 -0
  61. package/corpus/templates/design/actions/remove-breakpoint.ts +103 -0
  62. package/corpus/templates/design/actions/remove-motion-timeline.ts +196 -0
  63. package/corpus/templates/design/actions/run-design-audit.ts +436 -0
  64. package/corpus/templates/design/actions/run-design-extension-action.ts +284 -0
  65. package/corpus/templates/design/actions/set-active-breakpoint.ts +39 -0
  66. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1550 -82
  67. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +649 -43
  68. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +1 -1
  69. package/corpus/templates/design/app/components/design/EditPanel.tsx +1788 -52
  70. package/corpus/templates/design/app/components/design/LayersPanel.tsx +114 -33
  71. package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +157 -0
  72. package/corpus/templates/design/app/components/design/MotionDock.tsx +1071 -0
  73. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1053 -99
  74. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +766 -0
  75. package/corpus/templates/design/app/components/design/StatesPanel.tsx +581 -0
  76. package/corpus/templates/design/app/components/design/TokensPanel.tsx +578 -0
  77. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +241 -0
  78. package/corpus/templates/design/app/components/design/index.ts +16 -0
  79. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +15 -6
  80. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +66 -3
  81. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +145 -0
  82. package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +42 -12
  83. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
  84. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +131 -0
  85. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +3 -1
  86. package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
  87. package/corpus/templates/design/app/i18n-data.ts +280 -0
  88. package/corpus/templates/design/app/pages/DesignEditor.tsx +3087 -490
  89. package/corpus/templates/design/changelog/2026-06-29-added-a-review-panel-in-the-design-editor-s-inspector-with-a.md +6 -0
  90. package/corpus/templates/design/changelog/2026-06-29-copying-and-pasting-layers-no-longer-shows-success-notificat.md +6 -0
  91. package/corpus/templates/design/changelog/2026-06-29-device-presets-in-all-screens-view-resize-the-selected-previ.md +6 -0
  92. package/corpus/templates/design/changelog/2026-06-29-layer-and-canvas-drags-keep-the-layer-list-stable-while-chan.md +6 -0
  93. package/corpus/templates/design/changelog/2026-06-29-layer-moves-can-be-undone-and-redone-without-flashing-the-ca.md +6 -0
  94. package/corpus/templates/design/changelog/2026-06-29-screen-previews-use-a-single-blue-hover-border-in-all-screen.md +6 -0
  95. package/corpus/templates/design/changelog/2026-06-29-selected-containers-show-draggable-padding-and-gap-guides-on.md +6 -0
  96. package/corpus/templates/design/changelog/2026-06-29-the-design-editor-adds-a-studio-layer-with-tokens-respon.md +6 -0
  97. package/corpus/templates/design/changelog/2026-06-30-accessibility-findings-now-offer-a-one-click-fix.md +6 -0
  98. package/corpus/templates/design/changelog/2026-06-30-component-instances-now-have-editable-props-in-the-inspe.md +6 -0
  99. package/corpus/templates/design/changelog/2026-06-30-design-token-edits-now-stay-visible-in-previews-and-token-li.md +6 -0
  100. package/corpus/templates/design/changelog/2026-06-30-inspect-code-now-shows-the-elements-opening-tag-at-a-gla.md +6 -0
  101. package/corpus/templates/design/changelog/2026-06-30-shader-fill-presets-can-now-be-applied-to-an-element.md +6 -0
  102. package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-can-now-add-a-track-to-any-element-a.md +6 -0
  103. package/corpus/templates/design/changelog/2026-06-30-visual-editor-selection-layer-paint-and-drawing-controls-are-more-reliable.md +6 -0
  104. package/corpus/templates/design/e2e/global-setup.ts +94 -2
  105. package/corpus/templates/design/e2e/helpers.ts +10 -4
  106. package/corpus/templates/design/server/db/schema.ts +123 -0
  107. package/corpus/templates/design/server/plugins/db.ts +90 -0
  108. package/corpus/templates/design/shared/builder-app.ts +297 -0
  109. package/corpus/templates/design/shared/capability-resolver.ts +123 -0
  110. package/corpus/templates/design/shared/capture-sanitize.ts +70 -0
  111. package/corpus/templates/design/shared/code-layer.ts +1016 -71
  112. package/corpus/templates/design/shared/component-model.ts +274 -0
  113. package/corpus/templates/design/shared/design-review.ts +275 -0
  114. package/corpus/templates/design/shared/design-source-capabilities.ts +286 -0
  115. package/corpus/templates/design/shared/design-state.ts +112 -0
  116. package/corpus/templates/design/shared/design-surface-index.ts +258 -0
  117. package/corpus/templates/design/shared/motion-compiler.ts +349 -0
  118. package/corpus/templates/design/shared/motion-timeline.ts +193 -0
  119. package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
  120. package/corpus/templates/design/shared/responsive-classes.ts +452 -0
  121. package/corpus/templates/design/shared/shader-fill.ts +323 -0
  122. package/corpus/templates/design/shared/source-mode.ts +245 -0
  123. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +6 -2
  124. package/corpus/templates/plan/changelog/2026-06-29-plan-canvases-open-without-an-initial-pan-and-zoom-flicker.md +6 -0
  125. package/dist/agent/production-agent.d.ts.map +1 -1
  126. package/dist/agent/production-agent.js +94 -10
  127. package/dist/agent/production-agent.js.map +1 -1
  128. package/dist/cli/design-connect.d.ts.map +1 -1
  129. package/dist/cli/design-connect.js +28 -2
  130. package/dist/cli/design-connect.js.map +1 -1
  131. package/dist/cli/skills.d.ts.map +1 -1
  132. package/dist/cli/skills.js +37 -21
  133. package/dist/cli/skills.js.map +1 -1
  134. package/dist/client/AssistantChat.d.ts +14 -0
  135. package/dist/client/AssistantChat.d.ts.map +1 -1
  136. package/dist/client/AssistantChat.js +115 -22
  137. package/dist/client/AssistantChat.js.map +1 -1
  138. package/dist/client/sse-event-processor.d.ts.map +1 -1
  139. package/dist/client/sse-event-processor.js +10 -0
  140. package/dist/client/sse-event-processor.js.map +1 -1
  141. package/dist/collab/routes.d.ts +1 -1
  142. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  143. package/dist/notifications/routes.d.ts +2 -2
  144. package/dist/observability/routes.d.ts +8 -8
  145. package/dist/resources/handlers.d.ts +2 -2
  146. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  147. package/dist/server/transcribe-voice.d.ts +1 -1
  148. package/package.json +1 -1
@@ -0,0 +1,46 @@
1
+ import type { ActionRunContext } from "@agent-native/core/action";
2
+
3
+ const DEFAULT_TOOL_ACTIVITY_INTERVAL_MS = 8_000;
4
+
5
+ type ToolActivityOptions = {
6
+ label: string;
7
+ ongoingLabel?: string;
8
+ intervalMs?: number;
9
+ tool?: string;
10
+ };
11
+
12
+ function canEmitToolActivity(
13
+ context: ActionRunContext | undefined,
14
+ ): context is ActionRunContext & Required<Pick<ActionRunContext, "send">> {
15
+ return context?.caller === "tool" && typeof context.send === "function";
16
+ }
17
+
18
+ export async function withToolActivity<T>(
19
+ context: ActionRunContext | undefined,
20
+ options: ToolActivityOptions,
21
+ work: () => Promise<T>,
22
+ ): Promise<T> {
23
+ if (!canEmitToolActivity(context)) return work();
24
+
25
+ const tool = options.tool ?? context.actionName;
26
+ const sendActivity = (label: string) => {
27
+ context.send({
28
+ type: "activity",
29
+ label,
30
+ ...(tool ? { tool } : {}),
31
+ });
32
+ };
33
+
34
+ sendActivity(options.label);
35
+ const interval = setInterval(
36
+ () => sendActivity(options.ongoingLabel ?? options.label),
37
+ options.intervalMs ?? DEFAULT_TOOL_ACTIVITY_INTERVAL_MS,
38
+ );
39
+ interval.unref?.();
40
+
41
+ try {
42
+ return await work();
43
+ } finally {
44
+ clearInterval(interval);
45
+ }
46
+ }
@@ -18,7 +18,6 @@ import {
18
18
  STYLE_STRENGTHS,
19
19
  } from "../shared/api.js";
20
20
  import { requireGenerationSessionInLibrary } from "./_helpers.js";
21
- import { readImageModelDefault } from "./_image-model-default.js";
22
21
  import generateImage from "./generate-image.js";
23
22
  import { upsertVariantSlot } from "./variant-slots.js";
24
23
 
@@ -35,14 +34,36 @@ export default defineAction({
35
34
  "Brand kit/library ID. Pass the refId from a brand-kit @mention, or choose a kit from view-screen/list-libraries.",
36
35
  ),
37
36
  collectionId: z.string().optional(),
38
- presetId: z.string().optional(),
37
+ presetId: z
38
+ .string()
39
+ .optional()
40
+ .describe(
41
+ "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.",
42
+ ),
39
43
  sessionId: z.string().optional(),
40
44
  slots: z
41
45
  .array(
42
46
  z.object({
43
47
  slotId: z.string(),
44
48
  prompt: z.string().min(1),
45
- aspectRatio: z.enum(ASPECT_RATIOS).optional(),
49
+ embeddedText: z
50
+ .string()
51
+ .optional()
52
+ .describe(
53
+ "Exact words to render inside the image, spelled exactly. When set, the image is allowed to contain this text; when omitted, the image avoids embedded text.",
54
+ ),
55
+ textPlacement: z
56
+ .string()
57
+ .optional()
58
+ .describe(
59
+ "Where/how the embedded text should appear, e.g. 'centered headline', 'lower-left label'.",
60
+ ),
61
+ aspectRatio: z
62
+ .enum(ASPECT_RATIOS)
63
+ .optional()
64
+ .describe(
65
+ "Slot aspect ratio. When a presetId is set, omit this — the preset's aspect ratio is used unless the user explicitly asks for a different ratio for this slot.",
66
+ ),
46
67
  imageSize: z.enum(IMAGE_SIZES).optional(),
47
68
  categories: z.array(z.enum(IMAGE_CATEGORIES)).optional(),
48
69
  referenceAssetIds: z.array(z.string()).optional(),
@@ -78,7 +99,6 @@ export default defineAction({
78
99
  parallelSafe: true,
79
100
  timeoutMs: IMAGE_GENERATION_TOOL_TIMEOUT_MS,
80
101
  run: async ({ slots, ...inputBase }, context?: ActionRunContext) => {
81
- const imageModelDefault = await readImageModelDefault();
82
102
  const libraryId = inputBase.libraryId;
83
103
  if (!libraryId) {
84
104
  throw new Error(
@@ -88,7 +108,6 @@ export default defineAction({
88
108
  const base = {
89
109
  ...inputBase,
90
110
  libraryId,
91
- model: inputBase.model ?? imageModelDefault,
92
111
  };
93
112
  await assertAccess("asset-library", base.libraryId, "editor");
94
113
  if (base.sessionId) {
@@ -123,6 +142,8 @@ export default defineAction({
123
142
  presetId: base.presetId,
124
143
  sessionId: base.sessionId,
125
144
  prompt: slot.prompt,
145
+ embeddedText: slot.embeddedText,
146
+ textPlacement: slot.textPlacement,
126
147
  aspectRatio: slot.aspectRatio,
127
148
  imageSize: slot.imageSize,
128
149
  model: base.model,
@@ -21,6 +21,7 @@ import {
21
21
  DEFAULT_GENERATION_REFERENCE_LIMIT,
22
22
  generateWithManagedImageProvider,
23
23
  isImageGenerationSetupError,
24
+ resolveImageModelForRequest,
24
25
  selectReferences,
25
26
  } from "../server/lib/generation.js";
26
27
  import { compositeLogo } from "../server/lib/image-processing.js";
@@ -35,7 +36,6 @@ import {
35
36
  IMAGE_SIZES,
36
37
  STYLE_STRENGTHS,
37
38
  type ImageCategory,
38
- type ImageModel,
39
39
  type ImageQualityTier,
40
40
  type StyleBrief,
41
41
  } from "../shared/api.js";
@@ -44,22 +44,11 @@ import {
44
44
  serializeAsset,
45
45
  } from "./_helpers.js";
46
46
  import { readImageModelDefault } from "./_image-model-default.js";
47
+ import { withToolActivity } from "./_tool-activity.js";
47
48
  import { upsertVariantSlot, wasVariantSlotDismissed } from "./variant-slots.js";
48
49
 
49
50
  const IMAGE_GENERATION_TOOL_TIMEOUT_MS = 12 * 60_000;
50
51
 
51
- function resolveModelForTier(
52
- tier: ImageQualityTier | undefined,
53
- category: ImageCategory | undefined,
54
- ): ImageModel | undefined {
55
- if (!tier) return undefined;
56
- if (tier === "fast") return "gemini-3.1-flash-image";
57
- if (tier === "best") return "gemini-3-pro-image";
58
- return ["hero", "landing", "logo", "campaign"].includes(category ?? "")
59
- ? "gemini-3-pro-image"
60
- : "gemini-3.1-flash-image";
61
- }
62
-
63
52
  export default defineAction({
64
53
  description:
65
54
  "Generate one brand-consistent image from a brand kit/library. This is synchronous for images and returns the final asset with preview/download/embed URLs. Use @brand-kit mentions as libraryId and @preset mentions as presetId when present. Use generate-image-batch for multiple independent slots; do not poll image runs after this action returns.",
@@ -71,11 +60,38 @@ export default defineAction({
71
60
  "Brand kit/library ID. Pass the refId from a brand-kit @mention, or choose a kit from view-screen/list-libraries.",
72
61
  ),
73
62
  collectionId: z.string().optional(),
74
- presetId: z.string().optional(),
63
+ presetId: z
64
+ .string()
65
+ .optional()
66
+ .describe(
67
+ "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.",
68
+ ),
75
69
  sessionId: z.string().optional(),
76
70
  prompt: z.string().min(1),
77
- aspectRatio: z.enum(ASPECT_RATIOS).optional(),
78
- imageSize: z.enum(IMAGE_SIZES).optional(),
71
+ embeddedText: z
72
+ .string()
73
+ .optional()
74
+ .describe(
75
+ "Exact words to render inside the image, spelled exactly. When set, the image is allowed to contain this text; when omitted, the image avoids embedded text.",
76
+ ),
77
+ textPlacement: z
78
+ .string()
79
+ .optional()
80
+ .describe(
81
+ "Where/how the embedded text should appear, e.g. 'centered headline', 'lower-left label'.",
82
+ ),
83
+ aspectRatio: z
84
+ .enum(ASPECT_RATIOS)
85
+ .optional()
86
+ .describe(
87
+ "Image aspect ratio. When a presetId is set, omit this — the preset's aspect ratio is used. Pass a value only when there is no preset, or when the user explicitly asks for a ratio different from the preset's.",
88
+ ),
89
+ imageSize: z
90
+ .enum(IMAGE_SIZES)
91
+ .optional()
92
+ .describe(
93
+ "Output resolution tier. When a presetId is set, omit this — the preset's size is used unless the user explicitly requests a different one.",
94
+ ),
79
95
  model: z.enum(IMAGE_MODELS).optional(),
80
96
  tier: z.enum(IMAGE_QUALITY_TIERS).optional(),
81
97
  intent: z.enum(GENERATION_INTENTS).default("generate"),
@@ -251,11 +267,15 @@ export default defineAction({
251
267
  const category = (args.categories?.[0] ??
252
268
  preset?.category ??
253
269
  collection?.category) as ImageCategory | undefined;
254
- const resolvedModel = (args.model ??
255
- imageModelDefault ??
256
- resolveModelForTier(resolvedTier, category) ??
257
- preset?.model ??
258
- "gemini-3.1-flash-image") as (typeof IMAGE_MODELS)[number];
270
+ const resolvedModel = resolveImageModelForRequest({
271
+ explicitModel: args.model,
272
+ imageModelDefault,
273
+ explicitTier: args.tier,
274
+ resolvedTier,
275
+ category,
276
+ presetModel: preset?.model as (typeof IMAGE_MODELS)[number] | undefined,
277
+ embeddedText: args.embeddedText,
278
+ }) as (typeof IMAGE_MODELS)[number];
259
279
  const resolvedCategories =
260
280
  args.categories ??
261
281
  (preset?.category ? ([preset.category] as any) : undefined);
@@ -297,6 +317,8 @@ export default defineAction({
297
317
  .filter((item) => item?.trim())
298
318
  .join("\n\n"),
299
319
  prompt: promptForRun,
320
+ embeddedText: args.embeddedText,
321
+ textPlacement: args.textPlacement,
300
322
  referenceCount: references.length,
301
323
  includeLogo: args.includeLogo,
302
324
  aspectRatio: resolvedAspectRatio,
@@ -346,6 +368,8 @@ export default defineAction({
346
368
  collectionId: resolvedCollectionId ?? null,
347
369
  presetId: preset?.id ?? null,
348
370
  sessionId: session?.id ?? null,
371
+ embeddedText: args.embeddedText ?? null,
372
+ textPlacement: args.textPlacement ?? null,
349
373
  customInstructions: library.customInstructions ?? "",
350
374
  };
351
375
  const dismissibleSlot = args.dismissible !== false && Boolean(slotId);
@@ -357,6 +381,8 @@ export default defineAction({
357
381
  dismissible: dismissibleSlot,
358
382
  sourceAssetId: args.sourceAssetId,
359
383
  subjectAssetId: args.subjectAssetId,
384
+ embeddedText: args.embeddedText,
385
+ textPlacement: args.textPlacement,
360
386
  intent: args.intent,
361
387
  styleStrength: args.styleStrength,
362
388
  tier: resolvedTier,
@@ -404,22 +430,36 @@ export default defineAction({
404
430
  });
405
431
 
406
432
  try {
407
- const generated = await generateWithManagedImageProvider({
408
- prompt: promptForRun,
409
- compiledPrompt,
410
- references,
411
- model: resolvedModel,
412
- aspectRatio: resolvedAspectRatio,
413
- imageSize: resolvedImageSize,
414
- groundingMode: args.groundingMode,
415
- intent: args.intent,
416
- styleStrength: args.styleStrength,
417
- runId,
418
- libraryId: args.libraryId,
419
- collectionId: resolvedCollectionId ?? null,
420
- source: args.source,
421
- callerAppId: args.callerAppId,
422
- });
433
+ const generated = await withToolActivity(
434
+ context,
435
+ {
436
+ label: "Generating image.",
437
+ ongoingLabel: "Still generating image.",
438
+ // Intentionally no explicit `tool`: withToolActivity falls back to
439
+ // context.actionName, which is the ACTUAL dispatched tool. When this
440
+ // action is invoked directly that is "generate-image"; when it runs as
441
+ // a sub-step of generate-image-batch / rerun-generation-run it is the
442
+ // parent tool, so the activity event matches the real tool_start card
443
+ // instead of spawning an orphan "generate-image" activity card.
444
+ },
445
+ () =>
446
+ generateWithManagedImageProvider({
447
+ prompt: promptForRun,
448
+ compiledPrompt,
449
+ references,
450
+ model: resolvedModel,
451
+ aspectRatio: resolvedAspectRatio,
452
+ imageSize: resolvedImageSize,
453
+ groundingMode: args.groundingMode,
454
+ intent: args.intent,
455
+ styleStrength: args.styleStrength,
456
+ runId,
457
+ libraryId: args.libraryId,
458
+ collectionId: resolvedCollectionId ?? null,
459
+ source: args.source,
460
+ callerAppId: args.callerAppId,
461
+ }),
462
+ );
423
463
  await deleteAppState("image-generation-setup").catch(() => {});
424
464
  let image = generated.image;
425
465
  let mimeType = generated.mimeType;
@@ -468,37 +508,48 @@ export default defineAction({
468
508
  Artifacts: [],
469
509
  };
470
510
  }
471
- const asset = await createAssetFromBuffer({
472
- libraryId: args.libraryId,
473
- collectionId: resolvedCollectionId ?? null,
474
- buffer: image,
475
- mimeType,
476
- role: "generated",
477
- status: "candidate",
478
- prompt: args.prompt,
479
- model: generated.model,
480
- aspectRatio: resolvedAspectRatio,
481
- imageSize: resolvedImageSize,
482
- generationRunId: runId,
483
- metadata: {
484
- provider: generated.provider,
485
- compiledPrompt,
486
- referenceAssetIds: references.map((ref) => ref.id),
487
- sourceAssetId: args.sourceAssetId,
488
- subjectAssetId: args.subjectAssetId,
489
- intent: args.intent,
490
- styleStrength: args.styleStrength,
491
- tier: resolvedTier,
492
- includeLogo: args.includeLogo,
493
- presetId: preset?.id,
494
- sessionId: session?.id,
495
- generated: true,
496
- sourceUrl: generated.sourceUrl,
497
- providerGenerationId: generated.providerGenerationId,
498
- creditsCharged: generated.creditsCharged,
511
+ const asset = await withToolActivity(
512
+ context,
513
+ {
514
+ label: "Saving generated image.",
515
+ ongoingLabel: "Still saving generated image.",
516
+ // See above: omit `tool` so the activity is tagged with the real
517
+ // dispatched tool (context.actionName) rather than a hardcoded
518
+ // "generate-image" that orphans under batch/rerun.
499
519
  },
500
- category,
501
- });
520
+ () =>
521
+ createAssetFromBuffer({
522
+ libraryId: args.libraryId,
523
+ collectionId: resolvedCollectionId ?? null,
524
+ buffer: image,
525
+ mimeType,
526
+ role: "generated",
527
+ status: "candidate",
528
+ prompt: args.prompt,
529
+ model: generated.model,
530
+ aspectRatio: resolvedAspectRatio,
531
+ imageSize: resolvedImageSize,
532
+ generationRunId: runId,
533
+ metadata: {
534
+ provider: generated.provider,
535
+ compiledPrompt,
536
+ referenceAssetIds: references.map((ref) => ref.id),
537
+ sourceAssetId: args.sourceAssetId,
538
+ subjectAssetId: args.subjectAssetId,
539
+ intent: args.intent,
540
+ styleStrength: args.styleStrength,
541
+ tier: resolvedTier,
542
+ includeLogo: args.includeLogo,
543
+ presetId: preset?.id,
544
+ sessionId: session?.id,
545
+ generated: true,
546
+ sourceUrl: generated.sourceUrl,
547
+ providerGenerationId: generated.providerGenerationId,
548
+ creditsCharged: generated.creditsCharged,
549
+ },
550
+ category,
551
+ }),
552
+ );
502
553
  if (session) {
503
554
  const itemCreatedAt = nowIso();
504
555
  await db.insert(schema.assetGenerationSessionItems).values({
@@ -0,0 +1,50 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { accessFilter } from "@agent-native/core/sharing";
3
+ import { and, desc, eq, inArray, isNull } from "drizzle-orm";
4
+ import { z } from "zod";
5
+
6
+ import { getDb, schema } from "../server/db/index.js";
7
+ import { serializeAsset } from "./_helpers.js";
8
+
9
+ export default defineAction({
10
+ description:
11
+ "List unsaved draft generations (generated candidate assets) across accessible libraries, newest first.",
12
+ schema: z.object({
13
+ libraryId: z.string().optional(),
14
+ limit: z.coerce.number().int().min(1).max(500).optional(),
15
+ }),
16
+ http: { method: "GET" },
17
+ readOnly: true,
18
+ run: async ({ libraryId, limit }) => {
19
+ const db = getDb();
20
+ const libraryFilters = [
21
+ accessFilter(schema.assetLibraries, schema.assetLibraryShares),
22
+ isNull(schema.assetLibraries.archivedAt),
23
+ ];
24
+ if (libraryId) libraryFilters.push(eq(schema.assetLibraries.id, libraryId));
25
+ const accessibleLibraries = await db
26
+ .select({ id: schema.assetLibraries.id })
27
+ .from(schema.assetLibraries)
28
+ .where(and(...libraryFilters));
29
+ const libraryIds = accessibleLibraries.map((row) => row.id);
30
+ if (!libraryIds.length) return { count: 0, assets: [] };
31
+
32
+ const rows = await db
33
+ .select()
34
+ .from(schema.assets)
35
+ .where(
36
+ and(
37
+ inArray(schema.assets.libraryId, libraryIds),
38
+ eq(schema.assets.role, "generated"),
39
+ eq(schema.assets.status, "candidate"),
40
+ ),
41
+ )
42
+ .orderBy(desc(schema.assets.createdAt))
43
+ .limit(limit ?? 50);
44
+
45
+ return {
46
+ count: rows.length,
47
+ assets: rows.map((row) => serializeAsset(row)),
48
+ };
49
+ },
50
+ });
@@ -1,4 +1,5 @@
1
1
  import { defineAction } from "@agent-native/core";
2
+ import type { ActionRunContext } from "@agent-native/core/action";
2
3
  import { assertAccess } from "@agent-native/core/sharing";
3
4
  import { eq } from "drizzle-orm";
4
5
  import { z } from "zod";
@@ -30,7 +31,10 @@ export default defineAction({
30
31
  ),
31
32
  }),
32
33
  parallelSafe: true,
33
- run: async ({ runId, slotId, sessionId, source, callerAppId }) => {
34
+ run: async (
35
+ { runId, slotId, sessionId, source, callerAppId },
36
+ context?: ActionRunContext,
37
+ ) => {
34
38
  const db = getDb();
35
39
  const [run] = await db
36
40
  .select()
@@ -52,6 +56,8 @@ export default defineAction({
52
56
  intent?: string;
53
57
  styleStrength?: string;
54
58
  subjectAssetId?: string;
59
+ embeddedText?: string | null;
60
+ textPlacement?: string | null;
55
61
  };
56
62
  includeLogo?: boolean;
57
63
  categories?: string[];
@@ -60,39 +66,54 @@ export default defineAction({
60
66
  intent?: string;
61
67
  styleStrength?: string;
62
68
  tier?: string | null;
69
+ embeddedText?: string | null;
70
+ textPlacement?: string | null;
63
71
  }>(run.metadata, {});
64
72
  const categories =
65
73
  metadata.settingsUsed?.categories ?? metadata.categories ?? undefined;
66
74
 
67
- return generateImage.run({
68
- libraryId: run.libraryId,
69
- collectionId: run.collectionId ?? undefined,
70
- presetId: run.presetId ?? undefined,
71
- sessionId: resolvedSessionId,
72
- prompt: run.prompt,
73
- aspectRatio: run.aspectRatio as any,
74
- imageSize: run.imageSize as any,
75
- model: run.model as any,
76
- tier: (metadata.settingsUsed?.tier ?? metadata.tier ?? undefined) as any,
77
- intent: (metadata.settingsUsed?.intent ??
78
- metadata.intent ??
79
- "generate") as any,
80
- styleStrength: (metadata.settingsUsed?.styleStrength ??
81
- metadata.styleStrength ??
82
- "balanced") as any,
83
- categories: categories as any,
84
- includeLogo: Boolean(
85
- metadata.settingsUsed?.includeLogo ?? metadata.includeLogo,
86
- ),
87
- groundingMode: run.groundingMode as any,
88
- sourceAssetId: metadata.sourceAssetId,
89
- subjectAssetId:
90
- metadata.settingsUsed?.subjectAssetId ??
91
- metadata.subjectAssetId ??
92
- undefined,
93
- slotId,
94
- source,
95
- callerAppId,
96
- });
75
+ return generateImage.run(
76
+ {
77
+ libraryId: run.libraryId,
78
+ collectionId: run.collectionId ?? undefined,
79
+ presetId: run.presetId ?? undefined,
80
+ sessionId: resolvedSessionId,
81
+ prompt: run.prompt,
82
+ embeddedText:
83
+ metadata.settingsUsed?.embeddedText ??
84
+ metadata.embeddedText ??
85
+ undefined,
86
+ textPlacement:
87
+ metadata.settingsUsed?.textPlacement ??
88
+ metadata.textPlacement ??
89
+ undefined,
90
+ aspectRatio: run.aspectRatio as any,
91
+ imageSize: run.imageSize as any,
92
+ model: run.model as any,
93
+ tier: (metadata.settingsUsed?.tier ??
94
+ metadata.tier ??
95
+ undefined) as any,
96
+ intent: (metadata.settingsUsed?.intent ??
97
+ metadata.intent ??
98
+ "generate") as any,
99
+ styleStrength: (metadata.settingsUsed?.styleStrength ??
100
+ metadata.styleStrength ??
101
+ "balanced") as any,
102
+ categories: categories as any,
103
+ includeLogo: Boolean(
104
+ metadata.settingsUsed?.includeLogo ?? metadata.includeLogo,
105
+ ),
106
+ groundingMode: run.groundingMode as any,
107
+ sourceAssetId: metadata.sourceAssetId,
108
+ subjectAssetId:
109
+ metadata.settingsUsed?.subjectAssetId ??
110
+ metadata.subjectAssetId ??
111
+ undefined,
112
+ slotId,
113
+ source,
114
+ callerAppId,
115
+ },
116
+ context,
117
+ );
97
118
  },
98
119
  });
@@ -0,0 +1,100 @@
1
+ import { useActionQuery, useT } from "@agent-native/core/client";
2
+ import { IconArrowUpRight, IconPhoto } from "@tabler/icons-react";
3
+ import { Link } from "react-router";
4
+
5
+ import { Button } from "@/components/ui/button";
6
+ import { Skeleton } from "@/components/ui/skeleton";
7
+
8
+ type DraftAsset = {
9
+ id: string;
10
+ title?: string | null;
11
+ prompt?: string | null;
12
+ mediaType?: string | null;
13
+ mimeType?: string | null;
14
+ thumbnailUrl?: string | null;
15
+ previewUrl?: string | null;
16
+ url?: string | null;
17
+ };
18
+
19
+ const RECENT_DRAFTS_LIMIT = 5;
20
+
21
+ export function RecentDraftsSection() {
22
+ const t = useT();
23
+ const { data, isLoading } = useActionQuery("list-draft-assets", {
24
+ limit: RECENT_DRAFTS_LIMIT,
25
+ });
26
+ const drafts = ((data as any)?.assets ?? []) as DraftAsset[];
27
+
28
+ if (!isLoading && drafts.length === 0) return null;
29
+
30
+ return (
31
+ <section className="space-y-3">
32
+ <div className="flex flex-wrap items-center justify-between gap-3">
33
+ <h2 className="text-sm font-semibold text-foreground">
34
+ {t("library.recentDrafts")}
35
+ </h2>
36
+ <Button asChild variant="outline" size="sm">
37
+ <Link to="/library?tab=drafts">
38
+ {t("library.viewAllDrafts")}
39
+ <IconArrowUpRight size={15} className="ml-1.5" />
40
+ </Link>
41
+ </Button>
42
+ </div>
43
+
44
+ <div className="grid grid-cols-3 gap-3 sm:grid-cols-5">
45
+ {isLoading
46
+ ? Array.from({ length: RECENT_DRAFTS_LIMIT }).map((_, index) => (
47
+ <Skeleton key={index} className="aspect-square rounded-lg" />
48
+ ))
49
+ : drafts.map((draft) => (
50
+ <Link
51
+ key={draft.id}
52
+ to={`/asset/${encodeURIComponent(draft.id)}`}
53
+ title={draft.title || draft.prompt || t("library.draftAsset")}
54
+ className="group block overflow-hidden rounded-lg border border-border bg-card shadow-sm transition hover:border-primary/60 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
55
+ >
56
+ <div className="aspect-square bg-muted">
57
+ <DraftThumbnail draft={draft} />
58
+ </div>
59
+ </Link>
60
+ ))}
61
+ </div>
62
+ </section>
63
+ );
64
+ }
65
+
66
+ function DraftThumbnail({ draft }: { draft: DraftAsset }) {
67
+ const t = useT();
68
+ const isVideo =
69
+ draft.mediaType === "video" || draft.mimeType?.startsWith("video/");
70
+ const source = draft.thumbnailUrl ?? draft.previewUrl ?? draft.url ?? "";
71
+
72
+ if (isVideo && !draft.thumbnailUrl) {
73
+ return (
74
+ <video
75
+ src={draft.previewUrl ?? draft.url ?? undefined}
76
+ muted
77
+ playsInline
78
+ preload="metadata"
79
+ className="h-full w-full object-cover transition group-hover:scale-[1.02]"
80
+ />
81
+ );
82
+ }
83
+
84
+ if (!source) {
85
+ return (
86
+ <div className="flex h-full w-full items-center justify-center text-muted-foreground">
87
+ <IconPhoto className="size-5" />
88
+ </div>
89
+ );
90
+ }
91
+
92
+ return (
93
+ <img
94
+ src={source}
95
+ alt={draft.title ?? draft.prompt ?? t("library.draftAsset")}
96
+ loading="lazy"
97
+ className="h-full w-full object-cover transition group-hover:scale-[1.02]"
98
+ />
99
+ );
100
+ }
@@ -628,6 +628,13 @@ const messages = {
628
628
  videosCount: "{{count}} 個影片",
629
629
  },
630
630
  library: {
631
+ drafts: "草稿",
632
+ allLibraries: "所有資料庫",
633
+ noDrafts: "尚無草稿。",
634
+ noMatchingDrafts: "沒有符合的草稿。",
635
+ recentDrafts: "最近草稿",
636
+ viewAllDrafts: "查看所有草稿",
637
+ draftAsset: "草稿資產",
631
638
  addedToReferences: "已新增到參考。",
632
639
  addedAssetsToReferences: "已將 {{count}} 個資產新增到參考。",
633
640
  add: "新增",