@agent-native/core 0.84.1 → 0.84.2
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 +7 -0
- package/corpus/core/docs/design/durable-agent-runs.md +34 -35
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builder-engine.ts +81 -9
- package/corpus/core/src/agent/run-manager.ts +8 -7
- package/corpus/core/src/cli/design-connect.ts +175 -0
- package/corpus/core/src/cli/skills.ts +5 -7
- package/corpus/core/src/client/AgentPanel.tsx +8 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +4 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +45 -15
- package/corpus/core/src/client/sse-event-processor.ts +31 -4
- package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/corpus/templates/design/actions/apply-motion-edit.ts +50 -18
- package/corpus/templates/design/actions/get-motion-timeline.ts +4 -14
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +156 -12
- package/corpus/templates/design/app/components/design/MotionDock.tsx +2 -3
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +217 -75
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +39 -39
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +558 -37
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +75 -3
- package/corpus/templates/design/app/components/design/types.ts +13 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +17 -0
- package/corpus/templates/design/app/i18n-data.ts +245 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +762 -153
- package/corpus/templates/design/changelog/2026-06-30-copying-or-dragging-screen-elements-onto-the-infinite-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-questions-now-use-tighter-editor-typography-and-contr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-element-drags-can-be-cancelled-with-escape-before-they-commi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-pending-visual-style-edits-now-warn-before-you-leave-the-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-visual-style-drags-stay-live-while-pending-edits-can-be-appl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-design-chat-no-longer-shows-a-redundant-context-tab-above-the-composer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-motion-track-creation-no-longer-fails-in-local-editors.md +6 -0
- package/corpus/templates/design/server/plugins/db.ts +10 -0
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +61 -10
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/run-manager.d.ts +8 -7
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +8 -7
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/cli/design-connect.d.ts +2 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +140 -0
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +5 -7
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +4 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts +3 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +2 -2
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +37 -14
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +14 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +21 -5
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +2 -2
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/docs/design/durable-agent-runs.md +34 -35
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
|
@@ -17,6 +17,7 @@ import { formatChatErrorText, normalizeChatError } from "./error-format.js";
|
|
|
17
17
|
import {
|
|
18
18
|
AgentAutoContinueSignal,
|
|
19
19
|
type AgentActivityTrailEntry,
|
|
20
|
+
type AgentAutoContinueErrorInfo,
|
|
20
21
|
type ContentPart,
|
|
21
22
|
readSSEStream,
|
|
22
23
|
settleInterruptedToolCalls,
|
|
@@ -1353,6 +1354,7 @@ export function createAgentChatAdapter(
|
|
|
1353
1354
|
const structuredContinuationFragments: AgentChatStructuredMessage[] = [];
|
|
1354
1355
|
let visibleContinuationPrefix: ContentPart[] = [];
|
|
1355
1356
|
let lastAutoContinueReason: string | null = null;
|
|
1357
|
+
let lastRecoverableRunError: AgentAutoContinueErrorInfo | null = null;
|
|
1356
1358
|
const attemptedRunIds: string[] = [];
|
|
1357
1359
|
let authRecoveryAttempted = false;
|
|
1358
1360
|
let continuationToolCallCounter = 0;
|
|
@@ -1364,6 +1366,12 @@ export function createAgentChatAdapter(
|
|
|
1364
1366
|
lastAutoContinueReason
|
|
1365
1367
|
? `last_auto_continue_reason: ${lastAutoContinueReason}`
|
|
1366
1368
|
: "",
|
|
1369
|
+
lastRecoverableRunError?.errorCode
|
|
1370
|
+
? `last_recoverable_error_code: ${lastRecoverableRunError.errorCode}`
|
|
1371
|
+
: "",
|
|
1372
|
+
lastRecoverableRunError?.message
|
|
1373
|
+
? `last_recoverable_error: ${lastRecoverableRunError.message}`
|
|
1374
|
+
: "",
|
|
1367
1375
|
`stale_run_continuations: ${staleRunContinuationAttempts}`,
|
|
1368
1376
|
`stalled_transient_continuations: ${stalledTransientContinuationAttempts}`,
|
|
1369
1377
|
`empty_transient_continuations: ${emptyTransientContinuationAttempts}`,
|
|
@@ -1677,6 +1685,9 @@ export function createAgentChatAdapter(
|
|
|
1677
1685
|
signal: AgentAutoContinueSignal,
|
|
1678
1686
|
): { ok: boolean; resetVisibleContent: boolean } => {
|
|
1679
1687
|
lastAutoContinueReason = signal.reason;
|
|
1688
|
+
if (signal.errorInfo) {
|
|
1689
|
+
lastRecoverableRunError = signal.errorInfo;
|
|
1690
|
+
}
|
|
1680
1691
|
const isTransient = signal.reason !== "loop_limit";
|
|
1681
1692
|
const visibleContent = visibleContentForContinuation();
|
|
1682
1693
|
const currentPartialHistory =
|
|
@@ -1686,14 +1697,12 @@ export function createAgentChatAdapter(
|
|
|
1686
1697
|
// whitespace-only output cannot keep the run alive indefinitely.
|
|
1687
1698
|
const madeContentProgress = hasContinuationProgress(visibleContent);
|
|
1688
1699
|
// An action was streamed but has not returned yet (a tool_start with
|
|
1689
|
-
// no tool_done)
|
|
1690
|
-
//
|
|
1691
|
-
//
|
|
1692
|
-
//
|
|
1693
|
-
//
|
|
1694
|
-
const hasInFlightTool =
|
|
1695
|
-
hasInFlightToolCall(visibleContent) ||
|
|
1696
|
-
Boolean(lastActivityTool(signal.activityTrail));
|
|
1700
|
+
// no tool_done). This is durable enough to survive continuation: the
|
|
1701
|
+
// server already emitted a real tool call. A tool-scoped activity
|
|
1702
|
+
// card ("Preparing generate-design") is useful UI, but it happens
|
|
1703
|
+
// before tool_start; treating it as progress caused silent retry
|
|
1704
|
+
// loops when the LLM timed out while assembling a large tool input.
|
|
1705
|
+
const hasInFlightTool = hasInFlightToolCall(visibleContent);
|
|
1697
1706
|
// Either real output or an actively-running tool counts as progress
|
|
1698
1707
|
// for the stalled/empty caps.
|
|
1699
1708
|
const madeProgress = madeContentProgress || hasInFlightTool;
|
|
@@ -2174,15 +2183,28 @@ export function createAgentChatAdapter(
|
|
|
2174
2183
|
}
|
|
2175
2184
|
const continuation = prepareAutoContinuation(err);
|
|
2176
2185
|
if (!continuation.ok) {
|
|
2177
|
-
const
|
|
2186
|
+
const preservedError =
|
|
2187
|
+
err.errorInfo ?? lastRecoverableRunError ?? null;
|
|
2188
|
+
const message =
|
|
2189
|
+
preservedError?.message ??
|
|
2190
|
+
exhaustedRecoveryMessage(err.reason);
|
|
2191
|
+
const details = [
|
|
2192
|
+
preservedError?.details,
|
|
2193
|
+
connectionRecoveryDetails(),
|
|
2194
|
+
]
|
|
2195
|
+
.filter(Boolean)
|
|
2196
|
+
.join("\n\n");
|
|
2197
|
+
const errorCode =
|
|
2198
|
+
preservedError?.errorCode ?? "connection_error";
|
|
2178
2199
|
captureChatClientError(err, "auto-continuation-exhausted", {
|
|
2179
2200
|
autoContinueReason: err.reason,
|
|
2201
|
+
...(errorCode ? { errorCode } : {}),
|
|
2180
2202
|
});
|
|
2181
2203
|
const runError = {
|
|
2182
2204
|
message,
|
|
2183
|
-
details:
|
|
2184
|
-
errorCode
|
|
2185
|
-
recoverable: true,
|
|
2205
|
+
...(details ? { details } : {}),
|
|
2206
|
+
errorCode,
|
|
2207
|
+
recoverable: preservedError?.recoverable ?? true,
|
|
2186
2208
|
...(runId ? { runId } : {}),
|
|
2187
2209
|
};
|
|
2188
2210
|
if (typeof window !== "undefined") {
|
|
@@ -2192,10 +2214,16 @@ export function createAgentChatAdapter(
|
|
|
2192
2214
|
}),
|
|
2193
2215
|
);
|
|
2194
2216
|
}
|
|
2195
|
-
settleInterruptedToolCalls(content
|
|
2217
|
+
settleInterruptedToolCalls(content, undefined, {
|
|
2218
|
+
includeActivity: true,
|
|
2219
|
+
});
|
|
2196
2220
|
content.push({
|
|
2197
2221
|
type: "text",
|
|
2198
|
-
text:
|
|
2222
|
+
text: formatChatErrorText(
|
|
2223
|
+
message,
|
|
2224
|
+
preservedError?.upgradeUrl,
|
|
2225
|
+
errorCode,
|
|
2226
|
+
),
|
|
2199
2227
|
});
|
|
2200
2228
|
yield {
|
|
2201
2229
|
content: [...content],
|
|
@@ -2343,7 +2371,9 @@ export function createAgentChatAdapter(
|
|
|
2343
2371
|
}),
|
|
2344
2372
|
);
|
|
2345
2373
|
}
|
|
2346
|
-
settleInterruptedToolCalls(content
|
|
2374
|
+
settleInterruptedToolCalls(content, undefined, {
|
|
2375
|
+
includeActivity: true,
|
|
2376
|
+
});
|
|
2347
2377
|
content.push({
|
|
2348
2378
|
type: "text",
|
|
2349
2379
|
text: `Something went wrong: ${message}`,
|
|
@@ -80,21 +80,34 @@ export type AgentAutoContinueReason =
|
|
|
80
80
|
|
|
81
81
|
export type AgentActivityTrailEntry = { label: string; tool?: string };
|
|
82
82
|
|
|
83
|
+
export interface AgentAutoContinueErrorInfo {
|
|
84
|
+
message: string;
|
|
85
|
+
details?: string;
|
|
86
|
+
errorCode?: string;
|
|
87
|
+
recoverable?: boolean;
|
|
88
|
+
upgradeUrl?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
83
91
|
const INTERRUPTED_TOOL_RESULT =
|
|
84
92
|
"Interrupted before this tool returned a result.";
|
|
93
|
+
const INTERRUPTED_ACTIVITY_RESULT = "Stopped before this action started.";
|
|
85
94
|
|
|
86
95
|
export function settleInterruptedToolCalls(
|
|
87
96
|
content: ContentPart[],
|
|
88
97
|
result = INTERRUPTED_TOOL_RESULT,
|
|
98
|
+
options?: { includeActivity?: boolean; activityResult?: string },
|
|
89
99
|
): boolean {
|
|
90
100
|
let changed = false;
|
|
91
101
|
for (const part of content) {
|
|
92
102
|
if (
|
|
93
103
|
part.type === "tool-call" &&
|
|
94
104
|
part.result === undefined &&
|
|
95
|
-
part.activity !== true
|
|
105
|
+
(part.activity !== true || options?.includeActivity === true)
|
|
96
106
|
) {
|
|
97
|
-
part.result =
|
|
107
|
+
part.result =
|
|
108
|
+
part.activity === true
|
|
109
|
+
? (options?.activityResult ?? INTERRUPTED_ACTIVITY_RESULT)
|
|
110
|
+
: result;
|
|
98
111
|
changed = true;
|
|
99
112
|
}
|
|
100
113
|
}
|
|
@@ -105,17 +118,20 @@ export class AgentAutoContinueSignal extends Error {
|
|
|
105
118
|
readonly reason: AgentAutoContinueReason;
|
|
106
119
|
readonly maxIterations?: number;
|
|
107
120
|
readonly activityTrail: AgentActivityTrailEntry[];
|
|
121
|
+
readonly errorInfo?: AgentAutoContinueErrorInfo;
|
|
108
122
|
|
|
109
123
|
constructor(options: {
|
|
110
124
|
reason: AgentAutoContinueReason;
|
|
111
125
|
maxIterations?: number;
|
|
112
126
|
activityTrail?: AgentActivityTrailEntry[];
|
|
127
|
+
errorInfo?: AgentAutoContinueErrorInfo;
|
|
113
128
|
}) {
|
|
114
129
|
super(`Agent run needs automatic continuation: ${options.reason}`);
|
|
115
130
|
this.name = "AgentAutoContinueSignal";
|
|
116
131
|
this.reason = options.reason;
|
|
117
132
|
this.maxIterations = options.maxIterations;
|
|
118
133
|
this.activityTrail = options.activityTrail ?? [];
|
|
134
|
+
this.errorInfo = options.errorInfo;
|
|
119
135
|
}
|
|
120
136
|
}
|
|
121
137
|
|
|
@@ -342,6 +358,7 @@ export function processEvent(
|
|
|
342
358
|
autoContinue?: {
|
|
343
359
|
reason: AgentAutoContinueReason;
|
|
344
360
|
maxIterations?: number;
|
|
361
|
+
errorInfo?: AgentAutoContinueErrorInfo;
|
|
345
362
|
};
|
|
346
363
|
} {
|
|
347
364
|
if (ev.type === "clear") {
|
|
@@ -598,7 +615,7 @@ export function processEvent(
|
|
|
598
615
|
}),
|
|
599
616
|
);
|
|
600
617
|
}
|
|
601
|
-
settleInterruptedToolCalls(content);
|
|
618
|
+
settleInterruptedToolCalls(content, undefined, { includeActivity: true });
|
|
602
619
|
content.push({
|
|
603
620
|
type: "text",
|
|
604
621
|
text: formatChatErrorText(errMsg, undefined, errorCode),
|
|
@@ -665,6 +682,7 @@ export function processEvent(
|
|
|
665
682
|
(ev.errorCode === "run_timeout" && ev.recoverable) ||
|
|
666
683
|
isAutoRecoverableError(ev, errMsg)
|
|
667
684
|
) {
|
|
685
|
+
const normalized = normalizeChatError(errMsg, ev.errorCode);
|
|
668
686
|
return {
|
|
669
687
|
action: "auto_continue",
|
|
670
688
|
autoContinue: {
|
|
@@ -676,6 +694,15 @@ export function processEvent(
|
|
|
676
694
|
errMsg.toLowerCase().includes("timeout")
|
|
677
695
|
? "run_timeout"
|
|
678
696
|
: "stream_ended",
|
|
697
|
+
errorInfo: {
|
|
698
|
+
message: normalized.message,
|
|
699
|
+
...(ev.details || normalized.details
|
|
700
|
+
? { details: ev.details ?? normalized.details }
|
|
701
|
+
: {}),
|
|
702
|
+
...(ev.errorCode ? { errorCode: ev.errorCode } : {}),
|
|
703
|
+
recoverable: ev.recoverable ?? true,
|
|
704
|
+
...(ev.upgradeUrl ? { upgradeUrl: ev.upgradeUrl } : {}),
|
|
705
|
+
},
|
|
679
706
|
},
|
|
680
707
|
};
|
|
681
708
|
}
|
|
@@ -700,7 +727,7 @@ export function processEvent(
|
|
|
700
727
|
}),
|
|
701
728
|
);
|
|
702
729
|
}
|
|
703
|
-
settleInterruptedToolCalls(content);
|
|
730
|
+
settleInterruptedToolCalls(content, undefined, { includeActivity: true });
|
|
704
731
|
content.push({
|
|
705
732
|
type: "text",
|
|
706
733
|
text: formatChatErrorText(errMsg, ev.upgradeUrl, ev.errorCode),
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
name: reliable-mutations
|
|
3
3
|
description: >-
|
|
4
4
|
How the agent must perform writes so they actually persist under the hosted
|
|
5
|
-
|
|
6
|
-
data — especially "do this for many
|
|
7
|
-
expects N things to end up saved.
|
|
5
|
+
foreground run budget and long-running background handoffs. Use whenever you
|
|
6
|
+
create, update, delete, or batch-write app data — especially "do this for many
|
|
7
|
+
items" loops, or any task where the user expects N things to end up saved.
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Reliable Mutations
|
|
@@ -18,15 +18,12 @@ tool ✓ alone.
|
|
|
18
18
|
|
|
19
19
|
## Why
|
|
20
20
|
|
|
21
|
-
Hosted agent runs have a
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
tool call appeared to succeed. The user gets told "done" while the data says
|
|
28
|
-
otherwise. One atomic call commits or fails as a unit; verification turns a
|
|
29
|
-
hopeful ✓ into a fact.
|
|
21
|
+
Hosted foreground agent runs have a short soft budget so they can hand off
|
|
22
|
+
cleanly under synchronous serverless walls. Durable background runs get a much
|
|
23
|
+
longer budget, but they still should not rely on loops of many small writes:
|
|
24
|
+
continuations can retry the same intent and leave partial state if each item is
|
|
25
|
+
committed separately. One atomic call commits or fails as a unit; verification
|
|
26
|
+
turns a hopeful ✓ into a fact.
|
|
30
27
|
|
|
31
28
|
## How
|
|
32
29
|
|
|
@@ -34,11 +31,12 @@ hopeful ✓ into a fact.
|
|
|
34
31
|
many, set all, bulk update), pass the full batch in one call so it commits
|
|
35
32
|
atomically. Check the action surface for a batch/plural form before reaching
|
|
36
33
|
for a loop.
|
|
37
|
-
2. **Do not loop many small writes under
|
|
38
|
-
per-item writes
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
2. **Do not loop many small writes under any run budget.** A sequence of N
|
|
35
|
+
per-item writes can still leave partial or no state when a foreground run
|
|
36
|
+
hands off, a background run continues, or an upstream provider fails. If no
|
|
37
|
+
batch action exists, that is a gap in the action layer — add or extend an
|
|
38
|
+
action that accepts the batch (see the `actions` skill) rather than papering
|
|
39
|
+
over it with a loop.
|
|
42
40
|
3. **Verify the end state after writing.** Re-read the data (a list/read action,
|
|
43
41
|
a count query) and confirm the result matches intent — the right number of
|
|
44
42
|
rows, the expected ids/fields. Do this before you tell the user it worked.
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
getRequestOrgId,
|
|
21
21
|
getRequestUserEmail,
|
|
22
22
|
} from "@agent-native/core/server/request-context";
|
|
23
|
-
import {
|
|
23
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
24
24
|
import { and, desc, eq } from "drizzle-orm";
|
|
25
25
|
import { nanoid } from "nanoid";
|
|
26
26
|
import { z } from "zod";
|
|
@@ -70,6 +70,33 @@ const trackSchema = z.object({
|
|
|
70
70
|
|
|
71
71
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
72
72
|
|
|
73
|
+
function nonEmptyString(value: unknown): string | null {
|
|
74
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function resolveMotionTimelineInsertOwnership(args: {
|
|
78
|
+
requestUserEmail?: string | null;
|
|
79
|
+
requestOrgId?: string | null;
|
|
80
|
+
designOwnerEmail?: unknown;
|
|
81
|
+
designOrgId?: unknown;
|
|
82
|
+
}): { ownerEmail: string; orgId: string | null } {
|
|
83
|
+
const ownerEmail =
|
|
84
|
+
nonEmptyString(args.requestUserEmail) ??
|
|
85
|
+
nonEmptyString(args.designOwnerEmail);
|
|
86
|
+
|
|
87
|
+
if (!ownerEmail) throw new Error("no authenticated user");
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
ownerEmail,
|
|
91
|
+
orgId:
|
|
92
|
+
nonEmptyString(args.requestOrgId) ?? nonEmptyString(args.designOrgId),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function canPatchManagedMotionCss(content: string): boolean {
|
|
97
|
+
return /<\s*(?:!doctype|[a-z][a-z0-9:-]*(?:\s|>|\/>))/i.test(content);
|
|
98
|
+
}
|
|
99
|
+
|
|
73
100
|
async function persistFileContent(
|
|
74
101
|
fileId: string,
|
|
75
102
|
designId: string,
|
|
@@ -179,16 +206,13 @@ export default defineAction({
|
|
|
179
206
|
includeContent,
|
|
180
207
|
currentContent: currentContentInput,
|
|
181
208
|
}) => {
|
|
182
|
-
await assertAccess("design", designId, "editor");
|
|
209
|
+
const access = await assertAccess("design", designId, "editor");
|
|
183
210
|
|
|
184
211
|
const db = getDb();
|
|
185
212
|
const now = new Date().toISOString();
|
|
186
213
|
|
|
187
214
|
// ── 1. Resolve the target design file ──────────────────────────────────
|
|
188
|
-
const conditions = [
|
|
189
|
-
accessFilter(schema.designs, schema.designShares),
|
|
190
|
-
eq(schema.designFiles.designId, designId),
|
|
191
|
-
];
|
|
215
|
+
const conditions = [eq(schema.designFiles.designId, designId)];
|
|
192
216
|
if (fileIdInput) {
|
|
193
217
|
conditions.push(eq(schema.designFiles.id, fileIdInput));
|
|
194
218
|
} else {
|
|
@@ -256,7 +280,10 @@ export default defineAction({
|
|
|
256
280
|
});
|
|
257
281
|
|
|
258
282
|
// ── 3. Inject the managed CSS block into the HTML ───────────────────────
|
|
259
|
-
const
|
|
283
|
+
const contentPatched = canPatchManagedMotionCss(currentContent);
|
|
284
|
+
const patchedContent = contentPatched
|
|
285
|
+
? injectManagedMotionCss(currentContent, css)
|
|
286
|
+
: currentContent;
|
|
260
287
|
const bytesBefore = currentContent.length;
|
|
261
288
|
const bytesAfter = patchedContent.length;
|
|
262
289
|
|
|
@@ -303,11 +330,18 @@ export default defineAction({
|
|
|
303
330
|
if (existingForSource) {
|
|
304
331
|
existingTimelineId = existingForSource.id;
|
|
305
332
|
} else {
|
|
306
|
-
// Insert new row — derive ownership from the request context
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
333
|
+
// Insert new row — derive ownership from the request context, falling
|
|
334
|
+
// back to the already-authorized design owner for local/public editor
|
|
335
|
+
// sessions that do not carry an authenticated request user.
|
|
336
|
+
const insertOwnership = resolveMotionTimelineInsertOwnership({
|
|
337
|
+
requestUserEmail: getRequestUserEmail(),
|
|
338
|
+
requestOrgId: getRequestOrgId(),
|
|
339
|
+
designOwnerEmail: (access.resource as { ownerEmail?: unknown })
|
|
340
|
+
.ownerEmail,
|
|
341
|
+
designOrgId: (access.resource as { orgId?: unknown }).orgId,
|
|
342
|
+
});
|
|
343
|
+
insertOwnerEmail = insertOwnership.ownerEmail;
|
|
344
|
+
insertOrgId = insertOwnership.orgId;
|
|
311
345
|
}
|
|
312
346
|
}
|
|
313
347
|
|
|
@@ -352,12 +386,9 @@ export default defineAction({
|
|
|
352
386
|
// Written after the row so a SQL failure here leaves the timeline row
|
|
353
387
|
// accurate (correct tracks + hash) and the stale HTML can be recompiled on
|
|
354
388
|
// the next apply-motion-edit call via compiledHash drift detection.
|
|
355
|
-
const updatedAt =
|
|
356
|
-
fileId,
|
|
357
|
-
|
|
358
|
-
patchedContent,
|
|
359
|
-
now,
|
|
360
|
-
);
|
|
389
|
+
const updatedAt = contentPatched
|
|
390
|
+
? await persistFileContent(fileId, designId, patchedContent, now)
|
|
391
|
+
: now;
|
|
361
392
|
|
|
362
393
|
return {
|
|
363
394
|
timelineId: resolvedTimelineId,
|
|
@@ -371,6 +402,7 @@ export default defineAction({
|
|
|
371
402
|
bytesAfter,
|
|
372
403
|
bytesDelta: bytesAfter - bytesBefore,
|
|
373
404
|
persisted: true,
|
|
405
|
+
contentPatched,
|
|
374
406
|
patchedContent: includeContent ? patchedContent : undefined,
|
|
375
407
|
};
|
|
376
408
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { getText, hasCollabState } from "@agent-native/core/collab";
|
|
3
|
-
import {
|
|
3
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
4
4
|
import { and, desc, eq } from "drizzle-orm";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
@@ -67,13 +67,8 @@ async function readManagedCssForSource(args: {
|
|
|
67
67
|
content: schema.designFiles.content,
|
|
68
68
|
})
|
|
69
69
|
.from(schema.designFiles)
|
|
70
|
-
.innerJoin(
|
|
71
|
-
schema.designs,
|
|
72
|
-
eq(schema.designFiles.designId, schema.designs.id),
|
|
73
|
-
)
|
|
74
70
|
.where(
|
|
75
71
|
and(
|
|
76
|
-
accessFilter(schema.designs, schema.designShares),
|
|
77
72
|
eq(schema.designFiles.designId, args.designId),
|
|
78
73
|
eq(schema.designFiles.id, args.sourceRef),
|
|
79
74
|
),
|
|
@@ -124,12 +119,11 @@ export default defineAction({
|
|
|
124
119
|
),
|
|
125
120
|
}),
|
|
126
121
|
run: async ({ designId, timelineId, sourceRef }) => {
|
|
122
|
+
await assertAccess("design", designId, "viewer");
|
|
123
|
+
|
|
127
124
|
const db = getDb();
|
|
128
125
|
|
|
129
|
-
const conditions = [
|
|
130
|
-
accessFilter(schema.designs, schema.designShares),
|
|
131
|
-
eq(schema.motionTimeline.designId, designId),
|
|
132
|
-
];
|
|
126
|
+
const conditions = [eq(schema.motionTimeline.designId, designId)];
|
|
133
127
|
|
|
134
128
|
if (timelineId) {
|
|
135
129
|
conditions.push(eq(schema.motionTimeline.id, timelineId));
|
|
@@ -151,10 +145,6 @@ export default defineAction({
|
|
|
151
145
|
updatedAt: schema.motionTimeline.updatedAt,
|
|
152
146
|
})
|
|
153
147
|
.from(schema.motionTimeline)
|
|
154
|
-
.innerJoin(
|
|
155
|
-
schema.designs,
|
|
156
|
-
eq(schema.motionTimeline.designId, schema.designs.id),
|
|
157
|
-
)
|
|
158
148
|
.where(and(...conditions))
|
|
159
149
|
.orderBy(desc(schema.motionTimeline.updatedAt))
|
|
160
150
|
.limit(timelineId ? 1 : 100);
|