@agent-native/core 0.84.30 → 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 (29) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/session-replay.ts +5 -0
  5. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +13 -1
  6. package/corpus/templates/analytics/changelog/2026-07-01-session-replays-keep-inlined-css-without-live-resource-loa.md +6 -0
  7. package/corpus/templates/design/AGENTS.md +18 -0
  8. package/corpus/templates/design/actions/apply-source-edit.ts +87 -0
  9. package/corpus/templates/design/actions/list-source-files.ts +52 -0
  10. package/corpus/templates/design/actions/navigate.ts +3 -2
  11. package/corpus/templates/design/actions/preview-source-edit.ts +85 -0
  12. package/corpus/templates/design/actions/read-source-file.ts +55 -0
  13. package/corpus/templates/design/actions/resolve-selection-source.ts +101 -0
  14. package/corpus/templates/design/actions/view-screen.ts +43 -1
  15. package/corpus/templates/design/app/components/design/CodeWorkbenchHost.tsx +630 -0
  16. package/corpus/templates/design/app/hooks/use-navigation-state.ts +9 -6
  17. package/corpus/templates/design/app/pages/DesignEditor.tsx +144 -9
  18. package/corpus/templates/design/changelog/2026-07-01-design-code-workspace.md +6 -0
  19. package/corpus/templates/design/server/source-workspace.ts +215 -0
  20. package/corpus/templates/design/shared/design-source-capabilities.ts +5 -5
  21. package/corpus/templates/design/shared/source-workspace.ts +149 -0
  22. package/dist/client/session-replay.d.ts +1 -0
  23. package/dist/client/session-replay.d.ts.map +1 -1
  24. package/dist/client/session-replay.js +2 -0
  25. package/dist/client/session-replay.js.map +1 -1
  26. package/dist/progress/routes.d.ts +1 -1
  27. package/dist/resources/handlers.d.ts +1 -1
  28. package/dist/server/transcribe-voice.d.ts +1 -1
  29. 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: 4973
31
+ - template files: 4983
@@ -1,5 +1,17 @@
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
+
3
15
  ## 0.84.30
4
16
 
5
17
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.84.30",
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": {
@@ -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,
@@ -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
@@ -0,0 +1,85 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { z } from "zod";
3
+
4
+ import {
5
+ findSourceWorkspaceFile,
6
+ readLiveSourceFile,
7
+ resolveSourceWorkspace,
8
+ } from "../server/source-workspace.js";
9
+ import {
10
+ applySourceEdit,
11
+ previewSourceDiff,
12
+ } from "../shared/source-workspace.js";
13
+
14
+ const sourceEditSchema = z.discriminatedUnion("kind", [
15
+ z.object({
16
+ kind: z.literal("full-replace"),
17
+ content: z.string().describe("Complete replacement file content"),
18
+ }),
19
+ z.object({
20
+ kind: z.literal("exact-replace"),
21
+ search: z.string().min(1).describe("Unique exact text to replace"),
22
+ replace: z.string().describe("Replacement text"),
23
+ }),
24
+ ]);
25
+
26
+ export default defineAction({
27
+ description:
28
+ "Preview a source-file edit without saving it. Returns changed byte counts, " +
29
+ "line range, and compact before/after excerpts for Design code workspace diffs.",
30
+ schema: z
31
+ .object({
32
+ designId: z.string().describe("Design project ID"),
33
+ path: z
34
+ .string()
35
+ .optional()
36
+ .describe("Source path/filename, such as index.html"),
37
+ fileId: z.string().optional().describe("Design file ID"),
38
+ edit: sourceEditSchema,
39
+ expectedVersionHash: z
40
+ .string()
41
+ .optional()
42
+ .describe("Optional hash from read-source-file to detect stale edits"),
43
+ })
44
+ .refine((args) => args.path || args.fileId, {
45
+ message: "Provide either path or fileId.",
46
+ path: ["path"],
47
+ }),
48
+ readOnly: true,
49
+ run: async ({ designId, path, fileId, edit, expectedVersionHash }) => {
50
+ const workspace = await resolveSourceWorkspace(designId, {
51
+ includeContent: true,
52
+ });
53
+ const file = findSourceWorkspaceFile(workspace.files, { fileId, path });
54
+ const live = await readLiveSourceFile(file);
55
+ const stale =
56
+ expectedVersionHash !== undefined &&
57
+ expectedVersionHash !== live.versionHash;
58
+ if (stale) {
59
+ return {
60
+ designId,
61
+ path: file.filename,
62
+ fileId: file.id,
63
+ okToApply: false,
64
+ conflict: "stale-version",
65
+ currentVersionHash: live.versionHash,
66
+ message:
67
+ "Source file changed since it was read. Re-read before saving.",
68
+ };
69
+ }
70
+
71
+ const next = applySourceEdit(live.content, edit);
72
+ return {
73
+ designId,
74
+ path: file.filename,
75
+ fileId: file.id,
76
+ okToApply: workspace.canEdit && workspace.sourceType === "inline",
77
+ conflict:
78
+ workspace.sourceType === "inline" ? null : "unsupported-source-backend",
79
+ currentVersionHash: live.versionHash,
80
+ nextVersionHash: next.changed ? undefined : live.versionHash,
81
+ editsApplied: next.editsApplied,
82
+ diff: previewSourceDiff(live.content, next.content),
83
+ };
84
+ },
85
+ });
@@ -0,0 +1,55 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { z } from "zod";
3
+
4
+ import {
5
+ findSourceWorkspaceFile,
6
+ readLiveSourceFile,
7
+ resolveSourceWorkspace,
8
+ } from "../server/source-workspace.js";
9
+
10
+ export default defineAction({
11
+ description:
12
+ "Read one Design source file. For inline designs this returns live " +
13
+ "design_files content with a version hash for safe follow-up writes.",
14
+ schema: z
15
+ .object({
16
+ designId: z.string().describe("Design project ID"),
17
+ path: z
18
+ .string()
19
+ .optional()
20
+ .describe("Source path/filename, such as index.html"),
21
+ fileId: z.string().optional().describe("Design file ID"),
22
+ })
23
+ .refine((args) => args.path || args.fileId, {
24
+ message: "Provide either path or fileId.",
25
+ path: ["path"],
26
+ }),
27
+ readOnly: true,
28
+ http: { method: "GET" },
29
+ run: async ({ designId, path, fileId }) => {
30
+ const workspace = await resolveSourceWorkspace(designId, {
31
+ includeContent: true,
32
+ });
33
+ const file = findSourceWorkspaceFile(workspace.files, { fileId, path });
34
+ const live = await readLiveSourceFile(file);
35
+ return {
36
+ designId,
37
+ path: file.filename,
38
+ displayName: file.filename,
39
+ fileId: file.id,
40
+ sourceType: workspace.sourceType,
41
+ backendKind: "virtual-inline",
42
+ readonly: !workspace.canEdit || workspace.sourceType !== "inline",
43
+ language: live.language,
44
+ content: live.content,
45
+ versionHash: live.versionHash,
46
+ updatedAt: file.updatedAt,
47
+ provenance: {
48
+ kind: "design-file",
49
+ designId,
50
+ fileId: file.id,
51
+ filename: file.filename,
52
+ },
53
+ };
54
+ },
55
+ });
@@ -0,0 +1,101 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { z } from "zod";
3
+
4
+ import {
5
+ findSourceWorkspaceFile,
6
+ readLiveSourceFile,
7
+ resolveSourceWorkspace,
8
+ } from "../server/source-workspace.js";
9
+ import { buildCodeLayerProjection } from "../shared/code-layer.js";
10
+
11
+ function offsetToLineColumn(content: string, offset: number) {
12
+ let line = 1;
13
+ let column = 1;
14
+ for (let index = 0; index < offset; index += 1) {
15
+ if (content.charCodeAt(index) === 10) {
16
+ line += 1;
17
+ column = 1;
18
+ } else {
19
+ column += 1;
20
+ }
21
+ }
22
+ return { line, column };
23
+ }
24
+
25
+ export default defineAction({
26
+ description:
27
+ "Resolve a selected Design canvas node to the best source file location. " +
28
+ "Inline designs resolve to the containing design file and, when possible, " +
29
+ "a line/column/snippet from the code-layer projection.",
30
+ schema: z
31
+ .object({
32
+ designId: z.string().describe("Design project ID"),
33
+ path: z
34
+ .string()
35
+ .optional()
36
+ .describe("Source path/filename, such as index.html"),
37
+ fileId: z.string().optional().describe("Design file ID"),
38
+ nodeId: z
39
+ .string()
40
+ .optional()
41
+ .describe("data-agent-native-node-id or code-layer node id"),
42
+ selector: z.string().optional().describe("CSS selector fallback"),
43
+ })
44
+ .refine((args) => args.path || args.fileId, {
45
+ message: "Provide either path or fileId.",
46
+ path: ["path"],
47
+ }),
48
+ readOnly: true,
49
+ http: { method: "GET" },
50
+ run: async ({ designId, path, fileId, nodeId, selector }) => {
51
+ const workspace = await resolveSourceWorkspace(designId, {
52
+ includeContent: true,
53
+ });
54
+ const file = findSourceWorkspaceFile(workspace.files, { fileId, path });
55
+ const live = await readLiveSourceFile(file);
56
+ const projection = buildCodeLayerProjection(live.content, {
57
+ source: {
58
+ kind: "design-file",
59
+ designId,
60
+ fileId: file.id,
61
+ filename: file.filename,
62
+ },
63
+ });
64
+ const node =
65
+ projection.nodes.find(
66
+ (candidate) =>
67
+ candidate.id === nodeId ||
68
+ candidate.dataAttributes["data-agent-native-node-id"] === nodeId ||
69
+ candidate.dataAttributes["data-code-layer-id"] === nodeId,
70
+ ) ??
71
+ projection.nodes.find((candidate) =>
72
+ selector
73
+ ? candidate.selector === selector ||
74
+ candidate.path === selector ||
75
+ candidate.selectors.includes(selector)
76
+ : false,
77
+ );
78
+
79
+ const start = node?.source?.openStart ?? node?.source?.start;
80
+ const location =
81
+ typeof start === "number"
82
+ ? offsetToLineColumn(live.content, start)
83
+ : null;
84
+ const snippet =
85
+ node?.source && typeof node.source.start === "number"
86
+ ? live.content.slice(node.source.start, node.source.end).slice(0, 1200)
87
+ : undefined;
88
+
89
+ return {
90
+ designId,
91
+ sourceType: workspace.sourceType,
92
+ backendKind: "virtual-inline",
93
+ path: file.filename,
94
+ fileId: file.id,
95
+ line: location?.line ?? null,
96
+ column: location?.column ?? null,
97
+ snippet,
98
+ resolved: Boolean(node),
99
+ };
100
+ },
101
+ });
@@ -34,6 +34,20 @@ function stringArrayProp(value: unknown, key: string): string[] {
34
34
  : [];
35
35
  }
36
36
 
37
+ function boolProp(value: unknown, key: string): boolean | undefined {
38
+ if (!value || typeof value !== "object") return undefined;
39
+ const candidate = (value as Record<string, unknown>)[key];
40
+ return typeof candidate === "boolean" ? candidate : undefined;
41
+ }
42
+
43
+ function objectProp(value: unknown, key: string): Record<string, unknown> {
44
+ if (!value || typeof value !== "object") return {};
45
+ const candidate = (value as Record<string, unknown>)[key];
46
+ return candidate && typeof candidate === "object" && !Array.isArray(candidate)
47
+ ? (candidate as Record<string, unknown>)
48
+ : {};
49
+ }
50
+
37
51
  function resolveActiveScreen(
38
52
  files: Array<{
39
53
  id: string;
@@ -93,9 +107,36 @@ function resolveActiveScreen(
93
107
  return null;
94
108
  }
95
109
 
110
+ function resolveActiveCodeFile(
111
+ files: Array<{
112
+ id: string;
113
+ filename: string;
114
+ fileType: string | null;
115
+ updatedAt: string | null;
116
+ }>,
117
+ designSelection: unknown,
118
+ ) {
119
+ const codeWorkspace = objectProp(designSelection, "codeWorkspace");
120
+ if (Object.keys(codeWorkspace).length === 0) return null;
121
+ const fileId = stringProp(codeWorkspace, "activeFileId");
122
+ const path = stringProp(codeWorkspace, "activePath");
123
+ const file = files.find(
124
+ (candidate) => candidate.id === fileId || candidate.filename === path,
125
+ );
126
+ return {
127
+ open: boolProp(codeWorkspace, "open") ?? false,
128
+ backendKind: stringProp(codeWorkspace, "backendKind") ?? "virtual-inline",
129
+ path: path ?? file?.filename ?? null,
130
+ fileId: fileId ?? file?.id ?? null,
131
+ dirty: boolProp(codeWorkspace, "dirty") ?? false,
132
+ versionHash: stringProp(codeWorkspace, "versionHash") ?? null,
133
+ file: file ?? null,
134
+ };
135
+ }
136
+
96
137
  export default defineAction({
97
138
  description:
98
- "See what the user is currently looking at on screen. Returns the current navigation state including which design is open, which view they are on (list, editor, design-systems, present, settings), active/focused design screen, selected element, active inspector tab (design or tweaks), active left rail panel (file, agent, assets, tools, or tokens), overview canvas state, plus any pending question overlay. Always call this first before taking any action.",
139
+ "See what the user is currently looking at on screen. Returns the current navigation state including which design is open, which view they are on (list, editor, design-systems, present, settings), active/focused design screen, selected element, active inspector tab (design or tweaks), active left rail panel (file, agent, assets, tools, tokens, or code), active code file metadata, overview canvas state, plus any pending question overlay. Always call this first before taking any action.",
99
140
  schema: z.object({}),
100
141
  http: false,
101
142
  run: async () => {
@@ -154,6 +195,7 @@ export default defineAction({
154
195
  title: (access.resource as { title?: unknown }).title ?? null,
155
196
  screens: files,
156
197
  activeScreen: resolveActiveScreen(files, navigation, designSelection),
198
+ activeCodeFile: resolveActiveCodeFile(files, designSelection),
157
199
  canvasFrames: parseCanvasFrameGeometryById(data.canvasFrames),
158
200
  };
159
201
  }