@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +48 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +30 -0
- package/corpus/core/src/agent/production-agent.ts +34 -6
- package/corpus/core/src/agent/run-manager.ts +45 -2
- package/corpus/core/src/agent/run-store.ts +29 -6
- package/corpus/core/src/chat-threads/store.ts +2 -0
- package/corpus/core/src/cli/skills.ts +10 -6
- package/corpus/core/src/client/AssistantChat.tsx +40 -9
- package/corpus/core/src/client/agent-chat-adapter.ts +23 -2
- package/corpus/core/src/client/chat/run-recovery.tsx +52 -44
- package/corpus/core/src/client/chat/tool-call-display.tsx +7 -2
- package/corpus/core/src/client/require-session.tsx +20 -1
- package/corpus/core/src/client/sharing/ShareButton.tsx +9 -3
- package/corpus/core/src/collab/storage.ts +40 -5
- package/corpus/core/src/server/agent-chat-plugin.ts +1 -0
- package/corpus/core/src/server/auth.ts +6 -0
- package/corpus/core/src/shared/streaming-text-smoothing.ts +10 -5
- package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +7 -6
- package/corpus/templates/assets/AGENTS.md +15 -0
- package/corpus/templates/assets/actions/_helpers.ts +3 -1
- package/corpus/templates/assets/actions/create-generation-preset.ts +12 -1
- package/corpus/templates/assets/actions/generate-asset.ts +5 -1
- package/corpus/templates/assets/actions/generate-image-batch.ts +6 -1
- package/corpus/templates/assets/actions/generate-image.ts +18 -11
- package/corpus/templates/assets/actions/open-asset-picker.ts +4 -2
- package/corpus/templates/assets/actions/update-generation-preset.ts +16 -3
- package/corpus/templates/assets/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/assets/app/i18n-data.ts +30 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +42 -7
- package/corpus/templates/assets/app/routes/library.tsx +4 -2
- package/corpus/templates/assets/changelog/2026-07-01-generation-presets-can-now-composite-your-brand-s-canonical-.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-01-tagging-a-preset-now-briefs-the-agent-on-that-preset-s-aesth.md +6 -0
- package/corpus/templates/assets/server/lib/preset-chat-context.ts +118 -0
- package/corpus/templates/assets/server/plugins/agent-chat.ts +5 -0
- package/corpus/templates/assets/shared/api.ts +1 -0
- package/corpus/templates/clips/app/components/library/recording-card.tsx +14 -4
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +10 -12
- package/corpus/templates/clips/app/hooks/use-library.ts +7 -4
- package/corpus/templates/clips/app/lib/recording-status.ts +32 -0
- package/corpus/templates/clips/changelog/2026-07-01-dictation-keeps-listening-through-natural-pauses-and-gives-c.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-redoing-a-paused-desktop-recording-no-longer-leaves-the-recording-controls-disabled.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-stalled-clip-uploads-now-update-in-the-library-and-surface-as-failed.md +6 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +36 -18
- package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +99 -27
- package/corpus/templates/clips/desktop/src/overlays/flow-bar.tsx +4 -43
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +3 -0
- package/corpus/templates/clips/desktop/src/styles.css +5 -48
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +4 -5
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +15 -6
- package/corpus/templates/design/AGENTS.md +8 -4
- package/corpus/templates/design/actions/create-design.ts +1 -0
- package/corpus/templates/design/actions/duplicate-design.ts +3 -1
- package/corpus/templates/design/actions/edit-design.ts +43 -13
- package/corpus/templates/design/actions/export-html.ts +4 -2
- package/corpus/templates/design/actions/export-pdf.ts +3 -1
- package/corpus/templates/design/actions/export-svg.ts +4 -2
- package/corpus/templates/design/actions/export-zip.ts +10 -3
- package/corpus/templates/design/actions/generate-design.ts +5 -1
- package/corpus/templates/design/actions/present-design-variants.ts +17 -12
- package/corpus/templates/design/actions/update-file.ts +68 -17
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +3 -2
- package/corpus/templates/design/app/components/design/EditPanel.tsx +35 -3
- package/corpus/templates/design/app/components/design/MotionDock.tsx +46 -11
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +80 -30
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +60 -10
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +2 -0
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +4 -0
- package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +2 -2
- package/corpus/templates/design/app/i18n-data.ts +1 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +493 -109
- package/corpus/templates/design/app/pages/VisualEdit.tsx +4 -16
- package/corpus/templates/design/changelog/2026-07-01-org-members-can-see-design-projects-created-in-their-workspace.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +2 -2
- package/corpus/templates/design/server/plugins/db.ts +9 -0
- package/dist/agent/durable-background.d.ts +3 -0
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +19 -0
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/production-agent.d.ts +1 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +29 -6
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +40 -2
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +28 -6
- package/dist/agent/run-store.js.map +1 -1
- package/dist/chat-threads/store.d.ts +1 -0
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +2 -0
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +10 -6
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +10 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +26 -8
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +21 -2
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +15 -10
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +1 -0
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +3 -2
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/require-session.d.ts +14 -0
- package/dist/client/require-session.d.ts.map +1 -1
- package/dist/client/require-session.js +19 -1
- package/dist/client/require-session.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +4 -2
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -1
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/collab/storage.d.ts.map +1 -1
- package/dist/collab/storage.js +31 -5
- package/dist/collab/storage.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +1 -1
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +7 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/streaming-text-smoothing.d.ts.map +1 -1
- package/dist/shared/streaming-text-smoothing.js +8 -5
- package/dist/shared/streaming-text-smoothing.js.map +1 -1
- package/package.json +1 -1
|
@@ -513,7 +513,9 @@ export function RunErrorRecoveryCard({
|
|
|
513
513
|
onDismiss: () => void;
|
|
514
514
|
}) {
|
|
515
515
|
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
516
|
-
const [
|
|
516
|
+
const [copyState, setCopyState] = useState<"idle" | "copied" | "failed">(
|
|
517
|
+
"idle",
|
|
518
|
+
);
|
|
517
519
|
const [forking, setForking] = useState(false);
|
|
518
520
|
const [forkError, setForkError] = useState<string | null>(null);
|
|
519
521
|
const builderReconnect = useBuilderConnectFlow({
|
|
@@ -538,11 +540,15 @@ export function RunErrorRecoveryCard({
|
|
|
538
540
|
]
|
|
539
541
|
.filter(Boolean)
|
|
540
542
|
.join("\n\n");
|
|
541
|
-
void writeClipboardText(text)
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
543
|
+
void writeClipboardText(text)
|
|
544
|
+
.then((ok) => {
|
|
545
|
+
setCopyState(ok ? "copied" : "failed");
|
|
546
|
+
setTimeout(() => setCopyState("idle"), 1600);
|
|
547
|
+
})
|
|
548
|
+
.catch(() => {
|
|
549
|
+
setCopyState("failed");
|
|
550
|
+
setTimeout(() => setCopyState("idle"), 1600);
|
|
551
|
+
});
|
|
546
552
|
}, [info]);
|
|
547
553
|
const startNewChat = useCallback(() => {
|
|
548
554
|
window.dispatchEvent(new CustomEvent("agent-chat:new-chat"));
|
|
@@ -705,8 +711,20 @@ export function RunErrorRecoveryCard({
|
|
|
705
711
|
onClick={copyDetails}
|
|
706
712
|
className="ml-auto inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs font-medium text-muted-foreground hover:bg-background/80 hover:text-foreground"
|
|
707
713
|
>
|
|
708
|
-
{copied ?
|
|
709
|
-
|
|
714
|
+
{copyState === "copied" ? (
|
|
715
|
+
<IconCheck size={13} />
|
|
716
|
+
) : copyState === "failed" ? (
|
|
717
|
+
<IconX size={13} />
|
|
718
|
+
) : (
|
|
719
|
+
<IconCopy size={13} />
|
|
720
|
+
)}
|
|
721
|
+
<span aria-live="polite">
|
|
722
|
+
{copyState === "copied"
|
|
723
|
+
? "Copied"
|
|
724
|
+
: copyState === "failed"
|
|
725
|
+
? "Copy failed"
|
|
726
|
+
: copyLabel}
|
|
727
|
+
</span>
|
|
710
728
|
</button>
|
|
711
729
|
</div>
|
|
712
730
|
{shouldShowBuilderReconnect && builderReconnect.error && (
|
|
@@ -913,42 +931,32 @@ export function PlanModeCallout({
|
|
|
913
931
|
onSwitchToAct: () => void;
|
|
914
932
|
}) {
|
|
915
933
|
return (
|
|
916
|
-
<div className="shrink-0 px-3 pt-
|
|
917
|
-
<div className="rounded-
|
|
918
|
-
<
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
<button
|
|
943
|
-
type="button"
|
|
944
|
-
onClick={onSwitchToAct}
|
|
945
|
-
className="inline-flex h-8 shrink-0 items-center gap-1.5 rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent"
|
|
946
|
-
>
|
|
947
|
-
Act
|
|
948
|
-
<IconArrowRight size={13} />
|
|
949
|
-
</button>
|
|
950
|
-
)}
|
|
951
|
-
</div>
|
|
934
|
+
<div className="shrink-0 px-3 pt-1">
|
|
935
|
+
<div className="ml-auto flex w-fit max-w-full items-center gap-2 rounded-full border border-border/70 bg-background/95 px-2 py-1.5 text-xs text-muted-foreground shadow-sm">
|
|
936
|
+
<IconClipboardList size={13} className="shrink-0" />
|
|
937
|
+
<span className="min-w-0 truncate">
|
|
938
|
+
{canImplementPlan ? "Plan ready" : "Plan mode"}
|
|
939
|
+
</span>
|
|
940
|
+
{canImplementPlan ? (
|
|
941
|
+
<button
|
|
942
|
+
type="button"
|
|
943
|
+
onClick={onImplementPlan}
|
|
944
|
+
className="inline-flex h-6 shrink-0 items-center gap-1 rounded-full bg-foreground px-2.5 text-[11px] font-medium text-background hover:opacity-90"
|
|
945
|
+
>
|
|
946
|
+
<IconPlayerPlay size={12} />
|
|
947
|
+
Implement
|
|
948
|
+
</button>
|
|
949
|
+
) : (
|
|
950
|
+
<button
|
|
951
|
+
type="button"
|
|
952
|
+
onClick={onSwitchToAct}
|
|
953
|
+
className="inline-flex h-6 shrink-0 items-center gap-1 rounded-full border border-border bg-background px-2.5 text-[11px] font-medium text-foreground hover:bg-accent"
|
|
954
|
+
aria-label="Switch to Act mode"
|
|
955
|
+
>
|
|
956
|
+
Act
|
|
957
|
+
<IconArrowRight size={12} />
|
|
958
|
+
</button>
|
|
959
|
+
)}
|
|
952
960
|
</div>
|
|
953
961
|
</div>
|
|
954
962
|
);
|
|
@@ -692,10 +692,12 @@ export function ToolCallFallback({
|
|
|
692
692
|
mcpApp?: AgentMcpAppPayload;
|
|
693
693
|
chatUI?: ActionChatUIConfig;
|
|
694
694
|
structuredMeta?: Record<string, unknown>;
|
|
695
|
+
activity?: boolean;
|
|
695
696
|
approval?: { approvalKey: string; dismissed?: boolean };
|
|
696
697
|
}) {
|
|
697
698
|
const chatRunning = React.useContext(ChatRunningContext);
|
|
698
|
-
const isRunning =
|
|
699
|
+
const isRunning =
|
|
700
|
+
result === undefined && (chatRunning || rest.activity === true);
|
|
699
701
|
return (
|
|
700
702
|
<ToolCallDisplay
|
|
701
703
|
toolName={toolName}
|
|
@@ -757,7 +759,10 @@ export function ReconnectStreamMessage({
|
|
|
757
759
|
mcpApp={part.mcpApp}
|
|
758
760
|
chatUI={part.chatUI}
|
|
759
761
|
structuredMeta={part.structuredMeta}
|
|
760
|
-
isRunning={
|
|
762
|
+
isRunning={
|
|
763
|
+
part.result === undefined &&
|
|
764
|
+
(chatRunning || part.activity === true)
|
|
765
|
+
}
|
|
761
766
|
approval={part.approval}
|
|
762
767
|
/>
|
|
763
768
|
);
|
|
@@ -70,6 +70,24 @@ export function buildSignInReturnHref(): string {
|
|
|
70
70
|
return `${base}?return=${encodeURIComponent(ret)}`;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* True when the browser is already sitting on the framework sign-in entry
|
|
75
|
+
* point. Redirecting to sign-in from here would append the current sign-in
|
|
76
|
+
* URL as a fresh `?return=`, re-encode it, and navigate again — an infinite
|
|
77
|
+
* loop of ever-growing `…sign-in?return=%252F…sign-in%253Freturn%253D…` URLs.
|
|
78
|
+
*
|
|
79
|
+
* This happens when the authenticated app shell (wrapped in `RequireSession`)
|
|
80
|
+
* is served at the sign-in path instead of the framework login HTML — e.g.
|
|
81
|
+
* under a base-path deploy where the SPA shell answers `/<app>/_agent-native/
|
|
82
|
+
* sign-in`. The sign-in page is the framework's job, not the gate's: never
|
|
83
|
+
* redirect to ourselves. Compared against `agentNativePath(...)` so it matches
|
|
84
|
+
* whether or not the app is mounted under a base path.
|
|
85
|
+
*/
|
|
86
|
+
export function isOnSignInPage(): boolean {
|
|
87
|
+
if (typeof window === "undefined") return false;
|
|
88
|
+
return window.location.pathname === agentNativePath("/_agent-native/sign-in");
|
|
89
|
+
}
|
|
90
|
+
|
|
73
91
|
export function RequireSession({
|
|
74
92
|
children,
|
|
75
93
|
fallback,
|
|
@@ -83,7 +101,8 @@ export function RequireSession({
|
|
|
83
101
|
// flight is harmless but noisy.
|
|
84
102
|
const redirectedRef = useRef(false);
|
|
85
103
|
|
|
86
|
-
const mustRedirect =
|
|
104
|
+
const mustRedirect =
|
|
105
|
+
!bypass && !isLoading && !session && redirect && !isOnSignInPage();
|
|
87
106
|
|
|
88
107
|
useEffect(() => {
|
|
89
108
|
if (!mustRedirect) return;
|
|
@@ -46,8 +46,10 @@ export interface ShareButtonProps {
|
|
|
46
46
|
/** @deprecated No longer affects rendering — trigger always says
|
|
47
47
|
* "Share". Kept for callsite compatibility. */
|
|
48
48
|
variant?: "compact" | "label";
|
|
49
|
-
/** Optional trigger style. Defaults to the Google-Docs-style "Share" label.
|
|
50
|
-
|
|
49
|
+
/** Optional trigger style. Defaults to the Google-Docs-style "Share" label.
|
|
50
|
+
* Use "label-icon" to render a leading share glyph alongside the label so
|
|
51
|
+
* the trigger matches adjacent icon+label buttons; "icon" is icon-only. */
|
|
52
|
+
trigger?: "label" | "icon" | "label-icon";
|
|
51
53
|
/** @deprecated Label triggers no longer render a visibility/share glyph. */
|
|
52
54
|
hideTriggerIcon?: boolean;
|
|
53
55
|
/** Optional className applied to the trigger button. */
|
|
@@ -355,7 +357,9 @@ export function ShareButton(props: ShareButtonProps) {
|
|
|
355
357
|
};
|
|
356
358
|
|
|
357
359
|
// The default trigger stays text-only; the icon trigger keeps the share glyph.
|
|
360
|
+
// "label-icon" renders the glyph alongside the label for icon+label parity.
|
|
358
361
|
const iconOnly = props.trigger === "icon";
|
|
362
|
+
const showLabelIcon = props.trigger === "label-icon";
|
|
359
363
|
|
|
360
364
|
return (
|
|
361
365
|
<Popover open={open} onOpenChange={handleOpenChange}>
|
|
@@ -368,7 +372,9 @@ export function ShareButton(props: ShareButtonProps) {
|
|
|
368
372
|
)}
|
|
369
373
|
aria-label={iconOnly ? "Share" : undefined}
|
|
370
374
|
>
|
|
371
|
-
{iconOnly
|
|
375
|
+
{iconOnly || showLabelIcon ? (
|
|
376
|
+
<IconShare3 size={16} strokeWidth={1.75} />
|
|
377
|
+
) : null}
|
|
372
378
|
{!iconOnly && <span>Share</span>}
|
|
373
379
|
</button>
|
|
374
380
|
</PopoverTrigger>
|
|
@@ -10,6 +10,41 @@ import { ensureTableExists, ensureColumnExists } from "../db/ddl-guard.js";
|
|
|
10
10
|
|
|
11
11
|
let _initPromise: Promise<void> | undefined;
|
|
12
12
|
|
|
13
|
+
type DbExecClient = ReturnType<typeof getDbExec>;
|
|
14
|
+
type DbExecuteArg = Parameters<DbExecClient["execute"]>[0];
|
|
15
|
+
type DbExecuteResult = Awaited<ReturnType<DbExecClient["execute"]>>;
|
|
16
|
+
|
|
17
|
+
function isSqliteBusyError(error: unknown): boolean {
|
|
18
|
+
if (isPostgres()) return false;
|
|
19
|
+
const candidate = error as { code?: unknown; message?: unknown } | null;
|
|
20
|
+
return (
|
|
21
|
+
candidate?.code === "SQLITE_BUSY" ||
|
|
22
|
+
candidate?.code === "SQLITE_BUSY_RECOVERY" ||
|
|
23
|
+
(typeof candidate?.message === "string" &&
|
|
24
|
+
/database is locked|SQLITE_BUSY/i.test(candidate.message))
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sleep(ms: number): Promise<void> {
|
|
29
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function executeWithSqliteRetry(
|
|
33
|
+
client: DbExecClient,
|
|
34
|
+
statement: DbExecuteArg,
|
|
35
|
+
): Promise<DbExecuteResult> {
|
|
36
|
+
const delays = [25, 75, 150, 300, 600];
|
|
37
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
38
|
+
try {
|
|
39
|
+
return await client.execute(statement);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
const delay = delays[attempt];
|
|
42
|
+
if (delay === undefined || !isSqliteBusyError(error)) throw error;
|
|
43
|
+
await sleep(delay);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
13
48
|
async function ensureTable(): Promise<void> {
|
|
14
49
|
if (!_initPromise) {
|
|
15
50
|
_initPromise = (async () => {
|
|
@@ -97,7 +132,7 @@ export async function trySaveYDocState(
|
|
|
97
132
|
const b64 = uint8ArrayToBase64(state);
|
|
98
133
|
const nowExpr = isPostgres() ? "NOW()::text" : "datetime('now')";
|
|
99
134
|
if (expectedVersion === null) {
|
|
100
|
-
const result = await client
|
|
135
|
+
const result = await executeWithSqliteRetry(client, {
|
|
101
136
|
sql: isPostgres()
|
|
102
137
|
? `INSERT INTO _collab_docs (doc_id, yjs_state, text_snapshot, version, updated_at) VALUES (?, ?, ?, 0, ${nowExpr}) ON CONFLICT (doc_id) DO NOTHING`
|
|
103
138
|
: `INSERT OR IGNORE INTO _collab_docs (doc_id, yjs_state, text_snapshot, version, updated_at) VALUES (?, ?, ?, 0, ${nowExpr})`,
|
|
@@ -106,7 +141,7 @@ export async function trySaveYDocState(
|
|
|
106
141
|
return result.rowsAffected > 0;
|
|
107
142
|
}
|
|
108
143
|
|
|
109
|
-
const result = await client
|
|
144
|
+
const result = await executeWithSqliteRetry(client, {
|
|
110
145
|
sql: `UPDATE _collab_docs SET yjs_state = ?, text_snapshot = ?, version = version + 1, updated_at = ${nowExpr} WHERE doc_id = ? AND version = ?`,
|
|
111
146
|
args: [b64, textSnapshot, docId, expectedVersion],
|
|
112
147
|
});
|
|
@@ -123,13 +158,13 @@ export async function saveYDocState(
|
|
|
123
158
|
const client = getDbExec();
|
|
124
159
|
const b64 = uint8ArrayToBase64(state);
|
|
125
160
|
const nowExpr = isPostgres() ? "NOW()::text" : "datetime('now')";
|
|
126
|
-
const updated = await client
|
|
161
|
+
const updated = await executeWithSqliteRetry(client, {
|
|
127
162
|
sql: `UPDATE _collab_docs SET yjs_state = ?, text_snapshot = ?, version = version + 1, updated_at = ${nowExpr} WHERE doc_id = ?`,
|
|
128
163
|
args: [b64, textSnapshot, docId],
|
|
129
164
|
});
|
|
130
165
|
if (updated.rowsAffected > 0) return;
|
|
131
166
|
|
|
132
|
-
const inserted = await client
|
|
167
|
+
const inserted = await executeWithSqliteRetry(client, {
|
|
133
168
|
sql: isPostgres()
|
|
134
169
|
? `INSERT INTO _collab_docs (doc_id, yjs_state, text_snapshot, version, updated_at) VALUES (?, ?, ?, 0, ${nowExpr}) ON CONFLICT (doc_id) DO NOTHING`
|
|
135
170
|
: `INSERT OR IGNORE INTO _collab_docs (doc_id, yjs_state, text_snapshot, version, updated_at) VALUES (?, ?, ?, 0, ${nowExpr})`,
|
|
@@ -137,7 +172,7 @@ export async function saveYDocState(
|
|
|
137
172
|
});
|
|
138
173
|
if (inserted.rowsAffected > 0) return;
|
|
139
174
|
|
|
140
|
-
await client
|
|
175
|
+
await executeWithSqliteRetry(client, {
|
|
141
176
|
sql: `UPDATE _collab_docs SET yjs_state = ?, text_snapshot = ?, version = version + 1, updated_at = ${nowExpr} WHERE doc_id = ?`,
|
|
142
177
|
args: [b64, textSnapshot, docId],
|
|
143
178
|
});
|
|
@@ -7694,6 +7694,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
7694
7694
|
nextTitle,
|
|
7695
7695
|
nextPreview,
|
|
7696
7696
|
newMessageCount,
|
|
7697
|
+
{ ignoreConflicts: true },
|
|
7697
7698
|
);
|
|
7698
7699
|
// Scope updates piggyback on the PUT — the client uses this
|
|
7699
7700
|
// path for both "detach" (scope: null) and "retag" flows.
|
|
@@ -542,6 +542,12 @@ export function safeReturnPath(raw: string | null | undefined): string {
|
|
|
542
542
|
try {
|
|
543
543
|
const parsed = new URL(raw, "http://safe-base.invalid");
|
|
544
544
|
if (parsed.origin !== "http://safe-base.invalid") return "/";
|
|
545
|
+
// Never return to the sign-in entry point itself. A `return` that resolves
|
|
546
|
+
// back to `…/_agent-native/sign-in` re-enters the redirect loop — each hop
|
|
547
|
+
// nests the prior sign-in URL as a fresh `?return=`. Collapse it to "/" so
|
|
548
|
+
// the post-sign-in 302 lands on the app, not another sign-in page. Matches
|
|
549
|
+
// with or without an app base-path prefix (`/<app>/_agent-native/sign-in`).
|
|
550
|
+
if (parsed.pathname.endsWith("/_agent-native/sign-in")) return "/";
|
|
545
551
|
return parsed.pathname + parsed.search + parsed.hash;
|
|
546
552
|
} catch {
|
|
547
553
|
return "/";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const SMOOTH_STREAMING_COMMIT_INTERVAL_MS = 16;
|
|
2
2
|
export const SMOOTH_STREAMING_LONG_TEXT_THRESHOLD_GRAPHEMES = 640;
|
|
3
3
|
export const SMOOTH_STREAMING_LONG_TEXT_TAIL_GRAPHEMES = 180;
|
|
4
|
+
const SMOOTH_STREAMING_SPEED_MULTIPLIER = 2;
|
|
4
5
|
|
|
5
6
|
type SegmenterInstance = {
|
|
6
7
|
segment(input: string): Iterable<{ segment: string }>;
|
|
@@ -137,7 +138,7 @@ export function smoothStreamingRevealCount({
|
|
|
137
138
|
return 0;
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
const
|
|
141
|
+
const baseCharactersPerSecond = inputDone
|
|
141
142
|
? backlog > 800
|
|
142
143
|
? 900
|
|
143
144
|
: 420
|
|
@@ -148,8 +149,12 @@ export function smoothStreamingRevealCount({
|
|
|
148
149
|
: backlog > 180
|
|
149
150
|
? 190
|
|
150
151
|
: 95;
|
|
152
|
+
const charactersPerSecond =
|
|
153
|
+
baseCharactersPerSecond * SMOOTH_STREAMING_SPEED_MULTIPLIER;
|
|
151
154
|
|
|
152
|
-
const maxBurst =
|
|
155
|
+
const maxBurst =
|
|
156
|
+
(inputDone ? 160 : backlog > 1400 ? 120 : 72) *
|
|
157
|
+
SMOOTH_STREAMING_SPEED_MULTIPLIER;
|
|
153
158
|
const count = Math.floor((elapsedMs / 1000) * charactersPerSecond);
|
|
154
159
|
|
|
155
160
|
return Math.min(backlog, Math.max(1, count), maxBurst);
|
|
@@ -164,15 +169,15 @@ export function smoothStreamingPunctuationDelayMs(
|
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
if (grapheme === "\n") {
|
|
167
|
-
return 80;
|
|
172
|
+
return 80 / SMOOTH_STREAMING_SPEED_MULTIPLIER;
|
|
168
173
|
}
|
|
169
174
|
|
|
170
175
|
if (/[.!?)]/.test(grapheme)) {
|
|
171
|
-
return 70;
|
|
176
|
+
return 70 / SMOOTH_STREAMING_SPEED_MULTIPLIER;
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
if (/[,;:]/.test(grapheme)) {
|
|
175
|
-
return 35;
|
|
180
|
+
return 35 / SMOOTH_STREAMING_SPEED_MULTIPLIER;
|
|
176
181
|
}
|
|
177
182
|
|
|
178
183
|
return 0;
|
|
@@ -10,16 +10,17 @@ LLMs — including Gemini Pro — degrade complex logos. They smear gradients, d
|
|
|
10
10
|
## How it works
|
|
11
11
|
|
|
12
12
|
1. The library has a `canonicalLogoUrl` (set via `set-canonical-logo --libraryId --assetId`). The asset's role is `logo_reference`.
|
|
13
|
-
2. When `generate-image
|
|
13
|
+
2. **Logo compositing is a preset option.** A generation preset carries `includeLogo` (stored in the preset `settings` and surfaced as a first-class field). When a generation resolves to a preset with `includeLogo: true`, `generate-image` composites the logo. A generate call's own `includeLogo` arg, when passed, overrides the preset for that run; when omitted, the preset's value wins.
|
|
14
|
+
3. When logo compositing is on, the prompt envelope adds:
|
|
14
15
|
> Leave a clean uncluttered area in the upper-right for the real brand logo; do not draw or approximate the logo yourself.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
4. Gemini returns an image with empty space in that corner.
|
|
17
|
+
5. `compositeLogo()` from `server/lib/image-processing.ts` (Sharp) loads the canonical logo PNG / SVG, resizes it to ~16% of the image width with reasonable inset, and composites it onto the generated image.
|
|
18
|
+
6. Output: the image with the actual logo, pixel-perfect, vector-quality if the source is SVG.
|
|
18
19
|
|
|
19
20
|
## When to use it
|
|
20
21
|
|
|
21
|
-
- The user
|
|
22
|
-
- The agent infers the user wants the logo (e.g. "make a hero with our brand logo").
|
|
22
|
+
- The user turned on "Composite canonical logo" when creating a generation preset (the preset then stamps the logo on every image made with it).
|
|
23
|
+
- The agent infers the user wants the logo for a one-off (e.g. "make a hero with our brand logo") — pass `includeLogo: true` on that single generate call to override the preset.
|
|
23
24
|
- The image will appear in a customer-facing context where logo accuracy matters.
|
|
24
25
|
|
|
25
26
|
## When NOT to use it
|
|
@@ -47,10 +47,25 @@ prompt, aspectRatio }`.
|
|
|
47
47
|
composer is empty. The image model is the only remaining composer-side
|
|
48
48
|
default; the image-model picker writes `imageGenerationModel`, which image
|
|
49
49
|
generation actions may use when `model` is omitted.
|
|
50
|
+
- When a `preset` is tagged, the server embeds that preset's aesthetics and
|
|
51
|
+
creative philosophy (brand style brief, prompt template, text/logo policy,
|
|
52
|
+
output format) into your message inside a `<tagged-generation-presets>` block.
|
|
53
|
+
Study and internalize that brief before you generate — let it drive
|
|
54
|
+
composition, mood, lighting, and subject — then pass the `presetId` to
|
|
55
|
+
`generate-image` / `generate-image-batch` so the saved format/model/tier/logo
|
|
56
|
+
apply automatically. Do not restate those as ad-hoc args.
|
|
50
57
|
- For exact visible copy inside a generated image, pass `embeddedText` and
|
|
51
58
|
optional `textPlacement` to `generate-image` or each `generate-image-batch`
|
|
52
59
|
slot. Keep the general `prompt` for creative direction; the structured text
|
|
53
60
|
fields are what allow the pipeline to render copy instead of suppressing it.
|
|
61
|
+
- Logo compositing is a preset option: a generation preset carries
|
|
62
|
+
`includeLogo`, and `generate-image` / `generate-image-batch` stamp the
|
|
63
|
+
library's pixel-perfect canonical logo onto the result when the resolved
|
|
64
|
+
preset has it on (no-op if the library has no canonical logo). Set it via
|
|
65
|
+
`create-generation-preset` / `update-generation-preset` (`includeLogo`). The
|
|
66
|
+
model never draws the logo itself. Pass `includeLogo` on a generate call only
|
|
67
|
+
to override the preset for that one run; otherwise omit it. See the
|
|
68
|
+
`logo-composite` skill.
|
|
54
69
|
- `asset-variants` is the shared live generation tray state. New image
|
|
55
70
|
candidates should appear there through `generate-image` or
|
|
56
71
|
`generate-image-batch`; do not invent page-local progress surfaces.
|
|
@@ -155,6 +155,7 @@ export function serializeGenerationRun(row: any) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
export function serializeGenerationPreset(row: any): GenerationPresetSummary {
|
|
158
|
+
const settings = parseJson<Record<string, unknown>>(row.settings, {});
|
|
158
159
|
return {
|
|
159
160
|
id: row.id,
|
|
160
161
|
libraryId: row.libraryId,
|
|
@@ -169,7 +170,8 @@ export function serializeGenerationPreset(row: any): GenerationPresetSummary {
|
|
|
169
170
|
model: row.model,
|
|
170
171
|
textPolicy: row.textPolicy ?? "",
|
|
171
172
|
referencePolicy: row.referencePolicy ?? "auto",
|
|
172
|
-
|
|
173
|
+
includeLogo: settings.includeLogo === true,
|
|
174
|
+
settings,
|
|
173
175
|
sortOrder: Number(row.sortOrder ?? 0),
|
|
174
176
|
createdAt: row.createdAt,
|
|
175
177
|
updatedAt: row.updatedAt,
|
|
@@ -32,6 +32,12 @@ export default defineAction({
|
|
|
32
32
|
referencePolicy: z
|
|
33
33
|
.enum(GENERATION_PRESET_REFERENCE_POLICIES)
|
|
34
34
|
.default("auto"),
|
|
35
|
+
includeLogo: z.coerce
|
|
36
|
+
.boolean()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe(
|
|
39
|
+
"When true, images generated with this preset composite the library's canonical logo (no-op if the library has no canonical logo).",
|
|
40
|
+
),
|
|
35
41
|
settings: z.record(z.string(), z.unknown()).optional(),
|
|
36
42
|
sortOrder: z.coerce.number().optional(),
|
|
37
43
|
}),
|
|
@@ -62,7 +68,12 @@ export default defineAction({
|
|
|
62
68
|
model: args.model,
|
|
63
69
|
textPolicy: args.textPolicy,
|
|
64
70
|
referencePolicy: args.referencePolicy,
|
|
65
|
-
settings: stringifyJson(
|
|
71
|
+
settings: stringifyJson({
|
|
72
|
+
...(args.settings ?? {}),
|
|
73
|
+
...(args.includeLogo !== undefined
|
|
74
|
+
? { includeLogo: args.includeLogo }
|
|
75
|
+
: {}),
|
|
76
|
+
}),
|
|
66
77
|
sortOrder: args.sortOrder ?? 100,
|
|
67
78
|
createdAt: now,
|
|
68
79
|
updatedAt: now,
|
|
@@ -48,7 +48,11 @@ const schema = z.object({
|
|
|
48
48
|
.describe("Number of image candidates to generate in the picker."),
|
|
49
49
|
tier: z.enum(IMAGE_QUALITY_TIERS).optional(),
|
|
50
50
|
styleStrength: z.enum(STYLE_STRENGTHS).default("balanced"),
|
|
51
|
-
includeLogo: booleanParam
|
|
51
|
+
includeLogo: booleanParam
|
|
52
|
+
.optional()
|
|
53
|
+
.describe(
|
|
54
|
+
"Override logo compositing for this run. When omitted, the selected preset's logo setting decides whether the library's canonical logo is composited.",
|
|
55
|
+
),
|
|
52
56
|
callerAppId: z
|
|
53
57
|
.string()
|
|
54
58
|
.optional()
|
|
@@ -86,7 +86,12 @@ export default defineAction({
|
|
|
86
86
|
tier: z.enum(IMAGE_QUALITY_TIERS).optional(),
|
|
87
87
|
intent: z.enum(GENERATION_INTENTS).default("generate"),
|
|
88
88
|
styleStrength: z.enum(STYLE_STRENGTHS).default("balanced"),
|
|
89
|
-
includeLogo: z.coerce
|
|
89
|
+
includeLogo: z.coerce
|
|
90
|
+
.boolean()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe(
|
|
93
|
+
"Composite the library's canonical logo onto every slot. When omitted, the selected preset's logo setting is used; pass an explicit value to override it. No-op if the library has no canonical logo.",
|
|
94
|
+
),
|
|
90
95
|
groundingMode: z.enum(["auto", "off", "google-search"]).default("auto"),
|
|
91
96
|
source: z.enum(["chat", "ui", "a2a"]).default("chat"),
|
|
92
97
|
callerAppId: z
|
|
@@ -103,7 +103,12 @@ export default defineAction({
|
|
|
103
103
|
.describe(
|
|
104
104
|
"Exact reference assets to use. When omitted, the server deterministically chooses a small relevant subset from the latest library references.",
|
|
105
105
|
),
|
|
106
|
-
includeLogo: z.coerce
|
|
106
|
+
includeLogo: z.coerce
|
|
107
|
+
.boolean()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe(
|
|
110
|
+
"Composite the library's pixel-perfect canonical logo onto the finished image. When omitted, the selected preset's logo setting is used; pass an explicit value to override it. No-op if the library has no canonical logo.",
|
|
111
|
+
),
|
|
107
112
|
slotId: z.string().optional(),
|
|
108
113
|
variantBatchId: z.string().optional(),
|
|
109
114
|
variantScopeId: z
|
|
@@ -259,11 +264,13 @@ export default defineAction({
|
|
|
259
264
|
preset?.imageSize ??
|
|
260
265
|
collection?.defaultImageSize ??
|
|
261
266
|
"2K") as (typeof IMAGE_SIZES)[number];
|
|
262
|
-
const presetSettings = parseJson<{
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
);
|
|
267
|
+
const presetSettings = parseJson<{
|
|
268
|
+
tier?: ImageQualityTier;
|
|
269
|
+
includeLogo?: boolean;
|
|
270
|
+
}>(preset?.settings, {});
|
|
266
271
|
const resolvedTier = args.tier ?? presetSettings.tier;
|
|
272
|
+
const resolvedIncludeLogo =
|
|
273
|
+
args.includeLogo ?? presetSettings.includeLogo ?? false;
|
|
267
274
|
const category = (args.categories?.[0] ??
|
|
268
275
|
preset?.category ??
|
|
269
276
|
collection?.category) as ImageCategory | undefined;
|
|
@@ -320,7 +327,7 @@ export default defineAction({
|
|
|
320
327
|
embeddedText: args.embeddedText,
|
|
321
328
|
textPlacement: args.textPlacement,
|
|
322
329
|
referenceCount: references.length,
|
|
323
|
-
includeLogo:
|
|
330
|
+
includeLogo: resolvedIncludeLogo,
|
|
324
331
|
aspectRatio: resolvedAspectRatio,
|
|
325
332
|
imageSize: resolvedImageSize,
|
|
326
333
|
category,
|
|
@@ -363,7 +370,7 @@ export default defineAction({
|
|
|
363
370
|
aspectRatio: resolvedAspectRatio,
|
|
364
371
|
imageSize: resolvedImageSize,
|
|
365
372
|
groundingMode: args.groundingMode,
|
|
366
|
-
includeLogo:
|
|
373
|
+
includeLogo: resolvedIncludeLogo,
|
|
367
374
|
categories: resolvedCategories ?? [],
|
|
368
375
|
collectionId: resolvedCollectionId ?? null,
|
|
369
376
|
presetId: preset?.id ?? null,
|
|
@@ -386,7 +393,7 @@ export default defineAction({
|
|
|
386
393
|
intent: args.intent,
|
|
387
394
|
styleStrength: args.styleStrength,
|
|
388
395
|
tier: resolvedTier,
|
|
389
|
-
includeLogo:
|
|
396
|
+
includeLogo: resolvedIncludeLogo,
|
|
390
397
|
categories: resolvedCategories ?? [],
|
|
391
398
|
presetId: preset?.id,
|
|
392
399
|
sessionId: session?.id,
|
|
@@ -463,7 +470,7 @@ export default defineAction({
|
|
|
463
470
|
await deleteAppState("image-generation-setup").catch(() => {});
|
|
464
471
|
let image = generated.image;
|
|
465
472
|
let mimeType = generated.mimeType;
|
|
466
|
-
if (
|
|
473
|
+
if (resolvedIncludeLogo && library.canonicalLogoAssetId) {
|
|
467
474
|
const [logo] = await db
|
|
468
475
|
.select()
|
|
469
476
|
.from(schema.assets)
|
|
@@ -539,7 +546,7 @@ export default defineAction({
|
|
|
539
546
|
intent: args.intent,
|
|
540
547
|
styleStrength: args.styleStrength,
|
|
541
548
|
tier: resolvedTier,
|
|
542
|
-
includeLogo:
|
|
549
|
+
includeLogo: resolvedIncludeLogo,
|
|
543
550
|
presetId: preset?.id,
|
|
544
551
|
sessionId: session?.id,
|
|
545
552
|
generated: true,
|
|
@@ -584,7 +591,7 @@ export default defineAction({
|
|
|
584
591
|
intent: args.intent,
|
|
585
592
|
styleStrength: args.styleStrength,
|
|
586
593
|
tier: resolvedTier,
|
|
587
|
-
includeLogo:
|
|
594
|
+
includeLogo: resolvedIncludeLogo,
|
|
588
595
|
categories: resolvedCategories ?? [],
|
|
589
596
|
referenceSelection,
|
|
590
597
|
settingsUsed,
|
|
@@ -72,8 +72,10 @@ const schema = z.object({
|
|
|
72
72
|
.default("balanced")
|
|
73
73
|
.describe("How strongly to follow the library style during generation."),
|
|
74
74
|
includeLogo: booleanParam
|
|
75
|
-
.
|
|
76
|
-
.describe(
|
|
75
|
+
.optional()
|
|
76
|
+
.describe(
|
|
77
|
+
"Override logo compositing for auto-generation. When omitted, the selected preset's logo setting decides whether the library's canonical logo is composited.",
|
|
78
|
+
),
|
|
77
79
|
callerAppId: z
|
|
78
80
|
.string()
|
|
79
81
|
.optional()
|
|
@@ -4,7 +4,7 @@ import { eq } from "drizzle-orm";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
-
import { nowIso, stringifyJson } from "../server/lib/json.js";
|
|
7
|
+
import { nowIso, parseJson, stringifyJson } from "../server/lib/json.js";
|
|
8
8
|
import {
|
|
9
9
|
ASPECT_RATIOS,
|
|
10
10
|
GENERATION_PRESET_REFERENCE_POLICIES,
|
|
@@ -29,6 +29,12 @@ export default defineAction({
|
|
|
29
29
|
model: z.enum(IMAGE_MODELS).optional(),
|
|
30
30
|
textPolicy: z.string().optional(),
|
|
31
31
|
referencePolicy: z.enum(GENERATION_PRESET_REFERENCE_POLICIES).optional(),
|
|
32
|
+
includeLogo: z.coerce
|
|
33
|
+
.boolean()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe(
|
|
36
|
+
"When true, images generated with this preset composite the library's canonical logo (no-op if the library has no canonical logo).",
|
|
37
|
+
),
|
|
32
38
|
settings: z.record(z.string(), z.unknown()).optional(),
|
|
33
39
|
sortOrder: z.coerce.number().optional(),
|
|
34
40
|
}),
|
|
@@ -67,8 +73,15 @@ export default defineAction({
|
|
|
67
73
|
] as const) {
|
|
68
74
|
if (args[key] !== undefined) updates[key] = args[key];
|
|
69
75
|
}
|
|
70
|
-
if (args.settings !== undefined) {
|
|
71
|
-
|
|
76
|
+
if (args.settings !== undefined || args.includeLogo !== undefined) {
|
|
77
|
+
const nextSettings = {
|
|
78
|
+
...parseJson<Record<string, unknown>>(preset.settings, {}),
|
|
79
|
+
...(args.settings ?? {}),
|
|
80
|
+
};
|
|
81
|
+
if (args.includeLogo !== undefined) {
|
|
82
|
+
nextSettings.includeLogo = args.includeLogo;
|
|
83
|
+
}
|
|
84
|
+
updates.settings = stringifyJson(nextSettings);
|
|
72
85
|
}
|
|
73
86
|
await db
|
|
74
87
|
.update(schema.assetGenerationPresets)
|