@agent-native/core 0.84.10 → 0.84.13

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 (140) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +48 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/durable-background.ts +30 -0
  5. package/corpus/core/src/agent/production-agent.ts +34 -6
  6. package/corpus/core/src/agent/run-manager.ts +45 -2
  7. package/corpus/core/src/agent/run-store.ts +29 -6
  8. package/corpus/core/src/chat-threads/store.ts +2 -0
  9. package/corpus/core/src/cli/skills.ts +10 -6
  10. package/corpus/core/src/client/AssistantChat.tsx +40 -9
  11. package/corpus/core/src/client/agent-chat-adapter.ts +23 -2
  12. package/corpus/core/src/client/chat/run-recovery.tsx +52 -44
  13. package/corpus/core/src/client/chat/tool-call-display.tsx +7 -2
  14. package/corpus/core/src/client/require-session.tsx +20 -1
  15. package/corpus/core/src/client/sharing/ShareButton.tsx +9 -3
  16. package/corpus/core/src/collab/storage.ts +40 -5
  17. package/corpus/core/src/server/agent-chat-plugin.ts +1 -0
  18. package/corpus/core/src/server/auth.ts +6 -0
  19. package/corpus/core/src/shared/streaming-text-smoothing.ts +10 -5
  20. package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +7 -6
  21. package/corpus/templates/assets/AGENTS.md +15 -0
  22. package/corpus/templates/assets/actions/_helpers.ts +3 -1
  23. package/corpus/templates/assets/actions/create-generation-preset.ts +12 -1
  24. package/corpus/templates/assets/actions/generate-asset.ts +5 -1
  25. package/corpus/templates/assets/actions/generate-image-batch.ts +6 -1
  26. package/corpus/templates/assets/actions/generate-image.ts +18 -11
  27. package/corpus/templates/assets/actions/open-asset-picker.ts +4 -2
  28. package/corpus/templates/assets/actions/update-generation-preset.ts +16 -3
  29. package/corpus/templates/assets/app/i18n/zh-TW.ts +3 -0
  30. package/corpus/templates/assets/app/i18n-data.ts +30 -0
  31. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +42 -7
  32. package/corpus/templates/assets/app/routes/library.tsx +4 -2
  33. package/corpus/templates/assets/changelog/2026-07-01-generation-presets-can-now-composite-your-brand-s-canonical-.md +6 -0
  34. package/corpus/templates/assets/changelog/2026-07-01-tagging-a-preset-now-briefs-the-agent-on-that-preset-s-aesth.md +6 -0
  35. package/corpus/templates/assets/server/lib/preset-chat-context.ts +118 -0
  36. package/corpus/templates/assets/server/plugins/agent-chat.ts +5 -0
  37. package/corpus/templates/assets/shared/api.ts +1 -0
  38. package/corpus/templates/clips/app/components/library/recording-card.tsx +14 -4
  39. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +10 -12
  40. package/corpus/templates/clips/app/hooks/use-library.ts +7 -4
  41. package/corpus/templates/clips/app/lib/recording-status.ts +32 -0
  42. package/corpus/templates/clips/changelog/2026-07-01-dictation-keeps-listening-through-natural-pauses-and-gives-c.md +6 -0
  43. package/corpus/templates/clips/changelog/2026-07-01-redoing-a-paused-desktop-recording-no-longer-leaves-the-recording-controls-disabled.md +6 -0
  44. package/corpus/templates/clips/changelog/2026-07-01-stalled-clip-uploads-now-update-in-the-library-and-surface-as-failed.md +6 -0
  45. package/corpus/templates/clips/desktop/src/lib/recorder.ts +36 -18
  46. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +99 -27
  47. package/corpus/templates/clips/desktop/src/overlays/flow-bar.tsx +4 -43
  48. package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +3 -0
  49. package/corpus/templates/clips/desktop/src/styles.css +5 -48
  50. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +4 -5
  51. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +15 -6
  52. package/corpus/templates/design/AGENTS.md +8 -4
  53. package/corpus/templates/design/actions/create-design.ts +1 -0
  54. package/corpus/templates/design/actions/duplicate-design.ts +3 -1
  55. package/corpus/templates/design/actions/edit-design.ts +43 -13
  56. package/corpus/templates/design/actions/export-html.ts +4 -2
  57. package/corpus/templates/design/actions/export-pdf.ts +3 -1
  58. package/corpus/templates/design/actions/export-svg.ts +4 -2
  59. package/corpus/templates/design/actions/export-zip.ts +10 -3
  60. package/corpus/templates/design/actions/generate-design.ts +5 -1
  61. package/corpus/templates/design/actions/present-design-variants.ts +17 -12
  62. package/corpus/templates/design/actions/update-file.ts +68 -17
  63. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +3 -2
  64. package/corpus/templates/design/app/components/design/EditPanel.tsx +35 -3
  65. package/corpus/templates/design/app/components/design/MotionDock.tsx +46 -11
  66. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +80 -30
  67. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +60 -10
  68. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +2 -0
  69. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +4 -0
  70. package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
  71. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +2 -2
  72. package/corpus/templates/design/app/i18n-data.ts +1 -1
  73. package/corpus/templates/design/app/pages/DesignEditor.tsx +493 -109
  74. package/corpus/templates/design/app/pages/VisualEdit.tsx +4 -16
  75. package/corpus/templates/design/changelog/2026-07-01-org-members-can-see-design-projects-created-in-their-workspace.md +6 -0
  76. package/corpus/templates/design/e2e/helpers.ts +2 -2
  77. package/corpus/templates/design/server/plugins/db.ts +9 -0
  78. package/dist/agent/durable-background.d.ts +3 -0
  79. package/dist/agent/durable-background.d.ts.map +1 -1
  80. package/dist/agent/durable-background.js +19 -0
  81. package/dist/agent/durable-background.js.map +1 -1
  82. package/dist/agent/production-agent.d.ts +1 -0
  83. package/dist/agent/production-agent.d.ts.map +1 -1
  84. package/dist/agent/production-agent.js +29 -6
  85. package/dist/agent/production-agent.js.map +1 -1
  86. package/dist/agent/run-manager.d.ts.map +1 -1
  87. package/dist/agent/run-manager.js +40 -2
  88. package/dist/agent/run-manager.js.map +1 -1
  89. package/dist/agent/run-store.d.ts.map +1 -1
  90. package/dist/agent/run-store.js +28 -6
  91. package/dist/agent/run-store.js.map +1 -1
  92. package/dist/chat-threads/store.d.ts +1 -0
  93. package/dist/chat-threads/store.d.ts.map +1 -1
  94. package/dist/chat-threads/store.js +2 -0
  95. package/dist/chat-threads/store.js.map +1 -1
  96. package/dist/cli/skills.d.ts.map +1 -1
  97. package/dist/cli/skills.js +10 -6
  98. package/dist/cli/skills.js.map +1 -1
  99. package/dist/client/AssistantChat.d.ts +10 -0
  100. package/dist/client/AssistantChat.d.ts.map +1 -1
  101. package/dist/client/AssistantChat.js +26 -8
  102. package/dist/client/AssistantChat.js.map +1 -1
  103. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  104. package/dist/client/agent-chat-adapter.js +21 -2
  105. package/dist/client/agent-chat-adapter.js.map +1 -1
  106. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  107. package/dist/client/chat/run-recovery.js +15 -10
  108. package/dist/client/chat/run-recovery.js.map +1 -1
  109. package/dist/client/chat/tool-call-display.d.ts +1 -0
  110. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  111. package/dist/client/chat/tool-call-display.js +3 -2
  112. package/dist/client/chat/tool-call-display.js.map +1 -1
  113. package/dist/client/require-session.d.ts +14 -0
  114. package/dist/client/require-session.d.ts.map +1 -1
  115. package/dist/client/require-session.js +19 -1
  116. package/dist/client/require-session.js.map +1 -1
  117. package/dist/client/sharing/ShareButton.d.ts +4 -2
  118. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  119. package/dist/client/sharing/ShareButton.js +3 -1
  120. package/dist/client/sharing/ShareButton.js.map +1 -1
  121. package/dist/collab/routes.d.ts +2 -2
  122. package/dist/collab/storage.d.ts.map +1 -1
  123. package/dist/collab/storage.js +31 -5
  124. package/dist/collab/storage.js.map +1 -1
  125. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  126. package/dist/notifications/routes.d.ts +2 -2
  127. package/dist/observability/routes.d.ts +5 -5
  128. package/dist/progress/routes.d.ts +1 -1
  129. package/dist/resources/handlers.d.ts +3 -3
  130. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  131. package/dist/server/agent-chat-plugin.js +1 -1
  132. package/dist/server/agent-chat-plugin.js.map +1 -1
  133. package/dist/server/auth.d.ts.map +1 -1
  134. package/dist/server/auth.js +7 -0
  135. package/dist/server/auth.js.map +1 -1
  136. package/dist/server/transcribe-voice.d.ts +1 -1
  137. package/dist/shared/streaming-text-smoothing.d.ts.map +1 -1
  138. package/dist/shared/streaming-text-smoothing.js +8 -5
  139. package/dist/shared/streaming-text-smoothing.js.map +1 -1
  140. package/package.json +1 -1
@@ -383,7 +383,10 @@ const messages = {
383
383
  videoPromptPlaceholder: "8 秒產品展示,慢速推近鏡頭",
384
384
  imagePromptPlaceholder: "關於冷啟動延遲文章的部落格頭圖",
385
385
  variants: "{{count}} 個變體",
386
+ logo: "徽標",
386
387
  compositeCanonicalLogo: "合成規範徽標",
388
+ compositeCanonicalLogoHint:
389
+ "為使用此預設產生的每張圖片合成此套件的規範徽標。需要已設定規範徽標。",
387
390
  openChat: "開啟聊天",
388
391
  defaultTextPolicy: "盡量不要嵌入文字。如需文字,請保持簡短且易讀。",
389
392
  generationPresetCreated: "生成預設已建立。",
@@ -127,7 +127,10 @@ const brandKitDetailEnUS = {
127
127
  "Eight-second product reveal with slow camera push-in",
128
128
  imagePromptPlaceholder: "Blog hero for an article about cold-start latency",
129
129
  variants: "{{count}} variants",
130
+ logo: "Logo",
130
131
  compositeCanonicalLogo: "Composite canonical logo",
132
+ compositeCanonicalLogoHint:
133
+ "Stamps this kit's canonical logo onto every image from this preset. Requires a canonical logo.",
131
134
  openChat: "Open chat",
132
135
  defaultTextPolicy:
133
136
  "Prefer no embedded text. Keep any requested text short and readable.",
@@ -968,7 +971,10 @@ const brandKitDetailArSA = {
968
971
  clearSearch: "مسح البحث",
969
972
  clearing: "المقاصة...",
970
973
  compiledPrompt: "موجه المترجمة",
974
+ logo: "شعار",
971
975
  compositeCanonicalLogo: "الشعار الأساسي المركب",
976
+ compositeCanonicalLogoHint:
977
+ "يضيف الشعار الأساسي لهذه المجموعة إلى كل صورة تُنشأ بهذا الإعداد المسبق. يتطلب وجود شعار أساسي.",
972
978
  couldNotAddSelectedToReferences: "لا يمكن إضافة الأصول المحددة إلى المراجع.",
973
979
  couldNotAddToReferences: "لا يمكن إضافة الأصل إلى المراجع.",
974
980
  couldNotArchiveBrandKit: "تعذر أرشفة مجموعة العلامة التجارية.",
@@ -1137,7 +1143,10 @@ const brandKitDetailDeDE = {
1137
1143
  clearSearch: "Suche löschen",
1138
1144
  clearing: "Löschen...",
1139
1145
  compiledPrompt: "Kompilierte Eingabeaufforderung",
1146
+ logo: "Logo",
1140
1147
  compositeCanonicalLogo: "Zusammengesetztes kanonisches Logo",
1148
+ compositeCanonicalLogoHint:
1149
+ "Fügt das kanonische Logo dieses Kits jedem mit dieser Vorlage erstellten Bild hinzu. Erfordert ein kanonisches Logo.",
1141
1150
  couldNotAddSelectedToReferences:
1142
1151
  "Ausgewählte Assets konnten nicht zu Referenzen hinzugefügt werden.",
1143
1152
  couldNotAddToReferences:
@@ -1312,7 +1321,10 @@ const brandKitDetailEsES = {
1312
1321
  clearSearch: "Borrar búsqueda",
1313
1322
  clearing: "Limpiando...",
1314
1323
  compiledPrompt: "Aviso compilado",
1324
+ logo: "Logotipo",
1315
1325
  compositeCanonicalLogo: "Logotipo canónico compuesto",
1326
+ compositeCanonicalLogoHint:
1327
+ "Añade el logotipo canónico de este kit a cada imagen creada con este ajuste. Requiere un logotipo canónico.",
1316
1328
  couldNotAddSelectedToReferences:
1317
1329
  "No se pudieron agregar los recursos seleccionados a las Referencias.",
1318
1330
  couldNotAddToReferences: "No se pudo agregar activo a Referencias.",
@@ -1489,7 +1501,10 @@ const brandKitDetailFrFR = {
1489
1501
  clearSearch: "Effacer la recherche",
1490
1502
  clearing: "Dégagement...",
1491
1503
  compiledPrompt: "Invite compilée",
1504
+ logo: "Logo",
1492
1505
  compositeCanonicalLogo: "Logo canonique composite",
1506
+ compositeCanonicalLogoHint:
1507
+ "Ajoute le logo canonique de ce kit à chaque image créée avec ce préréglage. Nécessite un logo canonique.",
1493
1508
  couldNotAddSelectedToReferences:
1494
1509
  "Impossible d'ajouter les éléments sélectionnés aux références.",
1495
1510
  couldNotAddToReferences: "Impossible d'ajouter un élément aux références.",
@@ -1664,7 +1679,10 @@ const brandKitDetailHiIN = {
1664
1679
  clearSearch: "स्पष्ट खोज",
1665
1680
  clearing: "समाशोधन...",
1666
1681
  compiledPrompt: "शीघ्र संकलित",
1682
+ logo: "लोगो",
1667
1683
  compositeCanonicalLogo: "समग्र विहित लोगो",
1684
+ compositeCanonicalLogoHint:
1685
+ "इस प्रीसेट से बनी हर छवि पर इस किट का विहित लोगो जोड़ता है। इसके लिए विहित लोगो आवश्यक है।",
1668
1686
  couldNotAddSelectedToReferences:
1669
1687
  "चयनित परिसंपत्तियों को संदर्भों में नहीं जोड़ा जा सका.",
1670
1688
  couldNotAddToReferences: "संदर्भों में संपत्ति नहीं जोड़ी जा सकी.",
@@ -1832,7 +1850,10 @@ const brandKitDetailJaJP = {
1832
1850
  clearSearch: "検索をクリア",
1833
1851
  clearing: "クリア中...",
1834
1852
  compiledPrompt: "コンパイルされたプロンプト",
1853
+ logo: "ロゴ",
1835
1854
  compositeCanonicalLogo: "複合正規ロゴ",
1855
+ compositeCanonicalLogoHint:
1856
+ "このプリセットで生成する各画像に、このキットの正規ロゴを合成します。正規ロゴの設定が必要です。",
1836
1857
  couldNotAddSelectedToReferences:
1837
1858
  "選択したアセットを参照に追加できませんでした。",
1838
1859
  couldNotAddToReferences: "アセットを参照に追加できませんでした。",
@@ -2005,7 +2026,10 @@ const brandKitDetailKoKR = {
2005
2026
  clearSearch: "검색 지우기",
2006
2027
  clearing: "지우는 중...",
2007
2028
  compiledPrompt: "컴파일된 프롬프트",
2029
+ logo: "로고",
2008
2030
  compositeCanonicalLogo: "복합 표준 로고",
2031
+ compositeCanonicalLogoHint:
2032
+ "이 프리셋으로 생성되는 모든 이미지에 이 키트의 표준 로고를 합성합니다. 표준 로고가 필요합니다.",
2009
2033
  couldNotAddSelectedToReferences: "선택한 자산을 참조에 추가할 수 없습니다.",
2010
2034
  couldNotAddToReferences: "참조에 자산을 추가할 수 없습니다.",
2011
2035
  couldNotArchiveBrandKit: "브랜드 키트를 보관할 수 없습니다.",
@@ -2172,7 +2196,10 @@ const brandKitDetailPtBR = {
2172
2196
  clearSearch: "Limpar pesquisa",
2173
2197
  clearing: "Limpando...",
2174
2198
  compiledPrompt: "Prompt compilado",
2199
+ logo: "Logotipo",
2175
2200
  compositeCanonicalLogo: "Logotipo canônico composto",
2201
+ compositeCanonicalLogoHint:
2202
+ "Adiciona o logotipo canônico deste kit a cada imagem criada com esta predefinição. Requer um logotipo canônico.",
2176
2203
  couldNotAddSelectedToReferences:
2177
2204
  "Não foi possível adicionar os ativos selecionados às referências.",
2178
2205
  couldNotAddToReferences: "Não foi possível adicionar recurso às referências.",
@@ -3305,7 +3332,10 @@ export const messagesByLocale = {
3305
3332
  videoPromptPlaceholder: "8 秒产品展示,慢速推近镜头",
3306
3333
  imagePromptPlaceholder: "关于冷启动延迟文章的博客头图",
3307
3334
  variants: "{{count}} 个变体",
3335
+ logo: "徽标",
3308
3336
  compositeCanonicalLogo: "合成规范徽标",
3337
+ compositeCanonicalLogoHint:
3338
+ "为使用此预设生成的每张图片合成此套件的规范徽标。需要已设置规范徽标。",
3309
3339
  openChat: "打开聊天",
3310
3340
  defaultTextPolicy: "尽量不要嵌入文字。如需文字,请保持简短且易读。",
3311
3341
  generationPresetCreated: "生成预设已创建。",
@@ -1151,12 +1151,6 @@ export function BrandKitDetailRoute({
1151
1151
  <IconFolderPlus className="mr-2 h-4 w-4 shrink-0" />
1152
1152
  {t("library.newFolder")}
1153
1153
  </DropdownMenuItem>
1154
- <ShareButton
1155
- resourceType="asset-library"
1156
- resourceId={library.id}
1157
- resourceTitle={library.title}
1158
- triggerClassName="w-full justify-start border-0 bg-transparent px-2 py-1.5 text-sm font-normal shadow-none hover:bg-accent hover:text-accent-foreground"
1159
- />
1160
1154
  <DropdownMenuSeparator />
1161
1155
  <DropdownMenuItem
1162
1156
  disabled={duplicateLibrary.isPending}
@@ -1183,15 +1177,31 @@ export function BrandKitDetailRoute({
1183
1177
  </DropdownMenuContent>
1184
1178
  </DropdownMenu>
1185
1179
  );
1180
+ const shareAction = (
1181
+ <ShareButton
1182
+ trigger="label-icon"
1183
+ resourceType="asset-library"
1184
+ resourceId={library.id}
1185
+ resourceTitle={library.title}
1186
+ triggerClassName="h-10 gap-2 px-4 border-input bg-background hover:bg-accent hover:text-accent-foreground"
1187
+ />
1188
+ );
1186
1189
  const headerActions = (
1187
1190
  <div className="flex flex-wrap items-center gap-2 sm:flex-nowrap lg:shrink-0">
1188
1191
  {uploadAction}
1192
+ {shareAction}
1189
1193
  {moreActions}
1190
1194
  </div>
1191
1195
  );
1192
1196
  const headerPrimaryActionsPortal =
1193
1197
  headerMode === "actions" && headerPrimaryActionsTarget
1194
- ? createPortal(uploadAction, headerPrimaryActionsTarget)
1198
+ ? createPortal(
1199
+ <>
1200
+ {uploadAction}
1201
+ {shareAction}
1202
+ </>,
1203
+ headerPrimaryActionsTarget,
1204
+ )
1195
1205
  : null;
1196
1206
  const headerMoreActionsPortal =
1197
1207
  headerMode === "actions" && headerMoreActionsTarget
@@ -2036,6 +2046,7 @@ function GenerationPresetsPanel({
2036
2046
  const [aspectRatio, setAspectRatio] = useState<AspectRatio>("1:1");
2037
2047
  const [promptTemplate, setPromptTemplate] = useState("");
2038
2048
  const [textPolicy, setTextPolicy] = useState(t("library.defaultTextPolicy"));
2049
+ const [includeLogo, setIncludeLogo] = useState(false);
2039
2050
 
2040
2051
  function reset() {
2041
2052
  setTitle("");
@@ -2043,6 +2054,7 @@ function GenerationPresetsPanel({
2043
2054
  setAspectRatio("1:1");
2044
2055
  setPromptTemplate("");
2045
2056
  setTextPolicy(t("library.defaultTextPolicy"));
2057
+ setIncludeLogo(false);
2046
2058
  }
2047
2059
 
2048
2060
  function submit() {
@@ -2058,6 +2070,7 @@ function GenerationPresetsPanel({
2058
2070
  promptTemplate: promptTemplate.trim() || undefined,
2059
2071
  textPolicy,
2060
2072
  referencePolicy: "auto",
2073
+ includeLogo,
2061
2074
  },
2062
2075
  {
2063
2076
  onSuccess: () => {
@@ -2101,6 +2114,9 @@ function GenerationPresetsPanel({
2101
2114
  {preset.title}
2102
2115
  </span>
2103
2116
  <Badge variant="outline">{preset.aspectRatio}</Badge>
2117
+ {preset.includeLogo ? (
2118
+ <Badge variant="secondary">{t("brandKitDetail.logo")}</Badge>
2119
+ ) : null}
2104
2120
  </div>
2105
2121
  <p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
2106
2122
  {preset.textPolicy || preset.description || preset.category}
@@ -2251,6 +2267,25 @@ function GenerationPresetsPanel({
2251
2267
  onChange={(event) => setTextPolicy(event.target.value)}
2252
2268
  />
2253
2269
  </div>
2270
+ <label
2271
+ htmlFor="preset-include-logo"
2272
+ className="flex items-start gap-3 rounded-md border border-border p-3"
2273
+ >
2274
+ <Checkbox
2275
+ id="preset-include-logo"
2276
+ checked={includeLogo}
2277
+ onCheckedChange={(checked) => setIncludeLogo(checked === true)}
2278
+ className="mt-0.5"
2279
+ />
2280
+ <span className="grid gap-1">
2281
+ <span className="text-sm font-medium leading-none">
2282
+ {t("brandKitDetail.compositeCanonicalLogo")}
2283
+ </span>
2284
+ <span className="text-xs text-muted-foreground">
2285
+ {t("brandKitDetail.compositeCanonicalLogoHint")}
2286
+ </span>
2287
+ </span>
2288
+ </label>
2254
2289
  </div>
2255
2290
  <DialogFooter>
2256
2291
  <Button variant="outline" onClick={() => setOpen(false)}>
@@ -2439,7 +2439,8 @@ export function AssetPickerSurface() {
2439
2439
  presetTitle: selectedPreset?.title ?? null,
2440
2440
  tier: hostConfig.tier,
2441
2441
  styleStrength: hostConfig.styleStrength ?? "balanced",
2442
- includeLogo: hostConfig.includeLogo ?? false,
2442
+ // Omit when unset so the selected preset's logo setting drives it.
2443
+ includeLogo: hostConfig.includeLogo,
2443
2444
  }),
2444
2445
  submit: true,
2445
2446
  openSidebar: true,
@@ -2463,7 +2464,8 @@ export function AssetPickerSurface() {
2463
2464
  })),
2464
2465
  tier: hostConfig.tier,
2465
2466
  styleStrength: hostConfig.styleStrength ?? "balanced",
2466
- includeLogo: hostConfig.includeLogo ?? false,
2467
+ // Omit when unset so the selected preset's logo setting drives it.
2468
+ includeLogo: hostConfig.includeLogo,
2467
2469
  source: "ui",
2468
2470
  callerAppId: hostConfig.callerAppId,
2469
2471
  } as any);
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-07-01
4
+ ---
5
+
6
+ Generation presets can now composite your brand's canonical logo onto every image made with them
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-01
4
+ ---
5
+
6
+ Tagging a preset now briefs the agent on that preset's aesthetics and philosophy before it generates, for more on-brand results
@@ -0,0 +1,118 @@
1
+ import type { AgentChatReference } from "@agent-native/core/server";
2
+ import { inArray } from "drizzle-orm";
3
+
4
+ import type { StyleBrief } from "../../shared/api.js";
5
+ import { getDb, schema } from "../db/index.js";
6
+ import { parseJson } from "./json.js";
7
+
8
+ const PRESET_REF_TYPE = "preset";
9
+
10
+ type PresetRow = typeof schema.assetGenerationPresets.$inferSelect;
11
+ type LibraryRow = typeof schema.assetLibraries.$inferSelect;
12
+
13
+ /**
14
+ * When a user tags one or more generation presets with an `@preset` mention,
15
+ * embed each preset's aesthetics and creative philosophy into the model-facing
16
+ * message so the agent internalizes the brief before it generates. The user's
17
+ * visible message is untouched — only the message the model reads is augmented.
18
+ */
19
+ export async function preparePresetChatContext(args: {
20
+ message: string;
21
+ references: AgentChatReference[];
22
+ }): Promise<{ message?: string } | void> {
23
+ const presetIds = Array.from(
24
+ new Set(
25
+ (args.references ?? [])
26
+ .filter((ref) => ref.refType === PRESET_REF_TYPE && ref.refId)
27
+ .map((ref) => ref.refId as string),
28
+ ),
29
+ );
30
+ if (!presetIds.length) return;
31
+
32
+ const db = getDb();
33
+ const presets = (await db
34
+ .select()
35
+ .from(schema.assetGenerationPresets)
36
+ .where(
37
+ inArray(schema.assetGenerationPresets.id, presetIds),
38
+ )) as PresetRow[];
39
+ if (!presets.length) return;
40
+
41
+ const libraryIds = Array.from(
42
+ new Set(presets.map((preset) => preset.libraryId)),
43
+ );
44
+ const libraries = (await db
45
+ .select()
46
+ .from(schema.assetLibraries)
47
+ .where(inArray(schema.assetLibraries.id, libraryIds))) as LibraryRow[];
48
+ const libraryById = new Map(
49
+ libraries.map((library) => [library.id, library]),
50
+ );
51
+
52
+ const blocks = presets.map((preset) =>
53
+ describePreset(preset, libraryById.get(preset.libraryId)),
54
+ );
55
+
56
+ const context = [
57
+ "<tagged-generation-presets>",
58
+ "The user tagged the generation preset(s) below. Before generating anything, study each preset's aesthetics and creative philosophy and let it drive your composition, mood, lighting, styling, and subject choices. Treat the preset as the creative brief, not just a set of output dimensions.",
59
+ "",
60
+ blocks.join("\n\n"),
61
+ "",
62
+ "When you call generate-image or generate-image-batch, pass the matching presetId so its saved format, model, tier, logo setting, and prompt template apply automatically — do not restate aspect ratio, size, model, or tier as ad-hoc args. Keep your own prompt focused on the specific subject the user asked for, expressed through the preset's philosophy above.",
63
+ "</tagged-generation-presets>",
64
+ ].join("\n");
65
+
66
+ return { message: `${args.message}\n\n${context}` };
67
+ }
68
+
69
+ function describePreset(preset: PresetRow, library?: LibraryRow): string {
70
+ const settings = parseJson<{ tier?: string; includeLogo?: boolean }>(
71
+ preset.settings,
72
+ {},
73
+ );
74
+ const style = library
75
+ ? parseJson<StyleBrief>(library.styleBrief, {})
76
+ : ({} as StyleBrief);
77
+
78
+ const lines = [
79
+ `Preset "${preset.title}" (id: ${preset.id})`,
80
+ library ? `- Brand kit: ${library.title}` : "",
81
+ preset.description ? `- Intent: ${preset.description}` : "",
82
+ preset.category ? `- Deliverable type: ${preset.category}` : "",
83
+ preset.promptTemplate
84
+ ? `- Prompt philosophy / template: ${preset.promptTemplate}`
85
+ : "",
86
+ preset.textPolicy ? `- Text policy: ${preset.textPolicy}` : "",
87
+ `- Output: ${preset.aspectRatio}, ${preset.imageSize}, model ${preset.model}${
88
+ settings.tier ? `, ${settings.tier} tier` : ""
89
+ }`,
90
+ settings.includeLogo === true
91
+ ? "- Brand logo: the library's canonical logo is composited onto the result; leave a clean upper-right area and do not draw a logo yourself."
92
+ : "",
93
+ ];
94
+
95
+ const aesthetics = [
96
+ style.description ? `overall: ${style.description}` : "",
97
+ style.mood ? `mood: ${style.mood}` : "",
98
+ style.palette?.length ? `palette: ${style.palette.join(", ")}` : "",
99
+ style.medium ? `medium: ${style.medium}` : "",
100
+ style.composition ? `composition: ${style.composition}` : "",
101
+ style.lighting ? `lighting: ${style.lighting}` : "",
102
+ style.texture ? `texture: ${style.texture}` : "",
103
+ style.subjectMatter ? `subject matter: ${style.subjectMatter}` : "",
104
+ style.typographyPolicy ? `typography: ${style.typographyPolicy}` : "",
105
+ ].filter(Boolean);
106
+ if (aesthetics.length) {
107
+ lines.push(`- Brand aesthetics: ${aesthetics.join("; ")}.`);
108
+ }
109
+ if (style.doNot?.length) {
110
+ lines.push(`- Avoid: ${style.doNot.join("; ")}.`);
111
+ }
112
+ const customInstructions = library?.customInstructions?.trim();
113
+ if (customInstructions) {
114
+ lines.push(`- Brand custom instructions: ${customInstructions}`);
115
+ }
116
+
117
+ return lines.filter(Boolean).join("\n");
118
+ }
@@ -8,6 +8,7 @@ import { and, asc, desc, inArray, isNull } from "drizzle-orm";
8
8
 
9
9
  import actionsRegistry from "../../.generated/actions-registry.js";
10
10
  import { getDb, schema } from "../db/index.js";
11
+ import { preparePresetChatContext } from "../lib/preset-chat-context.js";
11
12
  import "../register-secrets.js";
12
13
 
13
14
  const ASSETS_BACKGROUND_RUN_SOFT_TIMEOUT_MS = 13 * 60_000;
@@ -49,6 +50,10 @@ export default createAgentChatPlugin({
49
50
  initialToolNames: INITIAL_TOOL_NAMES,
50
51
  actions: loadActionsFromStaticRegistry(actionsRegistry),
51
52
  resolveOrgId: async (event) => (await getOrgContext(event)).orgId,
53
+ // When a user tags an @preset, embed its aesthetics/philosophy into the
54
+ // model-facing message so the agent internalizes the brief before generating.
55
+ prepareRequest: ({ message, references }) =>
56
+ preparePresetChatContext({ message, references }),
52
57
  mentionProviders: {
53
58
  mediaTypes: {
54
59
  label: "Media type",
@@ -237,6 +237,7 @@ export interface GenerationPresetSummary {
237
237
  model: ImageModel | VideoModel;
238
238
  textPolicy: string;
239
239
  referencePolicy: GenerationPresetReferencePolicy;
240
+ includeLogo: boolean;
240
241
  settings: Record<string, unknown>;
241
242
  sortOrder: number;
242
243
  createdAt?: string;
@@ -28,6 +28,7 @@ import {
28
28
  } from "@/components/ui/dropdown-menu";
29
29
  import { isDefaultTitle } from "@/hooks/use-auto-title";
30
30
  import type { RecordingSummary } from "@/hooks/use-library";
31
+ import { isStaleRecordingUpload } from "@/lib/recording-status";
31
32
  import { isStorageSetupFailureReason } from "@/lib/storage-failures";
32
33
  import { cn } from "@/lib/utils";
33
34
 
@@ -100,6 +101,12 @@ export function RecordingCard({
100
101
  const waitingForStorage = isStorageSetupFailureReason(
101
102
  recording.failureReason,
102
103
  );
104
+ const staleUpload = isStaleRecordingUpload(recording);
105
+ const displayFailed = recording.status === "failed" || staleUpload;
106
+ const failureReason = staleUpload
107
+ ? (recording.failureReason ??
108
+ t("recordingPage.processingStuck", { status: recording.status }))
109
+ : (recording.failureReason ?? t("clipsFinalRaw.removeFailedClip"));
103
110
  const nativeUploadPaused =
104
111
  recording.status === "failed" &&
105
112
  /native recording|native fullscreen|screencapture|avconvert/i.test(
@@ -221,11 +228,15 @@ export function RecordingCard({
221
228
  {/* Status pill for non-ready recordings */}
222
229
  {recording.status !== "ready" && (
223
230
  <div className="absolute top-2 end-2 rounded-full bg-black/80 px-2 py-0.5 text-[10px] font-medium text-white uppercase tracking-wide">
224
- {waitingForStorage ? "storage" : recording.status}
231
+ {waitingForStorage
232
+ ? "storage"
233
+ : staleUpload
234
+ ? "failed"
235
+ : recording.status}
225
236
  </div>
226
237
  )}
227
238
 
228
- {(recording.status === "failed" || waitingForStorage) && (
239
+ {(displayFailed || waitingForStorage) && (
229
240
  <div
230
241
  className={cn(
231
242
  "absolute inset-x-2 bottom-2 rounded-md border bg-background/95 p-2 text-start shadow-sm backdrop-blur",
@@ -252,8 +263,7 @@ export function RecordingCard({
252
263
  ? t("clipsFinalRaw.connectStorageToFinish")
253
264
  : nativeUploadPaused
254
265
  ? t("clipsFinalRaw.retryFromClipsMenu")
255
- : (recording.failureReason ??
256
- t("clipsFinalRaw.removeFailedClip"))}
266
+ : failureReason}
257
267
  </div>
258
268
  </div>
259
269
  {!waitingForStorage && (
@@ -1145,6 +1145,9 @@ export class RecorderEngine {
1145
1145
  // mid-state — the UI spinner is wired to engine state and would
1146
1146
  // hang forever otherwise.
1147
1147
  const e = err instanceof Error ? err : new Error(String(err));
1148
+ if (e.name !== "AbortError") {
1149
+ this.rememberUploadFailure(e);
1150
+ }
1148
1151
  this.transition("error", { message: e.message });
1149
1152
  throw e;
1150
1153
  } finally {
@@ -1183,6 +1186,9 @@ export class RecorderEngine {
1183
1186
  return this.toFinalizeResult(result, meta);
1184
1187
  } catch (err) {
1185
1188
  const e = err instanceof Error ? err : new Error(String(err));
1189
+ if (e.name !== "AbortError") {
1190
+ this.rememberUploadFailure(e);
1191
+ }
1186
1192
  this.transition("error", { message: e.message });
1187
1193
  throw e;
1188
1194
  } finally {
@@ -1595,26 +1601,18 @@ export class RecorderEngine {
1595
1601
  const failure = err instanceof Error ? err : new Error(String(err));
1596
1602
  // User-initiated cancel — cancel() already runs the abortUrl path.
1597
1603
  if (failure.name === "AbortError") return;
1598
- await this.markUploadFailed(failure);
1604
+ this.rememberUploadFailure(failure);
1599
1605
  this.emitError(failure);
1600
1606
  }
1601
1607
  });
1602
1608
  }
1603
1609
 
1604
- private async markUploadFailed(err: Error): Promise<void> {
1610
+ private rememberUploadFailure(err: Error): void {
1605
1611
  if (!this.uploadFailure) {
1606
1612
  this.uploadFailure = err;
1607
1613
  }
1608
- if (!this.opts.abortUrl) return;
1609
- try {
1610
- await fetch(this.opts.abortUrl, {
1611
- method: "POST",
1612
- headers: { "Content-Type": "application/json" },
1613
- body: JSON.stringify({ reason: err.message }),
1614
- });
1615
- } catch {
1616
- // ignore — the stop path will surface the original upload error.
1617
- }
1614
+ // Do not call abortUrl for retryable upload failures. retryUpload() reuses
1615
+ // this recording id; cancel() owns the terminal server-side abort path.
1618
1616
  }
1619
1617
 
1620
1618
  private async uploadBlobInSlices(
@@ -1,5 +1,7 @@
1
1
  import { useActionQuery, useActionMutation } from "@agent-native/core/client";
2
2
 
3
+ import { isLiveRecordingUpload } from "@/lib/recording-status";
4
+
3
5
  export interface RecordingSummary {
4
6
  id: string;
5
7
  title: string;
@@ -72,16 +74,17 @@ export function useRecordings(args: ListRecordingsArgs = {}) {
72
74
  recordings: Array.isArray(data?.recordings) ? data.recordings : [],
73
75
  };
74
76
  },
75
- // If any recording is still on a replaceable seed title, keep a 3s
76
- // refetch cadence so the skeleton in `recording-card` upgrades to
77
- // the real title promptly even if the refresh-signal poll is missed.
77
+ // Keep a short poll while uploads/processors are active so the library
78
+ // card does not get stuck if the global refresh signal is missed.
79
+ // Also poll for replaceable seed titles so the card upgrades promptly.
78
80
  refetchInterval: (q) => {
79
81
  const recs = (q.state.data as any)?.recordings as
80
82
  | RecordingSummary[]
81
83
  | undefined;
82
84
  if (!recs || recs.length === 0) return false;
85
+ const pendingUpload = recs.some((rec) => isLiveRecordingUpload(rec));
83
86
  const pendingTitle = recs.some(isAwaitingAutoTitle);
84
- return pendingTitle ? 3000 : false;
87
+ return pendingUpload || pendingTitle ? 3000 : false;
85
88
  },
86
89
  },
87
90
  );
@@ -0,0 +1,32 @@
1
+ export const STALE_RECORDING_UPLOAD_MS = 30 * 60 * 1000;
2
+
3
+ type RecordingStatusLike = {
4
+ status?: string | null;
5
+ updatedAt?: string | null;
6
+ };
7
+
8
+ export function isActiveRecordingUploadStatus(
9
+ status: string | null | undefined,
10
+ ): boolean {
11
+ return status === "uploading" || status === "processing";
12
+ }
13
+
14
+ export function isStaleRecordingUpload(
15
+ recording: RecordingStatusLike,
16
+ nowMs = Date.now(),
17
+ ): boolean {
18
+ if (!isActiveRecordingUploadStatus(recording.status)) return false;
19
+ const updatedAtMs = Date.parse(recording.updatedAt ?? "");
20
+ if (!Number.isFinite(updatedAtMs)) return false;
21
+ return nowMs - updatedAtMs >= STALE_RECORDING_UPLOAD_MS;
22
+ }
23
+
24
+ export function isLiveRecordingUpload(
25
+ recording: RecordingStatusLike,
26
+ nowMs = Date.now(),
27
+ ): boolean {
28
+ if (!isActiveRecordingUploadStatus(recording.status)) return false;
29
+ const updatedAtMs = Date.parse(recording.updatedAt ?? "");
30
+ if (!Number.isFinite(updatedAtMs)) return true;
31
+ return nowMs - updatedAtMs < STALE_RECORDING_UPLOAD_MS;
32
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-01
4
+ ---
5
+
6
+ Dictation keeps listening through natural pauses and gives clearer start feedback.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-01
4
+ ---
5
+
6
+ Redoing a paused desktop recording no longer leaves the recording controls disabled.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-01
4
+ ---
5
+
6
+ Stalled clip uploads now update in the library and surface as failed instead of staying stuck as uploading.