@agent-native/core 0.90.3 → 0.90.5

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 (256) hide show
  1. package/bin/agent-native.js +25 -3
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +17 -0
  4. package/corpus/core/bin/agent-native.js +25 -3
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/agent/durable-background.ts +18 -18
  7. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +8 -2
  8. package/corpus/core/src/agent/engine/anthropic-engine.ts +11 -1
  9. package/corpus/core/src/agent/engine/index.ts +1 -0
  10. package/corpus/core/src/agent/engine/registry.ts +63 -2
  11. package/corpus/core/src/agent/production-agent.ts +22 -11
  12. package/corpus/core/src/agent/run-manager.ts +17 -6
  13. package/corpus/core/src/agent/run-store.ts +16 -11
  14. package/corpus/core/src/cli/create.ts +35 -0
  15. package/corpus/core/src/cli/design-connect.ts +263 -1
  16. package/corpus/core/src/cli/index.ts +84 -7
  17. package/corpus/core/src/cli/workspacify.ts +34 -0
  18. package/corpus/core/src/client/KeepTabOpenNotice.tsx +20 -15
  19. package/corpus/core/src/client/RunStuckBanner.tsx +14 -13
  20. package/corpus/core/src/client/agent-chat-adapter.ts +28 -17
  21. package/corpus/core/src/client/history/VersionHistoryPanel.tsx +77 -47
  22. package/corpus/core/src/client/history/index.ts +3 -0
  23. package/corpus/core/src/client/history/use-history.ts +28 -0
  24. package/corpus/core/src/client/index.ts +8 -0
  25. package/corpus/core/src/client/review/index.ts +5 -0
  26. package/corpus/core/src/client/review/use-review.ts +40 -2
  27. package/corpus/core/src/client/use-run-stuck-detection.ts +8 -5
  28. package/corpus/core/src/history/actions/create-resource-version.ts +11 -8
  29. package/corpus/core/src/history/index.ts +0 -1
  30. package/corpus/core/src/history/registry.ts +24 -12
  31. package/corpus/core/src/history/store.ts +84 -53
  32. package/corpus/core/src/review/actions/consume-review-feedback.ts +15 -5
  33. package/corpus/core/src/review/actions/delete-review-comment.ts +15 -3
  34. package/corpus/core/src/review/actions/resolve-review-thread.ts +12 -5
  35. package/corpus/core/src/review/index.ts +0 -5
  36. package/corpus/core/src/review/registry.ts +24 -12
  37. package/corpus/core/src/review/store.ts +10 -7
  38. package/corpus/core/src/server/agent-chat-plugin.ts +6 -0
  39. package/corpus/core/src/server/credential-provider.ts +13 -3
  40. package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +11 -11
  41. package/corpus/templates/assets/actions/generate-image.ts +64 -28
  42. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +1 -1
  43. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +91 -74
  44. package/corpus/templates/assets/server/lib/generation.ts +29 -4
  45. package/corpus/templates/assets/server/lib/image-processing.ts +131 -0
  46. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +24 -2
  47. package/corpus/templates/calendar/AGENTS.md +3 -0
  48. package/corpus/templates/calendar/actions/create-event.ts +8 -34
  49. package/corpus/templates/calendar/actions/event-action-helpers.ts +52 -0
  50. package/corpus/templates/calendar/actions/manage-event-draft.ts +7 -34
  51. package/corpus/templates/calendar/actions/update-event.ts +10 -23
  52. package/corpus/templates/calendar/app/components/calendar/AttendeeAutocomplete.tsx +47 -0
  53. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +23 -0
  54. package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +59 -2
  55. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +40 -1
  56. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +34 -1
  57. package/corpus/templates/calendar/app/hooks/use-events.ts +6 -0
  58. package/corpus/templates/calendar/app/i18n/zh-TW.ts +4 -0
  59. package/corpus/templates/calendar/app/i18n-data.ts +40 -0
  60. package/corpus/templates/calendar/changelog/2026-07-07-you-can-mark-event-guests-as-optional-while-inviting-them-or.md +6 -0
  61. package/corpus/templates/calendar/server/handlers/events.ts +1 -0
  62. package/corpus/templates/calendar/server/lib/google-calendar.ts +4 -0
  63. package/corpus/templates/calendar/server/plugins/agent-chat.ts +1 -1
  64. package/corpus/templates/calendar/shared/api.ts +2 -0
  65. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +29 -3
  66. package/corpus/templates/clips/actions/generate-workflow.ts +19 -8
  67. package/corpus/templates/clips/actions/get-clips-ai-prefs.ts +36 -0
  68. package/corpus/templates/clips/actions/lib/clips-ai-prefs.ts +28 -0
  69. package/corpus/templates/clips/actions/regenerate-chapters.ts +21 -7
  70. package/corpus/templates/clips/actions/regenerate-summary.ts +26 -8
  71. package/corpus/templates/clips/actions/regenerate-title.ts +44 -7
  72. package/corpus/templates/clips/actions/update-clips-ai-prefs.ts +50 -0
  73. package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +7 -2
  74. package/corpus/templates/clips/app/hooks/use-auto-title.ts +25 -10
  75. package/corpus/templates/clips/app/i18n/ar-SA.ts +28 -6
  76. package/corpus/templates/clips/app/i18n/de-DE.ts +28 -6
  77. package/corpus/templates/clips/app/i18n/en-US.ts +29 -6
  78. package/corpus/templates/clips/app/i18n/es-ES.ts +29 -6
  79. package/corpus/templates/clips/app/i18n/fr-FR.ts +30 -6
  80. package/corpus/templates/clips/app/i18n/hi-IN.ts +26 -6
  81. package/corpus/templates/clips/app/i18n/ja-JP.ts +27 -6
  82. package/corpus/templates/clips/app/i18n/ko-KR.ts +28 -6
  83. package/corpus/templates/clips/app/i18n/pt-BR.ts +28 -6
  84. package/corpus/templates/clips/app/i18n/zh-CN.ts +26 -6
  85. package/corpus/templates/clips/app/i18n/zh-TW.ts +25 -6
  86. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +73 -0
  87. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -0
  88. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -2
  89. package/corpus/templates/clips/changelog/2026-07-07-ai-tools-can-include-the-full-video-not-just-the-transcript-.md +6 -0
  90. package/corpus/templates/clips/changelog/2026-07-07-clips-desktop-now-surfaces-meetings-and-dictate-directly-in-.md +6 -0
  91. package/corpus/templates/clips/changelog/2026-07-07-meeting-notes-now-explain-exactly-how-to-start-granola-style.md +6 -0
  92. package/corpus/templates/clips/changelog/2026-07-07-meeting-start-alerts-now-include-separate-start-notes-and-jo.md +6 -0
  93. package/corpus/templates/clips/changelog/2026-07-07-recordings-start-more-predictably-and-newly-saved-clips-refr.md +6 -0
  94. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  95. package/corpus/templates/clips/desktop/src/app.tsx +435 -105
  96. package/corpus/templates/clips/desktop/src/lib/recorder.ts +27 -21
  97. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +23 -22
  98. package/corpus/templates/clips/desktop/src/styles.css +167 -2
  99. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  100. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +3 -7
  101. package/corpus/templates/clips/desktop/src-tauri/src/tray_meetings.rs +5 -4
  102. package/corpus/templates/clips/server/routes/_agent-native/clips/user-prefs.put.ts +16 -8
  103. package/corpus/templates/clips/shared/clips-ai-prefs.ts +81 -0
  104. package/corpus/templates/content/AGENTS.md +1 -0
  105. package/corpus/templates/content/actions/_database-source-utils.ts +73 -8
  106. package/corpus/templates/content/actions/_database-utils.ts +2 -1
  107. package/corpus/templates/content/actions/prepare-builder-source-review.ts +20 -1
  108. package/corpus/templates/content/actions/stage-builder-source-bulk-update.ts +663 -0
  109. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +33 -7
  110. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +53 -4
  111. package/corpus/templates/content/app/components/editor/database/settings.tsx +24 -0
  112. package/corpus/templates/content/app/hooks/use-content-database.ts +70 -1
  113. package/corpus/templates/content/app/hooks/use-document-properties.ts +63 -5
  114. package/corpus/templates/content/app/i18n/zh-TW.ts +3 -0
  115. package/corpus/templates/content/app/i18n-data.ts +31 -0
  116. package/corpus/templates/content/changelog/2026-07-01-builder-source-rows-can-be-staged-in-bulk-with-a-reviewable.md +6 -0
  117. package/corpus/templates/content/changelog/2026-07-07-builder-backed-database-fields-now-edit-like-normal-content-fields.md +6 -0
  118. package/corpus/templates/content/parity/matrix.md +24 -24
  119. package/corpus/templates/content/parity/matrix.ts +2 -0
  120. package/corpus/templates/content/shared/api.ts +59 -4
  121. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +16 -1
  122. package/corpus/templates/design/actions/add-localhost-screens.ts +1 -0
  123. package/corpus/templates/design/actions/connect-localhost.ts +26 -3
  124. package/corpus/templates/design/actions/open-visual-edit.ts +4 -37
  125. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +362 -12
  126. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +89 -14
  127. package/corpus/templates/design/app/components/design/bridge-security.ts +7 -1
  128. package/corpus/templates/design/app/components/design/multi-screen/types.ts +1 -1
  129. package/corpus/templates/design/app/i18n/zh-TW.ts +3 -0
  130. package/corpus/templates/design/app/i18n-data.ts +41 -0
  131. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-keeps-bridge-access-when-opening-previousl.md +6 -0
  132. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-now-keeps-the-live-app-connection-authenti.md +6 -0
  133. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-uses-live-app-iframes-for-click-to-edit-pr.md +6 -0
  134. package/dist/agent/durable-background.d.ts +12 -12
  135. package/dist/agent/durable-background.js +18 -18
  136. package/dist/agent/durable-background.js.map +1 -1
  137. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  138. package/dist/agent/engine/ai-sdk-engine.js +8 -2
  139. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  140. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  141. package/dist/agent/engine/anthropic-engine.js +11 -1
  142. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  143. package/dist/agent/engine/index.d.ts +1 -1
  144. package/dist/agent/engine/index.d.ts.map +1 -1
  145. package/dist/agent/engine/index.js +1 -1
  146. package/dist/agent/engine/index.js.map +1 -1
  147. package/dist/agent/engine/registry.d.ts +14 -0
  148. package/dist/agent/engine/registry.d.ts.map +1 -1
  149. package/dist/agent/engine/registry.js +56 -2
  150. package/dist/agent/engine/registry.js.map +1 -1
  151. package/dist/agent/production-agent.d.ts +2 -2
  152. package/dist/agent/production-agent.d.ts.map +1 -1
  153. package/dist/agent/production-agent.js +18 -11
  154. package/dist/agent/production-agent.js.map +1 -1
  155. package/dist/agent/run-manager.d.ts +7 -1
  156. package/dist/agent/run-manager.d.ts.map +1 -1
  157. package/dist/agent/run-manager.js +6 -1
  158. package/dist/agent/run-manager.js.map +1 -1
  159. package/dist/agent/run-store.d.ts +1 -1
  160. package/dist/agent/run-store.d.ts.map +1 -1
  161. package/dist/agent/run-store.js +15 -10
  162. package/dist/agent/run-store.js.map +1 -1
  163. package/dist/cli/create.d.ts.map +1 -1
  164. package/dist/cli/create.js +31 -0
  165. package/dist/cli/create.js.map +1 -1
  166. package/dist/cli/design-connect.d.ts.map +1 -1
  167. package/dist/cli/design-connect.js +197 -1
  168. package/dist/cli/design-connect.js.map +1 -1
  169. package/dist/cli/index.js +76 -9
  170. package/dist/cli/index.js.map +1 -1
  171. package/dist/cli/workspacify.d.ts.map +1 -1
  172. package/dist/cli/workspacify.js +30 -0
  173. package/dist/cli/workspacify.js.map +1 -1
  174. package/dist/client/KeepTabOpenNotice.d.ts +9 -10
  175. package/dist/client/KeepTabOpenNotice.d.ts.map +1 -1
  176. package/dist/client/KeepTabOpenNotice.js +8 -4
  177. package/dist/client/KeepTabOpenNotice.js.map +1 -1
  178. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  179. package/dist/client/RunStuckBanner.js +14 -13
  180. package/dist/client/RunStuckBanner.js.map +1 -1
  181. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  182. package/dist/client/agent-chat-adapter.js +25 -17
  183. package/dist/client/agent-chat-adapter.js.map +1 -1
  184. package/dist/client/history/VersionHistoryPanel.d.ts +1 -1
  185. package/dist/client/history/VersionHistoryPanel.d.ts.map +1 -1
  186. package/dist/client/history/VersionHistoryPanel.js +12 -5
  187. package/dist/client/history/VersionHistoryPanel.js.map +1 -1
  188. package/dist/client/history/index.d.ts +1 -1
  189. package/dist/client/history/index.d.ts.map +1 -1
  190. package/dist/client/history/index.js +1 -1
  191. package/dist/client/history/index.js.map +1 -1
  192. package/dist/client/history/use-history.d.ts +12 -0
  193. package/dist/client/history/use-history.d.ts.map +1 -1
  194. package/dist/client/history/use-history.js +6 -0
  195. package/dist/client/history/use-history.js.map +1 -1
  196. package/dist/client/index.d.ts +2 -2
  197. package/dist/client/index.d.ts.map +1 -1
  198. package/dist/client/index.js +2 -2
  199. package/dist/client/index.js.map +1 -1
  200. package/dist/client/review/index.d.ts +1 -1
  201. package/dist/client/review/index.d.ts.map +1 -1
  202. package/dist/client/review/index.js +1 -1
  203. package/dist/client/review/index.js.map +1 -1
  204. package/dist/client/review/use-review.d.ts +23 -0
  205. package/dist/client/review/use-review.d.ts.map +1 -1
  206. package/dist/client/review/use-review.js +8 -0
  207. package/dist/client/review/use-review.js.map +1 -1
  208. package/dist/client/use-run-stuck-detection.d.ts +1 -1
  209. package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
  210. package/dist/client/use-run-stuck-detection.js +6 -4
  211. package/dist/client/use-run-stuck-detection.js.map +1 -1
  212. package/dist/collab/struct-routes.d.ts +1 -1
  213. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  214. package/dist/history/actions/create-resource-version.js +7 -4
  215. package/dist/history/actions/create-resource-version.js.map +1 -1
  216. package/dist/history/index.d.ts +1 -1
  217. package/dist/history/index.d.ts.map +1 -1
  218. package/dist/history/index.js +1 -1
  219. package/dist/history/index.js.map +1 -1
  220. package/dist/history/registry.d.ts.map +1 -1
  221. package/dist/history/registry.js +14 -12
  222. package/dist/history/registry.js.map +1 -1
  223. package/dist/history/store.d.ts.map +1 -1
  224. package/dist/history/store.js +74 -49
  225. package/dist/history/store.js.map +1 -1
  226. package/dist/notifications/routes.d.ts +1 -1
  227. package/dist/resources/handlers.d.ts +3 -3
  228. package/dist/review/actions/consume-review-feedback.d.ts +1 -0
  229. package/dist/review/actions/consume-review-feedback.js +8 -2
  230. package/dist/review/actions/consume-review-feedback.js.map +1 -1
  231. package/dist/review/actions/delete-review-comment.d.ts +2 -1
  232. package/dist/review/actions/delete-review-comment.js +11 -3
  233. package/dist/review/actions/delete-review-comment.js.map +1 -1
  234. package/dist/review/actions/resolve-review-thread.d.ts +2 -1
  235. package/dist/review/actions/resolve-review-thread.js +5 -2
  236. package/dist/review/actions/resolve-review-thread.js.map +1 -1
  237. package/dist/review/index.d.ts +1 -1
  238. package/dist/review/index.d.ts.map +1 -1
  239. package/dist/review/index.js +1 -1
  240. package/dist/review/index.js.map +1 -1
  241. package/dist/review/registry.d.ts.map +1 -1
  242. package/dist/review/registry.js +14 -12
  243. package/dist/review/registry.js.map +1 -1
  244. package/dist/review/store.d.ts +3 -3
  245. package/dist/review/store.d.ts.map +1 -1
  246. package/dist/review/store.js +7 -4
  247. package/dist/review/store.js.map +1 -1
  248. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  249. package/dist/server/agent-chat-plugin.js +5 -0
  250. package/dist/server/agent-chat-plugin.js.map +1 -1
  251. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  252. package/dist/server/credential-provider.d.ts +2 -0
  253. package/dist/server/credential-provider.d.ts.map +1 -1
  254. package/dist/server/credential-provider.js +13 -3
  255. package/dist/server/credential-provider.js.map +1 -1
  256. package/package.json +1 -1
@@ -107,6 +107,12 @@ const NO_COLLECTION = "__none__";
107
107
  const NO_SKELETON_BACKGROUND = "__none__";
108
108
  const NO_SKELETON_MASK = "__none__";
109
109
 
110
+ function usesSkeletonInpaintMode(
111
+ form: Pick<PresetFormState, "model" | "skeletonContentMode">,
112
+ ) {
113
+ return form.skeletonContentMode === "cutout" && form.model === "gpt-image-2";
114
+ }
115
+
110
116
  export function meta() {
111
117
  return [{ title: messagesByLocale["en-US"].routeTitles.generationPreset }];
112
118
  }
@@ -147,7 +153,7 @@ function buildSkeletonSpec(
147
153
  ...(previous?.foreground ?? []).filter(
148
154
  (layer) => layer.source !== "canonicalLogo",
149
155
  ),
150
- ...(form.skeletonLogo
156
+ ...(form.skeletonLogo && !usesSkeletonInpaintMode(form)
151
157
  ? [logoLayerFromPlacement(form.skeletonLogoPlacement)]
152
158
  : []),
153
159
  ];
@@ -957,7 +963,7 @@ export default function GenerationPresetEditorRoute() {
957
963
  </span>
958
964
  </span>
959
965
  </label>
960
- <div className="grid gap-4 rounded-md border border-border p-4">
966
+ <div className="grid min-w-0 gap-4 rounded-md border border-border p-4">
961
967
  <label
962
968
  htmlFor="preset-skeleton-enabled"
963
969
  className={cn("flex items-start gap-3", readOnly && "opacity-70")}
@@ -981,7 +987,7 @@ export default function GenerationPresetEditorRoute() {
981
987
  </span>
982
988
  </label>
983
989
  {form.skeletonEnabled ? (
984
- <div className="grid gap-4 sm:grid-cols-2">
990
+ <div className="grid min-w-0 gap-4 sm:grid-cols-2">
985
991
  <div className="grid gap-2">
986
992
  <FieldLabel>{t("brandKitDetail.contentMode")}</FieldLabel>
987
993
  <Select
@@ -1004,9 +1010,9 @@ export default function GenerationPresetEditorRoute() {
1004
1010
  </SelectContent>
1005
1011
  </Select>
1006
1012
  </div>
1007
- <div className="grid gap-3 sm:col-span-2">
1013
+ <div className="grid min-w-0 gap-3 sm:col-span-2">
1008
1014
  <FieldLabel>{t("brandKitDetail.skeletonImage")}</FieldLabel>
1009
- <div className="grid gap-3 sm:grid-cols-[minmax(0,1fr)_16rem]">
1015
+ <div className="grid min-w-0 gap-3 md:grid-cols-[minmax(12rem,1fr)_minmax(12rem,16rem)]">
1010
1016
  <button
1011
1017
  type="button"
1012
1018
  className={cn(
@@ -1029,7 +1035,7 @@ export default function GenerationPresetEditorRoute() {
1029
1035
  </span>
1030
1036
  )}
1031
1037
  </button>
1032
- <div className="grid content-start gap-2">
1038
+ <div className="grid min-w-0 content-start gap-2">
1033
1039
  <input
1034
1040
  ref={skeletonFileInputRef}
1035
1041
  type="file"
@@ -1043,7 +1049,7 @@ export default function GenerationPresetEditorRoute() {
1043
1049
  <Button
1044
1050
  type="button"
1045
1051
  variant="outline"
1046
- className="gap-2"
1052
+ className="w-full min-w-0 gap-2"
1047
1053
  disabled={readOnly || skeletonUploadPending}
1048
1054
  onClick={() => skeletonFileInputRef.current?.click()}
1049
1055
  >
@@ -1052,9 +1058,11 @@ export default function GenerationPresetEditorRoute() {
1052
1058
  ) : (
1053
1059
  <IconUpload className="h-4 w-4" />
1054
1060
  )}
1055
- {skeletonUploadPending
1056
- ? t("brandKitDetail.uploadingSkeletonImage")
1057
- : t("brandKitDetail.uploadSkeletonImage")}
1061
+ <span className="min-w-0 truncate">
1062
+ {skeletonUploadPending
1063
+ ? t("brandKitDetail.uploadingSkeletonImage")
1064
+ : t("brandKitDetail.uploadSkeletonImage")}
1065
+ </span>
1058
1066
  </Button>
1059
1067
  <Select
1060
1068
  value={
@@ -1064,7 +1072,7 @@ export default function GenerationPresetEditorRoute() {
1064
1072
  disabled={readOnly || !skeletonBackgroundAssets.length}
1065
1073
  onValueChange={updateSkeletonBackgroundAsset}
1066
1074
  >
1067
- <SelectTrigger>
1075
+ <SelectTrigger className="min-w-0">
1068
1076
  <SelectValue
1069
1077
  placeholder={t(
1070
1078
  "brandKitDetail.chooseSkeletonImage",
@@ -1090,9 +1098,9 @@ export default function GenerationPresetEditorRoute() {
1090
1098
  </div>
1091
1099
  {form.skeletonContentMode === "cutout" &&
1092
1100
  form.model === "gpt-image-2" ? (
1093
- <div className="grid gap-3 sm:col-span-2">
1101
+ <div className="grid min-w-0 gap-3 sm:col-span-2">
1094
1102
  <FieldLabel>{t("brandKitDetail.skeletonMask")}</FieldLabel>
1095
- <div className="grid gap-3 sm:grid-cols-[minmax(0,1fr)_16rem]">
1103
+ <div className="grid min-w-0 gap-3 md:grid-cols-[minmax(12rem,1fr)_minmax(12rem,16rem)]">
1096
1104
  <button
1097
1105
  type="button"
1098
1106
  className={cn(
@@ -1119,7 +1127,7 @@ export default function GenerationPresetEditorRoute() {
1119
1127
  </span>
1120
1128
  )}
1121
1129
  </button>
1122
- <div className="grid content-start gap-2">
1130
+ <div className="grid min-w-0 content-start gap-2">
1123
1131
  <input
1124
1132
  ref={skeletonMaskFileInputRef}
1125
1133
  type="file"
@@ -1133,7 +1141,7 @@ export default function GenerationPresetEditorRoute() {
1133
1141
  <Button
1134
1142
  type="button"
1135
1143
  variant="outline"
1136
- className="gap-2"
1144
+ className="w-full min-w-0 gap-2"
1137
1145
  disabled={readOnly || skeletonMaskUploadPending}
1138
1146
  onClick={() =>
1139
1147
  skeletonMaskFileInputRef.current?.click()
@@ -1144,9 +1152,11 @@ export default function GenerationPresetEditorRoute() {
1144
1152
  ) : (
1145
1153
  <IconUpload className="h-4 w-4" />
1146
1154
  )}
1147
- {skeletonMaskUploadPending
1148
- ? t("brandKitDetail.uploadingSkeletonMask")
1149
- : t("brandKitDetail.uploadSkeletonMask")}
1155
+ <span className="min-w-0 truncate">
1156
+ {skeletonMaskUploadPending
1157
+ ? t("brandKitDetail.uploadingSkeletonMask")
1158
+ : t("brandKitDetail.uploadSkeletonMask")}
1159
+ </span>
1150
1160
  </Button>
1151
1161
  <Select
1152
1162
  value={form.skeletonMaskAssetId || NO_SKELETON_MASK}
@@ -1155,7 +1165,7 @@ export default function GenerationPresetEditorRoute() {
1155
1165
  }
1156
1166
  onValueChange={updateSkeletonMaskAsset}
1157
1167
  >
1158
- <SelectTrigger>
1168
+ <SelectTrigger className="min-w-0">
1159
1169
  <SelectValue
1160
1170
  placeholder={t(
1161
1171
  "brandKitDetail.chooseSkeletonMask",
@@ -1207,61 +1217,68 @@ export default function GenerationPresetEditorRoute() {
1207
1217
  </span>
1208
1218
  </label>
1209
1219
  ) : null}
1210
- <label
1211
- htmlFor="preset-skeleton-logo"
1212
- className={cn(
1213
- "flex items-start gap-3 rounded-md border border-border p-3",
1214
- readOnly && "opacity-70",
1215
- )}
1216
- >
1217
- <Checkbox
1218
- id="preset-skeleton-logo"
1219
- checked={form.skeletonLogo}
1220
- disabled={readOnly || !library?.canonicalLogoAssetId}
1221
- onCheckedChange={(checked) =>
1222
- updateForm({ skeletonLogo: checked === true })
1223
- }
1224
- className="mt-0.5"
1225
- />
1226
- <span className="grid gap-1">
1227
- <span className="text-sm font-medium leading-none">
1228
- {t("brandKitDetail.placeLogoInSkeleton")}
1229
- </span>
1230
- <span className="text-xs text-muted-foreground">
1231
- {t("brandKitDetail.placeLogoInSkeletonHint")}
1232
- </span>
1233
- </span>
1234
- </label>
1235
- <div className="grid gap-2">
1236
- <FieldLabel>{t("brandKitDetail.logoPlacement")}</FieldLabel>
1237
- <Select
1238
- value={form.skeletonLogoPlacement}
1239
- disabled={readOnly || !form.skeletonLogo}
1240
- onValueChange={(value) =>
1241
- updateForm({
1242
- skeletonLogoPlacement: value as SkeletonLogoPlacement,
1243
- })
1244
- }
1245
- >
1246
- <SelectTrigger>
1247
- <SelectValue />
1248
- </SelectTrigger>
1249
- <SelectContent>
1250
- <SelectItem value="upper-right">
1251
- {t("brandKitDetail.upperRight")}
1252
- </SelectItem>
1253
- <SelectItem value="upper-left">
1254
- {t("brandKitDetail.upperLeft")}
1255
- </SelectItem>
1256
- <SelectItem value="lower-right">
1257
- {t("brandKitDetail.lowerRight")}
1258
- </SelectItem>
1259
- <SelectItem value="lower-left">
1260
- {t("brandKitDetail.lowerLeft")}
1261
- </SelectItem>
1262
- </SelectContent>
1263
- </Select>
1264
- </div>
1220
+ {!usesSkeletonInpaintMode(form) ? (
1221
+ <>
1222
+ <label
1223
+ htmlFor="preset-skeleton-logo"
1224
+ className={cn(
1225
+ "flex items-start gap-3 rounded-md border border-border p-3",
1226
+ readOnly && "opacity-70",
1227
+ )}
1228
+ >
1229
+ <Checkbox
1230
+ id="preset-skeleton-logo"
1231
+ checked={form.skeletonLogo}
1232
+ disabled={readOnly || !library?.canonicalLogoAssetId}
1233
+ onCheckedChange={(checked) =>
1234
+ updateForm({ skeletonLogo: checked === true })
1235
+ }
1236
+ className="mt-0.5"
1237
+ />
1238
+ <span className="grid gap-1">
1239
+ <span className="text-sm font-medium leading-none">
1240
+ {t("brandKitDetail.placeLogoInSkeleton")}
1241
+ </span>
1242
+ <span className="text-xs text-muted-foreground">
1243
+ {t("brandKitDetail.placeLogoInSkeletonHint")}
1244
+ </span>
1245
+ </span>
1246
+ </label>
1247
+ <div className="grid gap-2">
1248
+ <FieldLabel>
1249
+ {t("brandKitDetail.logoPlacement")}
1250
+ </FieldLabel>
1251
+ <Select
1252
+ value={form.skeletonLogoPlacement}
1253
+ disabled={readOnly || !form.skeletonLogo}
1254
+ onValueChange={(value) =>
1255
+ updateForm({
1256
+ skeletonLogoPlacement:
1257
+ value as SkeletonLogoPlacement,
1258
+ })
1259
+ }
1260
+ >
1261
+ <SelectTrigger>
1262
+ <SelectValue />
1263
+ </SelectTrigger>
1264
+ <SelectContent>
1265
+ <SelectItem value="upper-right">
1266
+ {t("brandKitDetail.upperRight")}
1267
+ </SelectItem>
1268
+ <SelectItem value="upper-left">
1269
+ {t("brandKitDetail.upperLeft")}
1270
+ </SelectItem>
1271
+ <SelectItem value="lower-right">
1272
+ {t("brandKitDetail.lowerRight")}
1273
+ </SelectItem>
1274
+ <SelectItem value="lower-left">
1275
+ {t("brandKitDetail.lowerLeft")}
1276
+ </SelectItem>
1277
+ </SelectContent>
1278
+ </Select>
1279
+ </div>
1280
+ </>
1281
+ ) : null}
1265
1282
  </div>
1266
1283
  ) : null}
1267
1284
  </div>
@@ -642,7 +642,11 @@ export async function generateWithManagedImageProvider(
642
642
  const shouldFallback =
643
643
  err instanceof BuilderImageGenerationError &&
644
644
  [401, 402, 403, 429, 503, 504].includes(err.status ?? 0);
645
- if (shouldFallback && (await isManualImageGenerationConfigured())) {
645
+ if (
646
+ shouldFallback &&
647
+ input.mode !== "edit" &&
648
+ (await isManualImageGenerationConfigured())
649
+ ) {
646
650
  logGeneration("builder.fallback_to_manual", {
647
651
  model: input.model,
648
652
  status:
@@ -652,7 +656,7 @@ export async function generateWithManagedImageProvider(
652
656
  return generateWithManualImageProvider(input);
653
657
  }
654
658
  if (shouldFallback && err instanceof BuilderImageGenerationError) {
655
- throw createBuilderImageGenerationFallbackError(err);
659
+ throw createBuilderImageGenerationFallbackError(err, input);
656
660
  }
657
661
  throw err;
658
662
  }
@@ -660,12 +664,15 @@ export async function generateWithManagedImageProvider(
660
664
 
661
665
  function createBuilderImageGenerationFallbackError(
662
666
  err: BuilderImageGenerationError,
667
+ input?: GenerateProviderInput,
663
668
  ): Error {
664
- const message = builderImageGenerationFallbackMessage(err);
669
+ const message = builderImageGenerationFallbackMessage(err, input);
665
670
  if ([401, 402, 403].includes(err.status ?? 0)) {
666
671
  return new FeatureNotConfiguredError({
667
672
  requiredCredential:
668
- err.status === 401 ? "BUILDER_PRIVATE_KEY" : "GEMINI_API_KEY",
673
+ input?.mode === "edit" || err.status === 401
674
+ ? "BUILDER_PRIVATE_KEY"
675
+ : "GEMINI_API_KEY",
669
676
  builderConnectUrl: "/_agent-native/builder/connect",
670
677
  byokDocsUrl: "https://aistudio.google.com/apikey",
671
678
  message,
@@ -676,8 +683,26 @@ function createBuilderImageGenerationFallbackError(
676
683
 
677
684
  function builderImageGenerationFallbackMessage(
678
685
  err: BuilderImageGenerationError,
686
+ input?: GenerateProviderInput,
679
687
  ): string {
680
688
  const detail = err.detail ? `: ${err.detail}` : ".";
689
+ if (input?.mode === "edit") {
690
+ switch (err.status) {
691
+ case 401:
692
+ return `Masked skeleton inpainting needs Builder.io connected or reconnected${err.detail ? ` (${err.detail})` : ""}. Open Settings and click Connect Builder.io; manual OpenAI or Gemini fallback cannot pass image-edit masks.`;
693
+ case 402:
694
+ return `Builder.io is connected, but this Builder space cannot use managed image generation credits${detail} Masked skeleton inpainting cannot use manual OpenAI or Gemini fallback because it must pass an image-edit mask.`;
695
+ case 403:
696
+ return `Builder.io is connected, but this Builder space does not have access to managed image generation${detail} Ask a space admin to enable access or reconnect to a different Builder space; manual fallback cannot pass image-edit masks.`;
697
+ case 429:
698
+ return `Builder-managed masked inpainting is rate limited right now${detail} Retry shortly; manual OpenAI or Gemini fallback cannot pass image-edit masks.`;
699
+ case 503:
700
+ case 504:
701
+ return `Builder-managed masked inpainting is temporarily unavailable${detail} Retry shortly; manual OpenAI or Gemini fallback cannot pass image-edit masks.`;
702
+ default:
703
+ return `Builder-managed masked inpainting failed${detail} Retry with Builder-managed image generation; manual OpenAI or Gemini fallback cannot pass image-edit masks.`;
704
+ }
705
+ }
681
706
  switch (err.status) {
682
707
  case 401:
683
708
  return `Image generation needs Builder.io connected or reconnected${err.detail ? ` (${err.detail})` : ""}. Open Settings and click Connect Builder.io, or expand the Asset generation setup step and add an OpenAI or Gemini API key as the manual fallback.`;
@@ -130,6 +130,137 @@ export async function maskFromManualMaskAlpha(input: {
130
130
  });
131
131
  }
132
132
 
133
+ const GPT_IMAGE_2_EDIT_SIZE_MULTIPLE = 16;
134
+ const GPT_IMAGE_2_EDIT_MIN_PIXELS = 655_360;
135
+ const GPT_IMAGE_2_EDIT_MAX_PIXELS = 8_294_400;
136
+ const GPT_IMAGE_2_EDIT_MAX_SIDE = 3840;
137
+ const GPT_IMAGE_2_EDIT_MAX_RATIO = 3;
138
+
139
+ export async function prepareGptImage2SkeletonInpaintImages(input: {
140
+ plate: Buffer;
141
+ mask: Buffer;
142
+ }): Promise<{
143
+ plate: Buffer;
144
+ mask: Buffer;
145
+ size: { width: number; height: number };
146
+ resized: boolean;
147
+ }> {
148
+ const metadata = await sharp(input.plate, { failOn: "none" }).metadata();
149
+ const width = metadata.width ?? 0;
150
+ const height = metadata.height ?? 0;
151
+ if (!width || !height) {
152
+ throw new Error(
153
+ "Skeleton inpainting background plate dimensions are invalid.",
154
+ );
155
+ }
156
+ const size = gptImage2EditSizeForPlate({ width, height });
157
+ const resized = size.width !== width || size.height !== height;
158
+ if (!resized) {
159
+ return { plate: input.plate, mask: input.mask, size, resized };
160
+ }
161
+ return {
162
+ plate: await sharp(input.plate, { failOn: "none" })
163
+ .rotate()
164
+ .resize({ width: size.width, height: size.height, fit: "fill" })
165
+ .png()
166
+ .toBuffer(),
167
+ mask: await sharp(input.mask, { failOn: "none" })
168
+ .resize({ width: size.width, height: size.height, fit: "fill" })
169
+ .png()
170
+ .toBuffer(),
171
+ size,
172
+ resized,
173
+ };
174
+ }
175
+
176
+ function gptImage2EditSizeForPlate(input: { width: number; height: number }): {
177
+ width: number;
178
+ height: number;
179
+ } {
180
+ if (isValidGptImage2EditSize(input)) return input;
181
+ const ratio =
182
+ Math.max(input.width, input.height) / Math.min(input.width, input.height);
183
+ if (ratio > GPT_IMAGE_2_EDIT_MAX_RATIO) {
184
+ throw new Error(
185
+ "gpt-image-2 skeleton inpainting requires a background plate aspect ratio no wider or taller than 3:1.",
186
+ );
187
+ }
188
+
189
+ const pixels = input.width * input.height;
190
+ let scale = 1;
191
+ if (pixels < GPT_IMAGE_2_EDIT_MIN_PIXELS) {
192
+ scale = Math.sqrt(GPT_IMAGE_2_EDIT_MIN_PIXELS / pixels);
193
+ } else if (pixels > GPT_IMAGE_2_EDIT_MAX_PIXELS) {
194
+ scale = Math.sqrt(GPT_IMAGE_2_EDIT_MAX_PIXELS / pixels);
195
+ }
196
+ scale = Math.min(
197
+ scale,
198
+ GPT_IMAGE_2_EDIT_MAX_SIDE / Math.max(input.width, input.height),
199
+ );
200
+
201
+ for (let attempt = 0; attempt < 200; attempt += 1) {
202
+ const candidate = {
203
+ width: roundToMultiple(
204
+ input.width * scale,
205
+ GPT_IMAGE_2_EDIT_SIZE_MULTIPLE,
206
+ ),
207
+ height: roundToMultiple(
208
+ input.height * scale,
209
+ GPT_IMAGE_2_EDIT_SIZE_MULTIPLE,
210
+ ),
211
+ };
212
+ if (isValidGptImage2EditSize(candidate)) return candidate;
213
+ const candidatePixels = candidate.width * candidate.height;
214
+ if (
215
+ candidatePixels < GPT_IMAGE_2_EDIT_MIN_PIXELS ||
216
+ candidate.width % GPT_IMAGE_2_EDIT_SIZE_MULTIPLE !== 0 ||
217
+ candidate.height % GPT_IMAGE_2_EDIT_SIZE_MULTIPLE !== 0
218
+ ) {
219
+ scale *= 1.01;
220
+ } else {
221
+ scale *= 0.99;
222
+ }
223
+ }
224
+
225
+ throw new Error(
226
+ "Unable to prepare a valid gpt-image-2 skeleton inpainting size.",
227
+ );
228
+ }
229
+
230
+ function isValidGptImage2EditSize(input: {
231
+ width: number;
232
+ height: number;
233
+ }): boolean {
234
+ if (!Number.isInteger(input.width) || !Number.isInteger(input.height)) {
235
+ return false;
236
+ }
237
+ if (input.width <= 0 || input.height <= 0) return false;
238
+ if (Math.max(input.width, input.height) > GPT_IMAGE_2_EDIT_MAX_SIDE) {
239
+ return false;
240
+ }
241
+ if (
242
+ input.width % GPT_IMAGE_2_EDIT_SIZE_MULTIPLE !== 0 ||
243
+ input.height % GPT_IMAGE_2_EDIT_SIZE_MULTIPLE !== 0
244
+ ) {
245
+ return false;
246
+ }
247
+ if (
248
+ Math.max(input.width, input.height) / Math.min(input.width, input.height) >
249
+ GPT_IMAGE_2_EDIT_MAX_RATIO
250
+ ) {
251
+ return false;
252
+ }
253
+ const pixels = input.width * input.height;
254
+ return (
255
+ pixels >= GPT_IMAGE_2_EDIT_MIN_PIXELS &&
256
+ pixels <= GPT_IMAGE_2_EDIT_MAX_PIXELS
257
+ );
258
+ }
259
+
260
+ function roundToMultiple(value: number, multiple: number): number {
261
+ return Math.max(multiple, Math.round(value / multiple) * multiple);
262
+ }
263
+
133
264
  async function maskFromAlphaChannel(input: {
134
265
  image: Buffer;
135
266
  expectedSize?: { width: number; height: number };
@@ -125,6 +125,16 @@ Use only one generated video provider per event: `--addGoogleMeet=true` or `--ad
125
125
 
126
126
  `--attendees` accepts a comma- or space-separated list of email addresses. When attendees are provided, Google sends email invitations automatically (`sendUpdates=all`). Use `--sendUpdates=none` to suppress emails.
127
127
 
128
+ To mark a guest optional, pass attendees as a JSON array with `optional: true`:
129
+
130
+ ```bash
131
+ pnpm action create-event \
132
+ --title "Q2 planning" \
133
+ --start 2026-04-03T14:00:00 \
134
+ --end 2026-04-03T15:00:00 \
135
+ --attendees '[{"email":"alice@example.com"},{"email":"bob@example.com","optional":true}]'
136
+ ```
137
+
128
138
  Use `--startTimeZone` / `--endTimeZone` with IANA timezone names when the event should be anchored to a specific timezone, e.g. `--startTimeZone America/Los_Angeles`.
129
139
 
130
140
  Use `--reminders '[{"method":"popup","minutes":10},{"method":"email","minutes":1440}]'` for multiple alerts. Use `--remindersUseDefault false --reminders '[]'` for no alerts.
@@ -174,6 +184,17 @@ pnpm action update-event \
174
184
  --id google-event-id \
175
185
  --attendees "alice@example.com,bob@example.com,carol@example.com"
176
186
 
187
+ # Prefer addAttendees when inviting more people so existing RSVP metadata is preserved
188
+ pnpm action update-event \
189
+ --id google-event-id \
190
+ --addAttendees '[{"email":"dana@example.com","optional":true}]'
191
+
192
+ # Mark an existing guest optional without resetting RSVPs — fetch via get-event,
193
+ # then pass the full attendees list with optional:true on that guest
194
+ pnpm action update-event \
195
+ --id google-event-id \
196
+ --attendees '[{"email":"alice@example.com"},{"email":"bob@example.com","optional":true}]'
197
+
177
198
  # Suppress invitation emails
178
199
  pnpm action update-event --id google-event-id --attendees "alice@example.com" --sendUpdates none
179
200
 
@@ -189,7 +210,7 @@ pnpm action update-event \
189
210
  --attachments '[{"fileUrl":"https://drive.google.com/...","title":"Agenda"}]'
190
211
  ```
191
212
 
192
- `--attendees` REPLACES the entire attendee list — to add someone, fetch the existing attendees first via `get-event` and pass the merged list. Pass an empty string to clear all attendees.
213
+ `--attendees` REPLACES the entire attendee list — to add someone, prefer `addAttendees` so existing RSVP notes/statuses are preserved. To change whether a guest is optional or required after the fact, fetch the current list via `get-event` and pass the full `attendees` array with `optional: true` or omit/false for required. Pass an empty string to clear all attendees.
193
214
 
194
215
  For "add Zoom to this meeting", fetch or use the visible event id and call `update-event --addZoom=true`. Do not create an extension for Zoom; Zoom is a first-party calendar integration handled by the event actions and the Settings page.
195
216
 
@@ -246,7 +267,8 @@ Events require a connected Google Calendar account. Check with `GET /_agent-nati
246
267
  "location": "Conference Room A",
247
268
  "allDay": false,
248
269
  "attendees": [
249
- { "email": "alice@example.com", "displayName": "Alice", "responseStatus": "accepted" }
270
+ { "email": "alice@example.com", "displayName": "Alice", "responseStatus": "accepted" },
271
+ { "email": "bob@example.com", "displayName": "Bob", "responseStatus": "needsAction", "optional": true }
250
272
  ],
251
273
  "conferenceData": { ... },
252
274
  "hangoutLink": "https://meet.google.com/...",
@@ -50,6 +50,9 @@ Detailed event, availability, booking, storage, and UI rules live in
50
50
  `addAttendees` so existing RSVP notes/statuses are preserved. Use
51
51
  `scope: "all"` only when the user wants a recurring-event guest change applied
52
52
  to the whole series.
53
+ - Pass `optional: true` on an attendee object to mark someone optional when
54
+ creating, drafting, or adding guests. To change optional/required after the
55
+ fact, replace the full `attendees` list with `optional` set on that guest.
53
56
 
54
57
  ## Application State
55
58
 
@@ -13,11 +13,13 @@ import type { CalendarEvent } from "../shared/api.js";
13
13
  import {
14
14
  availabilityInput,
15
15
  attachmentsInput,
16
+ attendeesInput,
16
17
  buildReminderOverrides,
17
18
  buildStatusEventFields,
18
19
  cliBoolean,
19
20
  eventTypeInput,
20
21
  googleColorIdInput,
22
+ normalizeAttendees,
21
23
  reminderMethodInput,
22
24
  reminderMinutesInput,
23
25
  remindersInput,
@@ -25,36 +27,6 @@ import {
25
27
  workingLocationTypeInput,
26
28
  } from "./event-action-helpers.js";
27
29
 
28
- // Accept attendees as either an array of {email, displayName?} objects (when
29
- // invoked via JSON) or a comma/whitespace-separated string of emails (when
30
- // invoked from the CLI as `--attendees alice@x.com,bob@y.com`).
31
- const attendeesInput = z
32
- .union([
33
- z.array(
34
- z.object({
35
- email: z.string(),
36
- displayName: z.string().optional(),
37
- }),
38
- ),
39
- z.string(),
40
- ])
41
- .optional();
42
-
43
- function normalizeAttendees(
44
- input: z.infer<typeof attendeesInput>,
45
- ): Array<{ email: string; displayName?: string }> | undefined {
46
- if (!input) return undefined;
47
- if (typeof input === "string") {
48
- const emails = input
49
- .split(/[\s,;]+/)
50
- .map((s) => s.trim())
51
- .filter((s) => s.length > 0 && s.includes("@"));
52
- if (emails.length === 0) return undefined;
53
- return emails.map((email) => ({ email }));
54
- }
55
- return input.filter((a) => a.email && a.email.includes("@"));
56
- }
57
-
58
30
  export default defineAction({
59
31
  description: "Create a calendar event on Google Calendar",
60
32
  schema: z.object({
@@ -118,9 +90,11 @@ export default defineAction({
118
90
  .describe(
119
91
  "Create and attach a Zoom meeting link to the event. Requires Zoom to be connected in Settings.",
120
92
  ),
121
- attendees: attendeesInput.describe(
122
- "Invitees — either an array of {email, displayName?} or a comma-separated string of emails",
123
- ),
93
+ attendees: attendeesInput
94
+ .optional()
95
+ .describe(
96
+ "Invitees — either an array of {email, displayName?, optional?} or a comma-separated string of emails. Set optional:true to mark a guest optional.",
97
+ ),
124
98
  sendUpdates: z
125
99
  .enum(["all", "externalOnly", "none"])
126
100
  .optional()
@@ -214,7 +188,7 @@ export default defineAction({
214
188
 
215
189
  const result = await googleCalendar.createEvent(calEvent, {
216
190
  addGoogleMeet: args.addGoogleMeet,
217
- sendUpdates: args.sendUpdates ?? (attendees ? "all" : undefined),
191
+ sendUpdates: args.sendUpdates ?? (attendees?.length ? "all" : undefined),
218
192
  });
219
193
  if (result.id) {
220
194
  calEvent.id = `google-${result.id}`;