@agent-native/core 0.84.29 → 0.84.32

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 (44) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +19 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/chat/tool-call-display.tsx +50 -8
  5. package/corpus/core/src/client/session-replay.ts +5 -0
  6. package/corpus/core/src/client/sse-event-processor.ts +17 -4
  7. package/corpus/core/src/server/ssr-handler.ts +152 -8
  8. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +13 -1
  9. package/corpus/templates/analytics/changelog/2026-07-01-session-replays-keep-inlined-css-without-live-resource-loa.md +6 -0
  10. package/corpus/templates/design/AGENTS.md +18 -0
  11. package/corpus/templates/design/actions/apply-source-edit.ts +87 -0
  12. package/corpus/templates/design/actions/list-source-files.ts +52 -0
  13. package/corpus/templates/design/actions/navigate.ts +3 -2
  14. package/corpus/templates/design/actions/preview-source-edit.ts +85 -0
  15. package/corpus/templates/design/actions/read-source-file.ts +55 -0
  16. package/corpus/templates/design/actions/resolve-selection-source.ts +101 -0
  17. package/corpus/templates/design/actions/view-screen.ts +43 -1
  18. package/corpus/templates/design/app/components/design/CodeWorkbenchHost.tsx +630 -0
  19. package/corpus/templates/design/app/hooks/use-navigation-state.ts +9 -6
  20. package/corpus/templates/design/app/pages/DesignEditor.tsx +189 -11
  21. package/corpus/templates/design/changelog/2026-07-01-design-code-workspace.md +6 -0
  22. package/corpus/templates/design/changelog/2026-07-01-design-previews-refresh-immediately-after-agent-screen-edits.md +6 -0
  23. package/corpus/templates/design/server/source-workspace.ts +215 -0
  24. package/corpus/templates/design/shared/design-source-capabilities.ts +5 -5
  25. package/corpus/templates/design/shared/source-workspace.ts +149 -0
  26. package/dist/client/chat/tool-call-display.d.ts +1 -0
  27. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  28. package/dist/client/chat/tool-call-display.js +22 -5
  29. package/dist/client/chat/tool-call-display.js.map +1 -1
  30. package/dist/client/session-replay.d.ts +1 -0
  31. package/dist/client/session-replay.d.ts.map +1 -1
  32. package/dist/client/session-replay.js +2 -0
  33. package/dist/client/session-replay.js.map +1 -1
  34. package/dist/client/sse-event-processor.d.ts.map +1 -1
  35. package/dist/client/sse-event-processor.js +13 -4
  36. package/dist/client/sse-event-processor.js.map +1 -1
  37. package/dist/collab/routes.d.ts +1 -1
  38. package/dist/notifications/routes.d.ts +1 -1
  39. package/dist/observability/routes.d.ts +5 -5
  40. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  41. package/dist/server/ssr-handler.d.ts.map +1 -1
  42. package/dist/server/ssr-handler.js +111 -8
  43. package/dist/server/ssr-handler.js.map +1 -1
  44. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 2043
31
- - template files: 4972
31
+ - template files: 4983
@@ -1,5 +1,24 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.84.32
4
+
5
+ ### Patch Changes
6
+
7
+ - da8ac0a: Keep tail-resume reconnect content display-only, recover zero-byte action-prep stalls, and polish Design screen tool labels.
8
+
9
+ ## 0.84.31
10
+
11
+ ### Patch Changes
12
+
13
+ - 3190dea: Inline rrweb stylesheet snapshots by default so Analytics session replay captures can play back styled pages without live CSS fetches.
14
+
15
+ ## 0.84.30
16
+
17
+ ### Patch Changes
18
+
19
+ - 80e618a: Fix hosted Google Analytics / Tag Manager injection by baking the measurement id into Nitro server bundles and merging the required GA/GTM script, connect, and image hosts into existing stricter document CSPs.
20
+ - 80e618a: Improve chat tool-preparation UX by hiding zero-byte progress, using clearer preparation/writing copy, and showing a delayed long-running update hint.
21
+
3
22
  ## 0.84.29
4
23
 
5
24
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.84.29",
3
+ "version": "0.84.32",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -70,6 +70,43 @@ export const ApprovalContext = React.createContext<ApprovalContextValue | null>(
70
70
  null,
71
71
  );
72
72
 
73
+ export const TOOL_LONG_RUNNING_HINT_DELAY_MS = 45_000;
74
+
75
+ function ToolLongRunningHintShell({
76
+ toolName,
77
+ isRunning,
78
+ children,
79
+ }: {
80
+ toolName: string;
81
+ isRunning: boolean;
82
+ children: React.ReactNode;
83
+ }) {
84
+ const [showLongRunningHint, setShowLongRunningHint] = useState(false);
85
+
86
+ useEffect(() => {
87
+ if (!isRunning) {
88
+ setShowLongRunningHint(false);
89
+ return;
90
+ }
91
+ setShowLongRunningHint(false);
92
+ const timeout = window.setTimeout(() => {
93
+ setShowLongRunningHint(true);
94
+ }, TOOL_LONG_RUNNING_HINT_DELAY_MS);
95
+ return () => window.clearTimeout(timeout);
96
+ }, [isRunning, toolName]);
97
+
98
+ return (
99
+ <>
100
+ {children}
101
+ {isRunning && showLongRunningHint && (
102
+ <div className="mt-0.5 px-2.5 text-[11px] leading-snug text-muted-foreground/80">
103
+ Still working. Large updates can take a minute or two.
104
+ </div>
105
+ )}
106
+ </>
107
+ );
108
+ }
109
+
73
110
  // ─── Tool-payload formatting ──────────────────────────────────────────────────
74
111
 
75
112
  type ToolDetailSection = "input" | "result";
@@ -435,38 +472,43 @@ export function ToolCallDisplay({
435
472
  // These must be separate components so hook order in ToolCallDisplayGeneric
436
473
  // is always stable (no conditional hook calls).
437
474
  const toolKind = structuredMeta?.toolKind as string | undefined;
475
+ const wrapToolDisplay = (children: React.ReactNode) => (
476
+ <ToolLongRunningHintShell toolName={toolName} isRunning={isRunning}>
477
+ {children}
478
+ </ToolLongRunningHintShell>
479
+ );
438
480
  if (toolKind === "bash") {
439
- return (
481
+ return wrapToolDisplay(
440
482
  <BashCell
441
483
  meta={
442
484
  structuredMeta as unknown as Parameters<typeof BashCell>[0]["meta"]
443
485
  }
444
486
  output={result}
445
487
  isRunning={isRunning}
446
- />
488
+ />,
447
489
  );
448
490
  }
449
491
  if (toolKind === "edit") {
450
- return (
492
+ return wrapToolDisplay(
451
493
  <EditCell
452
494
  meta={
453
495
  structuredMeta as unknown as Parameters<typeof EditCell>[0]["meta"]
454
496
  }
455
497
  isRunning={isRunning}
456
- />
498
+ />,
457
499
  );
458
500
  }
459
501
  if (toolKind === "write") {
460
- return (
502
+ return wrapToolDisplay(
461
503
  <WriteCell
462
504
  meta={
463
505
  structuredMeta as unknown as Parameters<typeof WriteCell>[0]["meta"]
464
506
  }
465
507
  isRunning={isRunning}
466
- />
508
+ />,
467
509
  );
468
510
  }
469
- return (
511
+ return wrapToolDisplay(
470
512
  <ToolCallDisplayGeneric
471
513
  toolName={toolName}
472
514
  argsText={argsText}
@@ -477,7 +519,7 @@ export function ToolCallDisplay({
477
519
  isRunning={isRunning}
478
520
  approval={approval}
479
521
  repeatCount={repeatCount}
480
- />
522
+ />,
481
523
  );
482
524
  }
483
525
 
@@ -19,6 +19,7 @@ interface RrwebRecordOptions {
19
19
  emit: (event: ReplayEvent) => void;
20
20
  checkoutEveryNth?: number;
21
21
  checkoutEveryNms?: number;
22
+ inlineStylesheet?: boolean;
22
23
  blockClass?: string | RegExp;
23
24
  blockSelector?: string;
24
25
  ignoreClass?: string | RegExp;
@@ -82,6 +83,7 @@ export interface SessionReplayOptions {
82
83
  maxBatchBytes?: number;
83
84
  checkoutEveryNth?: number;
84
85
  checkoutEveryNms?: number;
86
+ inlineStylesheet?: boolean;
85
87
  blockSelector?: string;
86
88
  ignoreSelector?: string;
87
89
  maskTextClass?: string | RegExp;
@@ -135,6 +137,7 @@ interface NormalizedSessionReplayOptions {
135
137
  maxBatchBytes: number;
136
138
  checkoutEveryNth?: number;
137
139
  checkoutEveryNms?: number;
140
+ inlineStylesheet: boolean;
138
141
  blockSelector: string;
139
142
  ignoreSelector: string;
140
143
  maskTextClass: string | RegExp;
@@ -509,6 +512,7 @@ function normalizeOptions(
509
512
  ),
510
513
  checkoutEveryNth: options.checkoutEveryNth,
511
514
  checkoutEveryNms: options.checkoutEveryNms,
515
+ inlineStylesheet: options.inlineStylesheet ?? true,
512
516
  blockSelector: options.blockSelector || DEFAULT_BLOCK_SELECTOR,
513
517
  ignoreSelector: options.ignoreSelector || DEFAULT_IGNORE_SELECTOR,
514
518
  maskTextClass: options.maskTextClass || DEFAULT_MASK_TEXT_CLASS,
@@ -1010,6 +1014,7 @@ async function startSessionReplayRecorder(
1010
1014
  sampling: normalized.eventSampling,
1011
1015
  checkoutEveryNth: normalized.checkoutEveryNth,
1012
1016
  checkoutEveryNms: normalized.checkoutEveryNms,
1017
+ inlineStylesheet: normalized.inlineStylesheet,
1013
1018
  blockSelector: normalized.blockSelector,
1014
1019
  ignoreSelector: normalized.ignoreSelector,
1015
1020
  maskTextClass: normalized.maskTextClass,
@@ -205,13 +205,26 @@ function baseActivityLabel(ev: SSEEvent, tool?: string): string {
205
205
  return humanizeToolLabelText(ev.label ?? "Working", tool);
206
206
  }
207
207
 
208
+ function preparationActivityLabel(
209
+ tool: string | undefined,
210
+ progressBytes: number | undefined,
211
+ ): string {
212
+ const action = humanizeToolName(tool);
213
+ if (progressBytes === undefined) {
214
+ return `Starting ${action}...`;
215
+ }
216
+ if (progressBytes <= 0) {
217
+ return `Preparing ${action}...`;
218
+ }
219
+ return `Writing ${action}... (${formatProgressBytes(progressBytes)} prepared)`;
220
+ }
221
+
208
222
  function visibleActivityLabel(ev: SSEEvent, tool?: string): string {
209
- const label = baseActivityLabel(ev, tool);
210
223
  const progressBytes = activityProgressBytes(ev);
211
- if (progressBytes === undefined || !isPreparingActionActivity(ev)) {
212
- return label;
224
+ if (isPreparingActionActivity(ev)) {
225
+ return preparationActivityLabel(tool, progressBytes);
213
226
  }
214
- return `${label} (${formatProgressBytes(progressBytes)} streamed)`;
227
+ return baseActivityLabel(ev, tool);
215
228
  }
216
229
 
217
230
  function findPendingToolCallIndex(
@@ -277,6 +277,59 @@ type CspDirective = {
277
277
  tokens: string[];
278
278
  };
279
279
 
280
+ const CSP_DIRECTIVES_WITH_VALUE_TOKENS = new Set([
281
+ "base-uri",
282
+ "block-all-mixed-content",
283
+ "child-src",
284
+ "connect-src",
285
+ "default-src",
286
+ "fenced-frame-src",
287
+ "font-src",
288
+ "form-action",
289
+ "frame-ancestors",
290
+ "frame-src",
291
+ "img-src",
292
+ "manifest-src",
293
+ "media-src",
294
+ "navigate-to",
295
+ "object-src",
296
+ "plugin-types",
297
+ "prefetch-src",
298
+ "referrer",
299
+ "reflected-xss",
300
+ "require-sri-for",
301
+ "require-trusted-types-for",
302
+ "report-to",
303
+ "report-uri",
304
+ "sandbox",
305
+ "script-src",
306
+ "script-src-attr",
307
+ "script-src-elem",
308
+ "style-src",
309
+ "style-src-attr",
310
+ "style-src-elem",
311
+ "trusted-types",
312
+ "upgrade-insecure-requests",
313
+ "webrtc",
314
+ "worker-src",
315
+ ]);
316
+
317
+ function hasCommaJoinedCspPolicies(policy: string): boolean {
318
+ let commaIndex = policy.indexOf(",");
319
+ while (commaIndex !== -1) {
320
+ const afterComma = policy.slice(commaIndex + 1);
321
+ const directive = /^\s+([a-z][a-z0-9-]*)(?=\s|;|$)/i.exec(afterComma)?.[1];
322
+ if (
323
+ directive &&
324
+ CSP_DIRECTIVES_WITH_VALUE_TOKENS.has(directive.toLowerCase())
325
+ ) {
326
+ return true;
327
+ }
328
+ commaIndex = policy.indexOf(",", commaIndex + 1);
329
+ }
330
+ return false;
331
+ }
332
+
280
333
  function parseCsp(policy: string): CspDirective[] {
281
334
  return policy
282
335
  .split(";")
@@ -349,13 +402,96 @@ function appendToExistingCspDirective(
349
402
  existing.tokens = appendCspTokens(existing.tokens, additions);
350
403
  }
351
404
 
352
- function augmentExistingCspForFrameworkScripts(
405
+ function hasStrictNonceScriptPolicy(tokens: readonly string[]): boolean {
406
+ return tokens.some(
407
+ (token) => token === "'strict-dynamic'" || token.startsWith("'nonce-"),
408
+ );
409
+ }
410
+
411
+ function appendToScriptCspDirective(
412
+ directives: CspDirective[],
413
+ name: string,
414
+ additions: readonly string[],
415
+ ): boolean {
416
+ const existing = findCspDirective(directives, name);
417
+ if (existing) {
418
+ if (hasStrictNonceScriptPolicy(existing.tokens)) return false;
419
+ existing.tokens = appendCspTokens(existing.tokens, additions);
420
+ return true;
421
+ }
422
+
423
+ const defaultSrc = findCspDirective(directives, "default-src");
424
+ if (!defaultSrc || hasStrictNonceScriptPolicy(defaultSrc.tokens)) {
425
+ return false;
426
+ }
427
+ directives.push({
428
+ name,
429
+ tokens: appendCspTokens([...defaultSrc.tokens], additions),
430
+ });
431
+ return true;
432
+ }
433
+
434
+ function appendToEffectiveScriptElementCspDirective(
435
+ directives: CspDirective[],
436
+ additions: readonly string[],
437
+ ): boolean {
438
+ const scriptSrcElem = findCspDirective(directives, "script-src-elem");
439
+ if (scriptSrcElem) {
440
+ if (hasStrictNonceScriptPolicy(scriptSrcElem.tokens)) return false;
441
+ scriptSrcElem.tokens = appendCspTokens(scriptSrcElem.tokens, additions);
442
+ return true;
443
+ }
444
+
445
+ return appendToScriptCspDirective(directives, "script-src", additions);
446
+ }
447
+
448
+ function augmentExistingEnforcedCspForFrameworkScripts(
449
+ policy: string,
450
+ options: {
451
+ gaScriptSrcTokens: readonly string[];
452
+ gaEnabled: boolean;
453
+ },
454
+ ): string {
455
+ // Multiple CSP headers are surfaced by Headers.get() as one comma-joined
456
+ // string. CSP is not a comma-list header, so serializing a parsed combined
457
+ // value would turn two policies into one invalid policy. Leave those headers
458
+ // app-owned; a comma inside a source/report URL is still safe to parse.
459
+ if (hasCommaJoinedCspPolicies(policy)) return policy;
460
+
461
+ const directives = parseCsp(policy);
462
+ if (!directives.length) return policy;
463
+
464
+ if (options.gaEnabled) {
465
+ const addedScriptElement = appendToEffectiveScriptElementCspDirective(
466
+ directives,
467
+ options.gaScriptSrcTokens,
468
+ );
469
+ if (addedScriptElement) {
470
+ appendToExistingOrDefaultCspDirective(
471
+ directives,
472
+ "connect-src",
473
+ GA_CSP_CONNECT_HOSTS,
474
+ );
475
+ appendToExistingOrDefaultCspDirective(
476
+ directives,
477
+ "img-src",
478
+ GA_CSP_IMG_HOSTS,
479
+ );
480
+ }
481
+ }
482
+
483
+ return serializeCsp(directives);
484
+ }
485
+
486
+ function augmentExistingReportOnlyCspForFrameworkScripts(
353
487
  policy: string,
354
488
  options: {
355
489
  scriptSrcTokens: readonly string[];
356
490
  gaEnabled: boolean;
357
491
  },
358
492
  ): string {
493
+ if (hasCommaJoinedCspPolicies(policy)) return policy;
494
+
359
495
  const directives = parseCsp(policy);
360
496
  if (!directives.length) return policy;
361
497
 
@@ -364,7 +500,6 @@ function augmentExistingCspForFrameworkScripts(
364
500
  "script-src",
365
501
  options.scriptSrcTokens,
366
502
  );
367
- // `script-src-elem` overrides `script-src` for script tags when present.
368
503
  appendToExistingCspDirective(
369
504
  directives,
370
505
  "script-src-elem",
@@ -409,10 +544,11 @@ function augmentExistingCspForFrameworkScripts(
409
544
  * instead of reporting a violation on every page load.
410
545
  *
411
546
  * If an app or host already sends an enforced CSP with `script-src`,
412
- * `script-src-elem`, `connect-src`, `img-src`, or `default-src`, we merge the
413
- * framework's GA/GTM allowances into the existing directive. That keeps
414
- * stricter deployments working without adding a new enforced script policy to
415
- * routes that only declare unrelated directives such as `frame-ancestors`.
547
+ * `script-src-elem`, `connect-src`, `img-src`, or `default-src`, we merge only
548
+ * GA-specific allowances into existing host/hash policies. Strict nonce or
549
+ * `strict-dynamic` script policies stay app-owned because blindly appending
550
+ * hashes or hosts would widen the policy without reliably loading our injected
551
+ * scripts.
416
552
  *
417
553
  * Templates additionally render a theme-init inline script whose exact content
418
554
  * varies by template (default theme param, custom docs variant, etc.) and which
@@ -440,6 +576,7 @@ function applyDocumentCsp(headers: Headers, sentryScript: string | null): void {
440
576
  const gaInlineBody = getGaInlineConfigScriptBody();
441
577
  const gaHash = gaInlineBody ? computeInlineScriptHash(gaInlineBody) : null;
442
578
  const gaHosts = gaInlineBody ? [...GA_CSP_SCRIPT_HOSTS] : [];
579
+ const gaScriptSrcTokens = [...(gaHash ? [gaHash] : []), ...gaHosts];
443
580
  const scriptSrcTokens = [
444
581
  "'self'",
445
582
  ...(sentryHash ? [sentryHash] : []),
@@ -449,6 +586,7 @@ function applyDocumentCsp(headers: Headers, sentryScript: string | null): void {
449
586
 
450
587
  const cspAugmentOptions = {
451
588
  scriptSrcTokens,
589
+ gaScriptSrcTokens,
452
590
  gaEnabled: Boolean(gaInlineBody),
453
591
  };
454
592
  const existing = headers.get("content-security-policy") ?? "";
@@ -460,7 +598,10 @@ function applyDocumentCsp(headers: Headers, sentryScript: string | null): void {
460
598
  } else {
461
599
  headers.set(
462
600
  "content-security-policy",
463
- augmentExistingCspForFrameworkScripts(existing, cspAugmentOptions),
601
+ augmentExistingEnforcedCspForFrameworkScripts(
602
+ existing,
603
+ cspAugmentOptions,
604
+ ),
464
605
  );
465
606
  }
466
607
 
@@ -471,7 +612,10 @@ function applyDocumentCsp(headers: Headers, sentryScript: string | null): void {
471
612
  } else {
472
613
  headers.set(
473
614
  "content-security-policy-report-only",
474
- augmentExistingCspForFrameworkScripts(existingRo, cspAugmentOptions),
615
+ augmentExistingReportOnlyCspForFrameworkScripts(
616
+ existingRo,
617
+ cspAugmentOptions,
618
+ ),
475
619
  );
476
620
  }
477
621
  }
@@ -1243,7 +1243,14 @@ function sanitizeCssText(value: string): string {
1243
1243
  if (!containsStylesheetNetworkLoad(value)) return value;
1244
1244
  return value
1245
1245
  .replace(/@import\s+(?:url\s*\()?[^;{}]+;?/gi, "")
1246
- .replace(/\burl\s*\((?:\\.|[^\\)])*\)/gi, "none");
1246
+ .replace(/\burl\s*\(\s*((?:\\.|[^\\)])*)\)/gi, sanitizeCssUrlToken);
1247
+ }
1248
+
1249
+ function sanitizeCssUrlToken(match: string, rawValue: string): string {
1250
+ const urlValue = rawValue.trim();
1251
+ const unquoted = urlValue.replace(/^(['"])(.*)\1$/, "$2").trim();
1252
+ if (/^(?:data:|blob:|#)/i.test(unquoted)) return match;
1253
+ return "none";
1247
1254
  }
1248
1255
 
1249
1256
  function sanitizeAttributes(attributes: AnyRecord): AnyRecord {
@@ -1258,6 +1265,11 @@ function sanitizeAttributes(attributes: AnyRecord): AnyRecord {
1258
1265
  if (style.trim()) next[key] = style;
1259
1266
  continue;
1260
1267
  }
1268
+ if (normalized === "_csstext") {
1269
+ const cssText = sanitizeCssText(String(value));
1270
+ if (cssText.trim()) next[key] = cssText;
1271
+ continue;
1272
+ }
1261
1273
  next[key] = value;
1262
1274
  }
1263
1275
  return next;
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-01
4
+ ---
5
+
6
+ Session replay playback keeps rrweb's inlined stylesheet snapshots while stripping live resource loads, reducing blank replay frames for captured pages with external CSS.
@@ -191,6 +191,24 @@ patterns live in `.agents/skills/`.
191
191
  mode can list and resolve local routes now, but file reads/writes remain a
192
192
  bridge contract until explicit permission controls are hardened.
193
193
 
194
+ ## Code Workspace
195
+
196
+ - The editor left rail has a wide `code` workspace panel. Open it with
197
+ `navigate --view editor --designId <id> --leftPanel code` and optionally pass
198
+ `fileId`, `filename`, or `screen` to focus a file.
199
+ - Use `list-source-files` to inspect the source workspace. For the MVP the
200
+ backend is `virtual-inline` over SQL-backed `design_files`, exposed as
201
+ `designfs://<designId>/`.
202
+ - Use `read-source-file` for file contents and preserve its `versionHash` before
203
+ writing. Do not return full file content from `view-screen`; it reports only
204
+ active code file metadata and dirty state.
205
+ - Use `preview-source-edit` to show a diff without saving, then
206
+ `apply-source-edit` with the prior `versionHash` to save either a full replace
207
+ or exact replace. These actions update the same inline file state as the UI.
208
+ - Use `resolve-selection-source` when the user has a canvas element selected and
209
+ you need the best matching inline file location/snippet. Localhost/container
210
+ source reads and writes remain future backend work.
211
+
194
212
  ## Localhost Source Actions
195
213
 
196
214
  - `connect-localhost`: registers or refreshes a localhost source connection
@@ -0,0 +1,87 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { z } from "zod";
3
+
4
+ import {
5
+ findSourceWorkspaceFile,
6
+ readLiveSourceFile,
7
+ resolveSourceWorkspace,
8
+ writeInlineSourceFile,
9
+ } from "../server/source-workspace.js";
10
+ import {
11
+ applySourceEdit,
12
+ previewSourceDiff,
13
+ } from "../shared/source-workspace.js";
14
+
15
+ const sourceEditSchema = z.discriminatedUnion("kind", [
16
+ z.object({
17
+ kind: z.literal("full-replace"),
18
+ content: z.string().describe("Complete replacement file content"),
19
+ }),
20
+ z.object({
21
+ kind: z.literal("exact-replace"),
22
+ search: z.string().min(1).describe("Unique exact text to replace"),
23
+ replace: z.string().describe("Replacement text"),
24
+ }),
25
+ ]);
26
+
27
+ export default defineAction({
28
+ description:
29
+ "Apply a source-file edit through the shared Design source surface. In the " +
30
+ "MVP this writes inline design_files only and rejects stale version hashes.",
31
+ schema: z
32
+ .object({
33
+ designId: z.string().describe("Design project ID"),
34
+ path: z
35
+ .string()
36
+ .optional()
37
+ .describe("Source path/filename, such as index.html"),
38
+ fileId: z.string().optional().describe("Design file ID"),
39
+ edit: sourceEditSchema,
40
+ expectedVersionHash: z
41
+ .string()
42
+ .optional()
43
+ .describe("Hash returned by read-source-file or preview-source-edit"),
44
+ })
45
+ .refine((args) => args.path || args.fileId, {
46
+ message: "Provide either path or fileId.",
47
+ path: ["path"],
48
+ }),
49
+ run: async ({ designId, path, fileId, edit, expectedVersionHash }) => {
50
+ const workspace = await resolveSourceWorkspace(designId, {
51
+ includeContent: true,
52
+ });
53
+ if (workspace.sourceType !== "inline") {
54
+ throw new Error("Only inline Design files are editable in this MVP.");
55
+ }
56
+ const file = findSourceWorkspaceFile(workspace.files, { fileId, path });
57
+ const live = await readLiveSourceFile(file);
58
+ if (
59
+ expectedVersionHash !== undefined &&
60
+ expectedVersionHash !== live.versionHash
61
+ ) {
62
+ throw new Error(
63
+ "Source file changed since it was read. Re-read the file and retry.",
64
+ );
65
+ }
66
+
67
+ const next = applySourceEdit(live.content, edit);
68
+ const write = await writeInlineSourceFile({
69
+ designId,
70
+ file,
71
+ content: next.content,
72
+ expectedVersionHash: expectedVersionHash ?? live.versionHash,
73
+ });
74
+
75
+ return {
76
+ designId,
77
+ path: file.filename,
78
+ fileId: file.id,
79
+ backendKind: "virtual-inline",
80
+ changed: write.changed,
81
+ editsApplied: next.editsApplied,
82
+ versionHash: write.versionHash,
83
+ updatedAt: write.updatedAt,
84
+ diff: previewSourceDiff(live.content, next.content),
85
+ };
86
+ },
87
+ });
@@ -0,0 +1,52 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { z } from "zod";
3
+
4
+ import { resolveSourceWorkspace } from "../server/source-workspace.js";
5
+
6
+ export default defineAction({
7
+ description:
8
+ "List source files for a Design code workspace. In the current MVP this " +
9
+ "returns inline SQL-backed design_files by filename; future localhost and " +
10
+ "container source backends will use the same shape.",
11
+ schema: z.object({
12
+ designId: z.string().describe("Design project ID"),
13
+ }),
14
+ readOnly: true,
15
+ http: { method: "GET" },
16
+ run: async ({ designId }) => {
17
+ const workspace = await resolveSourceWorkspace(designId);
18
+ const readonly = !workspace.canEdit || workspace.sourceType !== "inline";
19
+ return {
20
+ designId,
21
+ backend: {
22
+ kind: "virtual-inline" as const,
23
+ workspaceUri: `designfs://${designId}/`,
24
+ designId,
25
+ capabilities: {
26
+ readFile: true,
27
+ writeFile: workspace.canEdit && workspace.sourceType === "inline",
28
+ diff: true,
29
+ },
30
+ },
31
+ sourceType: workspace.sourceType,
32
+ files: workspace.files.map((file) => ({
33
+ path: file.filename,
34
+ displayName: file.filename,
35
+ kind: "file" as const,
36
+ sourceType: workspace.sourceType,
37
+ fileId: file.id,
38
+ readonly,
39
+ reason: readonly
40
+ ? workspace.canEdit
41
+ ? "Only inline Design files are editable in this MVP."
42
+ : "You need editor access to change this file."
43
+ : undefined,
44
+ language:
45
+ file.fileType === "html" || file.fileType === "css"
46
+ ? file.fileType
47
+ : undefined,
48
+ updatedAt: file.updatedAt,
49
+ })),
50
+ };
51
+ },
52
+ });
@@ -18,7 +18,7 @@
18
18
  * --designId Design ID (for editor/present views)
19
19
  * --editorView Editor mode for designs: single or overview
20
20
  * --inspectorTab Inspector tab for designs: design or tweaks (extensions opens Tools for compatibility)
21
- * --leftPanel Left editor panel: file, agent, assets, tools, or tokens
21
+ * --leftPanel Left editor panel: file, agent, assets, tools, tokens, or code
22
22
  * --fileId Screen/file id to focus in the design editor
23
23
  * --filename Screen filename to focus in the design editor
24
24
  * --tool Design editor tool to activate
@@ -53,11 +53,12 @@ const designLeftPanelSchema = z.enum([
53
53
  "assets",
54
54
  "tools",
55
55
  "tokens",
56
+ "code",
56
57
  ]);
57
58
 
58
59
  export default defineAction({
59
60
  description:
60
- "Navigate the UI to a specific view or path. Views: list, editor, design-systems, present, settings. Use --designId with editor/present views and --designSystemId with design-systems. For designs, use editorView=overview to show the infinite screens canvas, or editorView=single with fileId/filename/screen to focus a screen. Use leftPanel=file|agent|assets|tools|tokens to focus the Figma-style left rail. Legacy inspectorTab=extensions opens Tools. Use tool to activate a design editor tool.",
61
+ "Navigate the UI to a specific view or path. Views: list, editor, design-systems, present, settings. Use --designId with editor/present views and --designSystemId with design-systems. For designs, use editorView=overview to show the infinite screens canvas, or editorView=single with fileId/filename/screen to focus a screen. Use leftPanel=file|agent|assets|tools|tokens|code to focus the left rail, including the wide Code workspace. Legacy inspectorTab=extensions opens Tools. Use tool to activate a design editor tool.",
61
62
  schema: z
62
63
  .object({
63
64
  view: z