@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.
Files changed (75) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +7 -0
  3. package/corpus/core/docs/design/durable-agent-runs.md +34 -35
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/agent/engine/builder-engine.ts +81 -9
  6. package/corpus/core/src/agent/run-manager.ts +8 -7
  7. package/corpus/core/src/cli/design-connect.ts +175 -0
  8. package/corpus/core/src/cli/skills.ts +5 -7
  9. package/corpus/core/src/client/AgentPanel.tsx +8 -0
  10. package/corpus/core/src/client/MultiTabAssistantChat.tsx +4 -1
  11. package/corpus/core/src/client/agent-chat-adapter.ts +45 -15
  12. package/corpus/core/src/client/sse-event-processor.ts +31 -4
  13. package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
  14. package/corpus/templates/design/actions/apply-motion-edit.ts +50 -18
  15. package/corpus/templates/design/actions/get-motion-timeline.ts +4 -14
  16. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +156 -12
  17. package/corpus/templates/design/app/components/design/MotionDock.tsx +2 -3
  18. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +217 -75
  19. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +39 -39
  20. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +558 -37
  21. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +75 -3
  22. package/corpus/templates/design/app/components/design/types.ts +13 -0
  23. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
  24. package/corpus/templates/design/app/i18n/zh-TW.ts +17 -0
  25. package/corpus/templates/design/app/i18n-data.ts +245 -0
  26. package/corpus/templates/design/app/pages/DesignEditor.tsx +762 -153
  27. package/corpus/templates/design/changelog/2026-06-30-copying-or-dragging-screen-elements-onto-the-infinite-canvas.md +6 -0
  28. package/corpus/templates/design/changelog/2026-06-30-design-questions-now-use-tighter-editor-typography-and-contr.md +6 -0
  29. package/corpus/templates/design/changelog/2026-06-30-element-drags-can-be-cancelled-with-escape-before-they-commi.md +6 -0
  30. package/corpus/templates/design/changelog/2026-06-30-pending-visual-style-edits-now-warn-before-you-leave-the-edi.md +6 -0
  31. package/corpus/templates/design/changelog/2026-06-30-visual-style-drags-stay-live-while-pending-edits-can-be-appl.md +6 -0
  32. package/corpus/templates/design/changelog/2026-07-01-design-chat-no-longer-shows-a-redundant-context-tab-above-the-composer.md +6 -0
  33. package/corpus/templates/design/changelog/2026-07-01-motion-track-creation-no-longer-fails-in-local-editors.md +6 -0
  34. package/corpus/templates/design/server/plugins/db.ts +10 -0
  35. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  36. package/dist/agent/engine/builder-engine.js +61 -10
  37. package/dist/agent/engine/builder-engine.js.map +1 -1
  38. package/dist/agent/run-manager.d.ts +8 -7
  39. package/dist/agent/run-manager.d.ts.map +1 -1
  40. package/dist/agent/run-manager.js +8 -7
  41. package/dist/agent/run-manager.js.map +1 -1
  42. package/dist/cli/design-connect.d.ts +2 -0
  43. package/dist/cli/design-connect.d.ts.map +1 -1
  44. package/dist/cli/design-connect.js +140 -0
  45. package/dist/cli/design-connect.js.map +1 -1
  46. package/dist/cli/skills.d.ts.map +1 -1
  47. package/dist/cli/skills.js +5 -7
  48. package/dist/cli/skills.js.map +1 -1
  49. package/dist/client/AgentPanel.d.ts +5 -1
  50. package/dist/client/AgentPanel.d.ts.map +1 -1
  51. package/dist/client/AgentPanel.js +4 -4
  52. package/dist/client/AgentPanel.js.map +1 -1
  53. package/dist/client/MultiTabAssistantChat.d.ts +3 -1
  54. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  55. package/dist/client/MultiTabAssistantChat.js +2 -2
  56. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  57. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  58. package/dist/client/agent-chat-adapter.js +37 -14
  59. package/dist/client/agent-chat-adapter.js.map +1 -1
  60. package/dist/client/sse-event-processor.d.ts +14 -1
  61. package/dist/client/sse-event-processor.d.ts.map +1 -1
  62. package/dist/client/sse-event-processor.js +21 -5
  63. package/dist/client/sse-event-processor.js.map +1 -1
  64. package/dist/collab/awareness.d.ts +2 -2
  65. package/dist/collab/awareness.d.ts.map +1 -1
  66. package/dist/collab/routes.d.ts +2 -2
  67. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  68. package/dist/notifications/routes.d.ts +3 -3
  69. package/dist/observability/routes.d.ts +2 -2
  70. package/dist/resources/handlers.d.ts +2 -2
  71. package/dist/server/transcribe-voice.d.ts +1 -1
  72. package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
  73. package/docs/design/durable-agent-runs.md +34 -35
  74. package/package.json +1 -1
  75. 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), or the activity trail shows the server was working
1690
- // on a tool. A run_timeout that fires in this window means the agent
1691
- // was actively making progress the server's foldAssistantTurn
1692
- // persisted the in-flight call — so it must NOT count against the
1693
- // stalled/empty continuation budgets.
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 message = exhaustedRecoveryMessage(err.reason);
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: connectionRecoveryDetails(),
2184
- errorCode: "connection_error",
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: `Something went wrong: ${message}`,
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 = 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
- ~40s run budget. Use whenever you create, update, delete, or batch-write app
6
- data — especially "do this for many items" loops, or any task where the user
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 ~40s soft budget (it exists to hand off cleanly under
22
- the upstream gateway/function walls it is correct and is not raisable; see the
23
- durable-agent-runs design doc). When you loop many sequential writes inside one
24
- turn, the budget can cut you off mid-loop. The run resumes in a new chunk, but
25
- the resume often re-does earlier steps instead of advancing, so the loop churns
26
- and the *saved* result ends up partial — or empty — even though each individual
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 the run budget.** A sequence of N
38
- per-item writes in one turn will race the ~40s cutoff and can leave partial
39
- or no state. If no batch action exists, that is a gap in the action layer —
40
- add or extend an action that accepts the batch (see the `actions` skill)
41
- rather than papering over it with a loop.
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 { accessFilter, assertAccess } from "@agent-native/core/sharing";
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 patchedContent = injectManagedMotionCss(currentContent, css);
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 (same
307
- // pattern as create-design-state and other create actions).
308
- insertOwnerEmail = getRequestUserEmail() ?? null;
309
- if (!insertOwnerEmail) throw new Error("no authenticated user");
310
- insertOrgId = getRequestOrgId() ?? null;
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 = await persistFileContent(
356
- fileId,
357
- designId,
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 { accessFilter } from "@agent-native/core/sharing";
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);