@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.
- package/bin/agent-native.js +25 -3
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +17 -0
- package/corpus/core/bin/agent-native.js +25 -3
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +18 -18
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +8 -2
- package/corpus/core/src/agent/engine/anthropic-engine.ts +11 -1
- package/corpus/core/src/agent/engine/index.ts +1 -0
- package/corpus/core/src/agent/engine/registry.ts +63 -2
- package/corpus/core/src/agent/production-agent.ts +22 -11
- package/corpus/core/src/agent/run-manager.ts +17 -6
- package/corpus/core/src/agent/run-store.ts +16 -11
- package/corpus/core/src/cli/create.ts +35 -0
- package/corpus/core/src/cli/design-connect.ts +263 -1
- package/corpus/core/src/cli/index.ts +84 -7
- package/corpus/core/src/cli/workspacify.ts +34 -0
- package/corpus/core/src/client/KeepTabOpenNotice.tsx +20 -15
- package/corpus/core/src/client/RunStuckBanner.tsx +14 -13
- package/corpus/core/src/client/agent-chat-adapter.ts +28 -17
- package/corpus/core/src/client/history/VersionHistoryPanel.tsx +77 -47
- package/corpus/core/src/client/history/index.ts +3 -0
- package/corpus/core/src/client/history/use-history.ts +28 -0
- package/corpus/core/src/client/index.ts +8 -0
- package/corpus/core/src/client/review/index.ts +5 -0
- package/corpus/core/src/client/review/use-review.ts +40 -2
- package/corpus/core/src/client/use-run-stuck-detection.ts +8 -5
- package/corpus/core/src/history/actions/create-resource-version.ts +11 -8
- package/corpus/core/src/history/index.ts +0 -1
- package/corpus/core/src/history/registry.ts +24 -12
- package/corpus/core/src/history/store.ts +84 -53
- package/corpus/core/src/review/actions/consume-review-feedback.ts +15 -5
- package/corpus/core/src/review/actions/delete-review-comment.ts +15 -3
- package/corpus/core/src/review/actions/resolve-review-thread.ts +12 -5
- package/corpus/core/src/review/index.ts +0 -5
- package/corpus/core/src/review/registry.ts +24 -12
- package/corpus/core/src/review/store.ts +10 -7
- package/corpus/core/src/server/agent-chat-plugin.ts +6 -0
- package/corpus/core/src/server/credential-provider.ts +13 -3
- package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +11 -11
- package/corpus/templates/assets/actions/generate-image.ts +64 -28
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +91 -74
- package/corpus/templates/assets/server/lib/generation.ts +29 -4
- package/corpus/templates/assets/server/lib/image-processing.ts +131 -0
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +24 -2
- package/corpus/templates/calendar/AGENTS.md +3 -0
- package/corpus/templates/calendar/actions/create-event.ts +8 -34
- package/corpus/templates/calendar/actions/event-action-helpers.ts +52 -0
- package/corpus/templates/calendar/actions/manage-event-draft.ts +7 -34
- package/corpus/templates/calendar/actions/update-event.ts +10 -23
- package/corpus/templates/calendar/app/components/calendar/AttendeeAutocomplete.tsx +47 -0
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +23 -0
- package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +59 -2
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +40 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +34 -1
- package/corpus/templates/calendar/app/hooks/use-events.ts +6 -0
- package/corpus/templates/calendar/app/i18n/zh-TW.ts +4 -0
- package/corpus/templates/calendar/app/i18n-data.ts +40 -0
- package/corpus/templates/calendar/changelog/2026-07-07-you-can-mark-event-guests-as-optional-while-inviting-them-or.md +6 -0
- package/corpus/templates/calendar/server/handlers/events.ts +1 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +4 -0
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/calendar/shared/api.ts +2 -0
- package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +29 -3
- package/corpus/templates/clips/actions/generate-workflow.ts +19 -8
- package/corpus/templates/clips/actions/get-clips-ai-prefs.ts +36 -0
- package/corpus/templates/clips/actions/lib/clips-ai-prefs.ts +28 -0
- package/corpus/templates/clips/actions/regenerate-chapters.ts +21 -7
- package/corpus/templates/clips/actions/regenerate-summary.ts +26 -8
- package/corpus/templates/clips/actions/regenerate-title.ts +44 -7
- package/corpus/templates/clips/actions/update-clips-ai-prefs.ts +50 -0
- package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +7 -2
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +25 -10
- package/corpus/templates/clips/app/i18n/ar-SA.ts +28 -6
- package/corpus/templates/clips/app/i18n/de-DE.ts +28 -6
- package/corpus/templates/clips/app/i18n/en-US.ts +29 -6
- package/corpus/templates/clips/app/i18n/es-ES.ts +29 -6
- package/corpus/templates/clips/app/i18n/fr-FR.ts +30 -6
- package/corpus/templates/clips/app/i18n/hi-IN.ts +26 -6
- package/corpus/templates/clips/app/i18n/ja-JP.ts +27 -6
- package/corpus/templates/clips/app/i18n/ko-KR.ts +28 -6
- package/corpus/templates/clips/app/i18n/pt-BR.ts +28 -6
- package/corpus/templates/clips/app/i18n/zh-CN.ts +26 -6
- package/corpus/templates/clips/app/i18n/zh-TW.ts +25 -6
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +73 -0
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -2
- package/corpus/templates/clips/changelog/2026-07-07-ai-tools-can-include-the-full-video-not-just-the-transcript-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-clips-desktop-now-surfaces-meetings-and-dictate-directly-in-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-meeting-notes-now-explain-exactly-how-to-start-granola-style.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-meeting-start-alerts-now-include-separate-start-notes-and-jo.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-recordings-start-more-predictably-and-newly-saved-clips-refr.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/desktop/src/app.tsx +435 -105
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +27 -21
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +23 -22
- package/corpus/templates/clips/desktop/src/styles.css +167 -2
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +3 -7
- package/corpus/templates/clips/desktop/src-tauri/src/tray_meetings.rs +5 -4
- package/corpus/templates/clips/server/routes/_agent-native/clips/user-prefs.put.ts +16 -8
- package/corpus/templates/clips/shared/clips-ai-prefs.ts +81 -0
- package/corpus/templates/content/AGENTS.md +1 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +73 -8
- package/corpus/templates/content/actions/_database-utils.ts +2 -1
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +20 -1
- package/corpus/templates/content/actions/stage-builder-source-bulk-update.ts +663 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +33 -7
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +53 -4
- package/corpus/templates/content/app/components/editor/database/settings.tsx +24 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +70 -1
- package/corpus/templates/content/app/hooks/use-document-properties.ts +63 -5
- package/corpus/templates/content/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/content/app/i18n-data.ts +31 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-rows-can-be-staged-in-bulk-with-a-reviewable.md +6 -0
- package/corpus/templates/content/changelog/2026-07-07-builder-backed-database-fields-now-edit-like-normal-content-fields.md +6 -0
- package/corpus/templates/content/parity/matrix.md +24 -24
- package/corpus/templates/content/parity/matrix.ts +2 -0
- package/corpus/templates/content/shared/api.ts +59 -4
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +16 -1
- package/corpus/templates/design/actions/add-localhost-screens.ts +1 -0
- package/corpus/templates/design/actions/connect-localhost.ts +26 -3
- package/corpus/templates/design/actions/open-visual-edit.ts +4 -37
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +362 -12
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +89 -14
- package/corpus/templates/design/app/components/design/bridge-security.ts +7 -1
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +1 -1
- package/corpus/templates/design/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +41 -0
- package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-keeps-bridge-access-when-opening-previousl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-now-keeps-the-live-app-connection-authenti.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-uses-live-app-iframes-for-click-to-edit-pr.md +6 -0
- package/dist/agent/durable-background.d.ts +12 -12
- package/dist/agent/durable-background.js +18 -18
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +8 -2
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +11 -1
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/engine/index.d.ts +1 -1
- package/dist/agent/engine/index.d.ts.map +1 -1
- package/dist/agent/engine/index.js +1 -1
- package/dist/agent/engine/index.js.map +1 -1
- package/dist/agent/engine/registry.d.ts +14 -0
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +56 -2
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/agent/production-agent.d.ts +2 -2
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +18 -11
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +7 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +6 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +15 -10
- package/dist/agent/run-store.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +31 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +197 -1
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/index.js +76 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/workspacify.d.ts.map +1 -1
- package/dist/cli/workspacify.js +30 -0
- package/dist/cli/workspacify.js.map +1 -1
- package/dist/client/KeepTabOpenNotice.d.ts +9 -10
- package/dist/client/KeepTabOpenNotice.d.ts.map +1 -1
- package/dist/client/KeepTabOpenNotice.js +8 -4
- package/dist/client/KeepTabOpenNotice.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +14 -13
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +25 -17
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/history/VersionHistoryPanel.d.ts +1 -1
- package/dist/client/history/VersionHistoryPanel.d.ts.map +1 -1
- package/dist/client/history/VersionHistoryPanel.js +12 -5
- package/dist/client/history/VersionHistoryPanel.js.map +1 -1
- package/dist/client/history/index.d.ts +1 -1
- package/dist/client/history/index.d.ts.map +1 -1
- package/dist/client/history/index.js +1 -1
- package/dist/client/history/index.js.map +1 -1
- package/dist/client/history/use-history.d.ts +12 -0
- package/dist/client/history/use-history.d.ts.map +1 -1
- package/dist/client/history/use-history.js +6 -0
- package/dist/client/history/use-history.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/review/index.d.ts +1 -1
- package/dist/client/review/index.d.ts.map +1 -1
- package/dist/client/review/index.js +1 -1
- package/dist/client/review/index.js.map +1 -1
- package/dist/client/review/use-review.d.ts +23 -0
- package/dist/client/review/use-review.d.ts.map +1 -1
- package/dist/client/review/use-review.js +8 -0
- package/dist/client/review/use-review.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts +1 -1
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +6 -4
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/history/actions/create-resource-version.js +7 -4
- package/dist/history/actions/create-resource-version.js.map +1 -1
- package/dist/history/index.d.ts +1 -1
- package/dist/history/index.d.ts.map +1 -1
- package/dist/history/index.js +1 -1
- package/dist/history/index.js.map +1 -1
- package/dist/history/registry.d.ts.map +1 -1
- package/dist/history/registry.js +14 -12
- package/dist/history/registry.js.map +1 -1
- package/dist/history/store.d.ts.map +1 -1
- package/dist/history/store.js +74 -49
- package/dist/history/store.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/review/actions/consume-review-feedback.d.ts +1 -0
- package/dist/review/actions/consume-review-feedback.js +8 -2
- package/dist/review/actions/consume-review-feedback.js.map +1 -1
- package/dist/review/actions/delete-review-comment.d.ts +2 -1
- package/dist/review/actions/delete-review-comment.js +11 -3
- package/dist/review/actions/delete-review-comment.js.map +1 -1
- package/dist/review/actions/resolve-review-thread.d.ts +2 -1
- package/dist/review/actions/resolve-review-thread.js +5 -2
- package/dist/review/actions/resolve-review-thread.js.map +1 -1
- package/dist/review/index.d.ts +1 -1
- package/dist/review/index.d.ts.map +1 -1
- package/dist/review/index.js +1 -1
- package/dist/review/index.js.map +1 -1
- package/dist/review/registry.d.ts.map +1 -1
- package/dist/review/registry.js +14 -12
- package/dist/review/registry.js.map +1 -1
- package/dist/review/store.d.ts +3 -3
- package/dist/review/store.d.ts.map +1 -1
- package/dist/review/store.js +7 -4
- package/dist/review/store.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +5 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/credential-provider.d.ts +2 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +13 -3
- package/dist/server/credential-provider.js.map +1 -1
- 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
|
|
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
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
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
|
|
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
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
skeletonLogoPlacement
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
122
|
-
|
|
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}`;
|