@agent-native/core 0.182.1 → 0.182.2-nightly-20260917225811

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 (119) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/app/root.tsx +59 -10
  3. package/corpus/templates/clips/changelog/2026-09-17-desktop-popover-closes-when-clicking-away.md +6 -0
  4. package/corpus/templates/clips/changelog/2026-09-17-self-hosted-extension-sign-in.md +6 -0
  5. package/corpus/templates/clips/chrome-extension/src/background.ts +45 -25
  6. package/corpus/templates/clips/chrome-extension/src/content-script.ts +43 -0
  7. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +55 -6
  8. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +107 -19
  9. package/corpus/templates/design/.generated/bridge/hit-test.generated.ts +1 -0
  10. package/corpus/templates/design/actions/add-localhost-screens.ts +54 -22
  11. package/corpus/templates/design/actions/apply-component-prop-edit.ts +174 -14
  12. package/corpus/templates/design/actions/apply-visual-edit.ts +3 -1
  13. package/corpus/templates/design/actions/create-component.ts +173 -12
  14. package/corpus/templates/design/actions/create-design-from-template.ts +50 -35
  15. package/corpus/templates/design/actions/create-design-version.ts +21 -0
  16. package/corpus/templates/design/actions/create-file.ts +35 -33
  17. package/corpus/templates/design/actions/delete-design.ts +29 -28
  18. package/corpus/templates/design/actions/delete-file.ts +154 -23
  19. package/corpus/templates/design/actions/generate-design.ts +28 -16
  20. package/corpus/templates/design/actions/get-component-details.ts +272 -33
  21. package/corpus/templates/design/actions/get-design-version.ts +9 -1
  22. package/corpus/templates/design/actions/go-to-main-component.ts +37 -35
  23. package/corpus/templates/design/actions/index-components.ts +241 -126
  24. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +12 -9
  25. package/corpus/templates/design/actions/present-design-variants.ts +68 -72
  26. package/corpus/templates/design/actions/rename-component.ts +294 -0
  27. package/corpus/templates/design/actions/rename-screen.ts +10 -10
  28. package/corpus/templates/design/actions/update-file.ts +327 -330
  29. package/corpus/templates/design/actions/update-screen-source.ts +2 -43
  30. package/corpus/templates/design/app/components/design/EditPanel.tsx +17 -1
  31. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +228 -24
  32. package/corpus/templates/design/app/components/design/ResponsiveInteractBar.tsx +17 -1
  33. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +151 -32
  34. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +4 -0
  35. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +1 -1
  36. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +8 -1
  37. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +55 -33
  38. package/corpus/templates/design/app/components/design/code-workbench/workspace/create-providers.ts +2 -0
  39. package/corpus/templates/design/app/components/design/code-workbench/workspace/inline-provider.ts +4 -2
  40. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +273 -60
  41. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +9 -2
  42. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +57 -9
  43. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +13 -0
  44. package/corpus/templates/design/app/components/design/multi-screen/types.ts +14 -0
  45. package/corpus/templates/design/app/components/design/types.ts +9 -1
  46. package/corpus/templates/design/app/hooks/use-design-versions.ts +3 -2
  47. package/corpus/templates/design/app/i18n-breakpoints.ts +0 -110
  48. package/corpus/templates/design/app/i18n-data.ts +0 -10
  49. package/corpus/templates/design/app/pages/DesignEditor.tsx +669 -161
  50. package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +20 -0
  51. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +2 -0
  52. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +21 -3
  53. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +2 -0
  54. package/corpus/templates/design/app/pages/design-editor/commands/create-component.ts +44 -3
  55. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +69 -47
  56. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +157 -52
  57. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +76 -1
  58. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +152 -44
  59. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +131 -2
  60. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +5 -1
  61. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +26 -1
  62. package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +1 -0
  63. package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +58 -8
  64. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +63 -2
  65. package/corpus/templates/design/app/pages/design-editor/history.ts +3 -0
  66. package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +74 -22
  67. package/corpus/templates/design/app/pages/design-editor/source-publication.ts +14 -2
  68. package/corpus/templates/design/changelog/2026-09-16-design-components-retain-linked-identities-when-edited-and-d.md +6 -0
  69. package/corpus/templates/design/changelog/2026-09-16-fix-design-nested-layer-dragging-and-screen-duplication-pari.md +6 -0
  70. package/corpus/templates/design/changelog/2026-09-17-design-drag-and-drop-keeps-duplicates-cross-screen-moves-and.md +6 -0
  71. package/corpus/templates/design/changelog/2026-09-17-design-preserves-reusable-component-identity-and-props-acros.md +6 -0
  72. package/corpus/templates/design/changelog/2026-09-17-open-vector-paths-no-longer-show-fills-and-interact-dimensio.md +6 -0
  73. package/corpus/templates/design/changelog/2026-09-17-screen-deletion-is-immediate-and-undoable-with-history-that-.md +6 -0
  74. package/corpus/templates/design/e2e/fixtures/design-component-react/index.html +98 -0
  75. package/corpus/templates/design/e2e/fixtures/design-component-react/inline-component.html +65 -0
  76. package/corpus/templates/design/e2e/helpers.ts +6 -1
  77. package/corpus/templates/design/server/lib/design-data-mutation.ts +9 -5
  78. package/corpus/templates/design/server/lib/design-snapshot.ts +28 -16
  79. package/corpus/templates/design/server/lib/design-versions.ts +208 -130
  80. package/corpus/templates/design/server/lib/fusion-screens.ts +46 -20
  81. package/corpus/templates/design/server/lib/import-design-files.ts +5 -1
  82. package/corpus/templates/design/server/lib/localhost-connection.ts +54 -0
  83. package/corpus/templates/design/server/source-workspace.ts +412 -356
  84. package/corpus/templates/design/shared/component-library.ts +36 -6
  85. package/corpus/templates/design/shared/component-model.ts +12 -2
  86. package/corpus/templates/design/shared/local-jsx-visual-edit.ts +231 -5
  87. package/corpus/templates/design/shared/source-mode.ts +22 -0
  88. package/dist/automation/index.d.ts +1 -1
  89. package/dist/client/AssistantChat.d.ts +4 -1
  90. package/dist/client/AssistantChat.js +26 -3
  91. package/dist/client/chat/agentkit-protocol.js +148 -34
  92. package/dist/client/chat/message-components.js +7 -5
  93. package/dist/client/chat/tool-call-display.js +4 -3
  94. package/dist/client/conversation/AgentConversation.js +4 -2
  95. package/dist/client/tool-display.d.ts +9 -0
  96. package/dist/client/tool-display.js +15 -0
  97. package/dist/collab/awareness.d.ts +2 -2
  98. package/dist/collab/routes.d.ts +1 -1
  99. package/dist/localization/core-messages/ar-SA.js +2 -0
  100. package/dist/localization/core-messages/de-DE.js +2 -0
  101. package/dist/localization/core-messages/en-US.d.ts +2 -0
  102. package/dist/localization/core-messages/en-US.js +2 -0
  103. package/dist/localization/core-messages/es-ES.js +2 -0
  104. package/dist/localization/core-messages/fr-FR.js +2 -0
  105. package/dist/localization/core-messages/hi-IN.js +2 -0
  106. package/dist/localization/core-messages/ja-JP.js +2 -0
  107. package/dist/localization/core-messages/ko-KR.js +2 -0
  108. package/dist/localization/core-messages/pt-BR.js +2 -0
  109. package/dist/localization/core-messages/zh-CN.js +2 -0
  110. package/dist/localization/core-messages/zh-TW.js +2 -0
  111. package/dist/localization/core-messages.d.ts +2 -0
  112. package/dist/notifications/routes.d.ts +3 -3
  113. package/dist/observability/routes.d.ts +3 -3
  114. package/dist/resources/handlers.d.ts +1 -1
  115. package/dist/secrets/routes.d.ts +3 -3
  116. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  117. package/dist/server/core-routes-plugin.d.ts +1 -1
  118. package/package.json +3 -3
  119. package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +0 -66
@@ -22,7 +22,11 @@ import {
22
22
  buildCodeLayerProjection,
23
23
  type CodeLayerSource,
24
24
  } from "./code-layer.js";
25
- import { componentNameFor, isComponentInstance } from "./component-model.js";
25
+ import {
26
+ COMPONENT_ID_ATTR,
27
+ COMPONENT_REF_ATTR,
28
+ componentNameFor,
29
+ } from "./component-model.js";
26
30
 
27
31
  export interface ComponentLibraryFile {
28
32
  id: string;
@@ -34,6 +38,10 @@ export interface ComponentLibraryFile {
34
38
  export interface ComponentLibraryEntry {
35
39
  /** Component name from `data-agent-native-component`. */
36
40
  name: string;
41
+ /** Opaque id on a canonical component root, when present. */
42
+ componentId?: string;
43
+ /** Opaque id referenced by a linked component instance, when present. */
44
+ componentRef?: string;
37
45
  fileId: string;
38
46
  filename: string;
39
47
  /** `data-agent-native-node-id` (or fallback) of the instance root. */
@@ -50,7 +58,12 @@ export interface ComponentLibraryEntry {
50
58
  export function scanComponentLibrary(
51
59
  files: ComponentLibraryFile[],
52
60
  ): ComponentLibraryEntry[] {
53
- const entries: ComponentLibraryEntry[] = [];
61
+ const entries: Array<
62
+ Omit<ComponentLibraryEntry, "name"> & {
63
+ name?: string;
64
+ }
65
+ > = [];
66
+ const namesByIdentity = new Map<string, string>();
54
67
 
55
68
  for (const file of files) {
56
69
  const html = file.content ?? "";
@@ -68,9 +81,15 @@ export function scanComponentLibrary(
68
81
  });
69
82
 
70
83
  for (const node of projection.nodes) {
71
- if (!isComponentInstance(node)) continue;
72
- const name = componentNameFor(node);
73
- if (!name) continue;
84
+ const name = componentNameFor(node) ?? undefined;
85
+ const componentId =
86
+ node.dataAttributes[COMPONENT_ID_ATTR]?.trim() || undefined;
87
+ const componentRef =
88
+ node.dataAttributes[COMPONENT_REF_ATTR]?.trim() || undefined;
89
+ if (!name && !componentId && !componentRef) continue;
90
+
91
+ if (name && componentId) namesByIdentity.set(componentId, name);
92
+ if (name && componentRef) namesByIdentity.set(componentRef, name);
74
93
 
75
94
  // `node.id` is an ephemeral id scoped to this one projection call — it
76
95
  // is NOT the same as the durable `data-agent-native-node-id` attribute
@@ -85,6 +104,8 @@ export function scanComponentLibrary(
85
104
 
86
105
  entries.push({
87
106
  name,
107
+ componentId,
108
+ componentRef,
88
109
  fileId: file.id,
89
110
  filename: file.filename,
90
111
  nodeId,
@@ -93,7 +114,16 @@ export function scanComponentLibrary(
93
114
  }
94
115
  }
95
116
 
96
- return entries;
117
+ return entries.flatMap((entry) => {
118
+ const name =
119
+ entry.name ??
120
+ (entry.componentRef
121
+ ? namesByIdentity.get(entry.componentRef)
122
+ : entry.componentId
123
+ ? namesByIdentity.get(entry.componentId)
124
+ : undefined);
125
+ return name ? [{ ...entry, name }] : [];
126
+ });
97
127
  }
98
128
 
99
129
  /**
@@ -44,6 +44,15 @@ export const COMPONENT_OVERRIDES_ATTR = "data-agent-native-component-overrides";
44
44
  /** Prefix for simple prop attributes stamped next to the component root. */
45
45
  export const COMPONENT_PROP_PREFIX = "data-agent-native-prop-";
46
46
 
47
+ /** Return the identity that survives projection rebuilds and reloads. */
48
+ export function stableComponentNodeId(node: CodeLayerNode): string {
49
+ return node.dataAttributes["data-agent-native-node-id"]?.trim() || node.id;
50
+ }
51
+
52
+ export function componentIndexId(designId: string, name: string): string {
53
+ return `ci_${designId}_${name.toLowerCase().replace(/[^a-z0-9]/g, "_")}`;
54
+ }
55
+
47
56
  // ─── Extracted prop value ─────────────────────────────────────────────────────
48
57
 
49
58
  export interface ComponentPropValue {
@@ -228,14 +237,15 @@ export function instanceFromNode(
228
237
  const alpineDataRaw = node.attributes["x-data"];
229
238
  const alpineData =
230
239
  typeof alpineDataRaw === "string" ? alpineDataRaw : undefined;
240
+ const stableNodeId = stableComponentNodeId(node);
231
241
 
232
242
  return {
233
- instanceId: node.id,
243
+ instanceId: stableNodeId,
234
244
  name,
235
245
  props: extractProps(node),
236
246
  alpineData,
237
247
  selector: node.selector,
238
- nodeId: node.id,
248
+ nodeId: stableNodeId,
239
249
  componentIndexId,
240
250
  componentId: node.dataAttributes[COMPONENT_ID_ATTR]?.trim() || undefined,
241
251
  componentRef: node.dataAttributes[COMPONENT_REF_ATTR]?.trim() || undefined,
@@ -32,7 +32,18 @@ export type LocalJsxLeafIntent =
32
32
  from?: string;
33
33
  to?: string;
34
34
  }
35
- | { kind: "style"; property: string; value: string };
35
+ | { kind: "style"; property: string; value: string }
36
+ | {
37
+ kind: "attribute";
38
+ name: string;
39
+ value: string;
40
+ expectedValue?: string;
41
+ }
42
+ | {
43
+ kind: "attributes";
44
+ values: Record<string, string>;
45
+ expectedValues?: Record<string, string | undefined>;
46
+ };
36
47
 
37
48
  export interface LocalJsxVisualEditResult {
38
49
  content: string;
@@ -51,6 +62,23 @@ interface OpeningTag {
51
62
  selfClosing: boolean;
52
63
  }
53
64
 
65
+ export interface LocalJsxLiteralProp {
66
+ name: string;
67
+ value: string;
68
+ }
69
+
70
+ function isEscaped(value: string, index: number): boolean {
71
+ let backslashes = 0;
72
+ for (
73
+ let cursor = index - 1;
74
+ cursor >= 0 && value[cursor] === "\\";
75
+ cursor--
76
+ ) {
77
+ backslashes += 1;
78
+ }
79
+ return backslashes % 2 === 1;
80
+ }
81
+
54
82
  function offsetAt(
55
83
  content: string,
56
84
  line: number,
@@ -108,7 +136,7 @@ function scanOpeningTags(content: string): OpeningTag[] {
108
136
  ) {
109
137
  const quote = content[start];
110
138
  for (start += 1; start < content.length; start += 1) {
111
- if (content[start] === quote && content[start - 1] !== "\\") break;
139
+ if (content[start] === quote && !isEscaped(content, start)) break;
112
140
  }
113
141
  continue;
114
142
  }
@@ -127,7 +155,7 @@ function scanOpeningTags(content: string): OpeningTag[] {
127
155
  ) {
128
156
  const char = content[index] ?? "";
129
157
  if (quote) {
130
- if (char === quote && content[index - 1] !== "\\") quote = "";
158
+ if (char === quote && !isEscaped(content, index)) quote = "";
131
159
  continue;
132
160
  }
133
161
  if (char === '"' || char === "'") {
@@ -153,6 +181,104 @@ function scanOpeningTags(content: string): OpeningTag[] {
153
181
  return tags;
154
182
  }
155
183
 
184
+ /**
185
+ * Read quoted component props from the authored opening tag at a verified
186
+ * source anchor. Dynamic and shorthand attributes make the full literal prop
187
+ * set uncertain, so this reader fails closed; spreads can shadow every prop.
188
+ */
189
+ export function readLiteralJsxPropsAtAnchor(args: {
190
+ content: string;
191
+ anchor: LocalJsxSourceAnchor;
192
+ }): LocalJsxLiteralProp[] | undefined {
193
+ const { content, anchor } = args;
194
+ if (
195
+ anchor.positionPrecision !== "authored" ||
196
+ (anchor.runtimeMultiplicity ?? 1) !== 1 ||
197
+ anchor.scope === "repeated-render" ||
198
+ anchor.scope === "shared-component-definition"
199
+ ) {
200
+ return undefined;
201
+ }
202
+ if (offsetAt(content, anchor.line, anchor.column) === null) return undefined;
203
+ const candidates = scanOpeningTags(content).filter((tag) => {
204
+ const position = positionAt(content, tag.start);
205
+ return (
206
+ position.line === anchor.line &&
207
+ Math.abs(position.column - anchor.column) <= 1
208
+ );
209
+ });
210
+ if (candidates.length !== 1) return undefined;
211
+
212
+ const tag = candidates[0]!;
213
+ const opening = content.slice(tag.start, tag.end);
214
+ if (/\{\s*\.\.\./.test(opening)) return undefined;
215
+
216
+ const props: LocalJsxLiteralProp[] = [];
217
+ const reserved = new Set(["children", "key", "ref"]);
218
+ let sawUnsupported = false;
219
+ let index = 1 + tag.name.length;
220
+ while (index < opening.length - 1) {
221
+ while (/\s/.test(opening[index] ?? "")) index += 1;
222
+ if (opening[index] === ">" || opening.slice(index, index + 2) === "/>")
223
+ break;
224
+ if (opening[index] === "{") {
225
+ let depth = 0;
226
+ do {
227
+ const char = opening[index];
228
+ if (char === "{") depth += 1;
229
+ else if (char === "}") depth -= 1;
230
+ index += 1;
231
+ } while (index < opening.length && depth > 0);
232
+ if (depth > 0) return undefined;
233
+ sawUnsupported = true;
234
+ continue;
235
+ }
236
+
237
+ const nameMatch = /^[A-Za-z_:][A-Za-z0-9:_.-]*/.exec(opening.slice(index));
238
+ if (!nameMatch) return undefined;
239
+ const name = nameMatch[0]!;
240
+ index += name.length;
241
+ while (/\s/.test(opening[index] ?? "")) index += 1;
242
+ if (opening[index] !== "=") {
243
+ return undefined;
244
+ }
245
+ index += 1;
246
+ while (/\s/.test(opening[index] ?? "")) index += 1;
247
+ const quote = opening[index];
248
+ if (quote !== '"' && quote !== "'") {
249
+ let depth = 0;
250
+ if (opening[index] === "{") {
251
+ do {
252
+ const char = opening[index];
253
+ if (char === "{") depth += 1;
254
+ else if (char === "}") depth -= 1;
255
+ index += 1;
256
+ } while (index < opening.length && depth > 0);
257
+ }
258
+ if (depth > 0) return undefined;
259
+ sawUnsupported = true;
260
+ continue;
261
+ }
262
+ index += 1;
263
+ const valueStart = index;
264
+ while (
265
+ index < opening.length &&
266
+ (opening[index] !== quote || isEscaped(opening, index))
267
+ ) {
268
+ index += 1;
269
+ }
270
+ if (index >= opening.length) return undefined;
271
+ if (!reserved.has(name)) {
272
+ props.push({
273
+ name,
274
+ value: decodeJsxAttributeValue(opening.slice(valueStart, index)),
275
+ });
276
+ }
277
+ index += 1;
278
+ }
279
+ return sawUnsupported && props.length === 0 ? undefined : props;
280
+ }
281
+
156
282
  function fail(
157
283
  content: string,
158
284
  status: LocalJsxVisualEditResult["result"]["status"],
@@ -190,6 +316,32 @@ function jsxText(value: string): string {
190
316
  .join("&#125;");
191
317
  }
192
318
 
319
+ function jsxAttributeValue(value: string): string {
320
+ return value
321
+ .split("&")
322
+ .join("&amp;")
323
+ .split('"')
324
+ .join("&quot;")
325
+ .split("<")
326
+ .join("&lt;")
327
+ .split(">")
328
+ .join("&gt;");
329
+ }
330
+
331
+ function decodeJsxAttributeValue(value: string): string {
332
+ return value
333
+ .replace(/&quot;/g, '"')
334
+ .replace(/&apos;/g, "'")
335
+ .replace(/&lt;/g, "<")
336
+ .replace(/&gt;/g, ">")
337
+ .replace(/&amp;/g, "&");
338
+ }
339
+
340
+ function jsxAttributeName(name: string): string | null {
341
+ const normalized = name.trim();
342
+ return /^[A-Za-z_:][A-Za-z0-9:_.-]*$/.test(normalized) ? normalized : null;
343
+ }
344
+
193
345
  function camelProperty(property: string): string | null {
194
346
  const value = property
195
347
  .trim()
@@ -203,11 +355,11 @@ export function planLocalJsxVisualEdit(args: {
203
355
  intent: LocalJsxLeafIntent;
204
356
  }): LocalJsxVisualEditResult {
205
357
  const { content, anchor, intent } = args;
206
- if (anchor.positionPrecision === "transformed") {
358
+ if (anchor.positionPrecision !== "authored") {
207
359
  return fail(
208
360
  content,
209
361
  "needsAgent",
210
- "The source anchor's line and column are the dev server's transformed coordinates (React 19 exposes no authored position), so they cannot be seeked in the authored file.",
362
+ "The source anchor does not have verified authored coordinates, so it cannot be seeked safely in the authored file.",
211
363
  );
212
364
  }
213
365
  if (
@@ -353,6 +505,80 @@ export function planLocalJsxVisualEdit(args: {
353
505
  );
354
506
  }
355
507
 
508
+ if (intent.kind === "attribute" || intent.kind === "attributes") {
509
+ const values =
510
+ intent.kind === "attribute"
511
+ ? { [intent.name]: intent.value }
512
+ : intent.values;
513
+ const entries = Object.entries(values);
514
+ if (entries.length === 0) {
515
+ return fail(
516
+ content,
517
+ "unsupported",
518
+ "At least one JSX attribute is required.",
519
+ );
520
+ }
521
+
522
+ let nextOpening = opening;
523
+ for (const [rawName, value] of entries) {
524
+ const name = jsxAttributeName(rawName);
525
+ if (!name) {
526
+ return fail(
527
+ content,
528
+ "unsupported",
529
+ `The JSX attribute name "${rawName}" is not a safe literal identifier.`,
530
+ );
531
+ }
532
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
533
+ const literal = new RegExp(
534
+ `(\\s${escapedName}\\s*=\\s*)(["'])((?:\\\\.|(?!\\2)[\\s\\S])*)\\2`,
535
+ "s",
536
+ );
537
+ const literalMatch = literal.exec(nextOpening);
538
+ const expectedValue =
539
+ intent.kind === "attribute" && rawName === intent.name
540
+ ? intent.expectedValue
541
+ : intent.kind === "attributes"
542
+ ? intent.expectedValues?.[rawName]
543
+ : undefined;
544
+ if (expectedValue !== undefined) {
545
+ if (
546
+ !literalMatch ||
547
+ decodeJsxAttributeValue(literalMatch[3] ?? "") !== expectedValue
548
+ ) {
549
+ return fail(
550
+ content,
551
+ "conflict",
552
+ `The JSX prop "${name}" changed since the selected instance was read. Refresh and retry.`,
553
+ );
554
+ }
555
+ }
556
+ if (literalMatch) {
557
+ nextOpening = nextOpening.replace(
558
+ literal,
559
+ (_match, prefix: string) => `${prefix}"${jsxAttributeValue(value)}"`,
560
+ );
561
+ continue;
562
+ }
563
+ if (new RegExp(`\\s${escapedName}(?:\\s|=|/?>)`).test(nextOpening)) {
564
+ return fail(
565
+ content,
566
+ "needsAgent",
567
+ `The JSX attribute "${name}" is dynamic or shorthand and requires semantic source inspection.`,
568
+ );
569
+ }
570
+ const insertAt = nextOpening.lastIndexOf(tag.selfClosing ? "/>" : ">");
571
+ nextOpening = `${nextOpening.slice(0, insertAt)} ${name}="${jsxAttributeValue(value)}"${nextOpening.slice(insertAt)}`;
572
+ }
573
+ return replaceRange(
574
+ content,
575
+ tag.start,
576
+ tag.end,
577
+ nextOpening,
578
+ "Literal JSX attributes updated.",
579
+ );
580
+ }
581
+
356
582
  const property = camelProperty(intent.property);
357
583
  if (!property)
358
584
  return fail(
@@ -117,6 +117,28 @@ export type ElementProvenanceFramework =
117
117
  | "angular"
118
118
  | "lwc";
119
119
 
120
+ /**
121
+ * Runtime component boundary discovered by a framework bridge. This is
122
+ * deliberately separate from the durable component annotations used by the
123
+ * inline component model: the bridge can identify an unannotated React
124
+ * boundary before promotion, while promotion still writes the canonical
125
+ * `data-agent-native-component` marker.
126
+ */
127
+ export interface RuntimeComponentIdentity {
128
+ componentId: string;
129
+ instanceId: string;
130
+ name: string;
131
+ framework: ElementProvenanceFramework;
132
+ sourceFile?: string;
133
+ line?: number;
134
+ column?: number;
135
+ method?: ElementProvenanceMethod;
136
+ ownerKey?: string;
137
+ props: Array<{ name: string; value: string }>;
138
+ writeCapability: "authored-jsx-literal" | "unsupported";
139
+ reason?: string;
140
+ }
141
+
120
142
  export const ELEMENT_PROVENANCE_METHODS: readonly ElementProvenanceMethod[] = [
121
143
  "data-attribute",
122
144
  "debug-source",
@@ -186,8 +186,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
186
186
  duplicate?: undefined;
187
187
  eventId?: undefined;
188
188
  } | {
189
- error?: undefined;
190
189
  accepted: boolean;
191
190
  duplicate: boolean;
192
191
  eventId: string;
192
+ error?: undefined;
193
193
  }>;
@@ -168,8 +168,9 @@ export declare function resolveAssistantChatRunningState({ forceStopped, isRunti
168
168
  isRunning: boolean;
169
169
  showRunningInUI: boolean;
170
170
  };
171
- export declare function resolveAssistantChatRunningStatusLabel({ runningActivityLabel, isAutoResuming, isReconnecting, hasReconnectContent, labels, }: {
171
+ export declare function resolveAssistantChatRunningStatusLabel({ runningActivityLabel, runningActivityTool, isAutoResuming, isReconnecting, hasReconnectContent, labels, }: {
172
172
  runningActivityLabel: string | null | undefined;
173
+ runningActivityTool?: string | null;
173
174
  isAutoResuming: boolean;
174
175
  isReconnecting: boolean;
175
176
  hasReconnectContent: boolean;
@@ -183,6 +184,8 @@ export declare function resolveAssistantChatRunningStatusLabel({ runningActivity
183
184
  preparing?: (activity: string) => string;
184
185
  writing?: (activity: string) => string;
185
186
  stillGenerating?: (activity: string) => string;
187
+ runningTool?: (toolName: string) => string;
188
+ toolDisplayName?: (toolName: string) => string;
186
189
  };
187
190
  }): string;
188
191
  export declare function resolveAssistantChatComposerPlaceholder(composerPlaceholder: string | null | undefined): string;
@@ -43,6 +43,7 @@ import { claimRunStream, createRunStreamToken, ownsRunStream, releaseRunStream,
43
43
  import { signOut } from "./sign-out.js";
44
44
  import { AgentAutoContinueSignal, readSSEStreamRaw, settleInterruptedToolCalls, } from "./sse-event-processor.js";
45
45
  import { ThinkingDisplayProvider } from "./thinking-display.js";
46
+ import { humanizeToolName, runningToolLabel, toolLabel, } from "./tool-display.js";
46
47
  import { callAction, useActionMutation, useActionQuery } from "./use-action.js";
47
48
  import { useAgentEngineConfigured } from "./use-agent-engine-configured.js";
48
49
  import { appendChatThreadScopeParams, } from "./use-chat-threads.js";
@@ -1129,7 +1130,7 @@ export function resolveAssistantChatRunningState({ forceStopped, isRuntimeRunnin
1129
1130
  showRunningInUI: !forceStopped && !hasTerminalRunError && (isRunning || isAutoResuming),
1130
1131
  };
1131
1132
  }
1132
- export function resolveAssistantChatRunningStatusLabel({ runningActivityLabel, isAutoResuming, isReconnecting, hasReconnectContent, labels = {
1133
+ export function resolveAssistantChatRunningStatusLabel({ runningActivityLabel, runningActivityTool, isAutoResuming, isReconnecting, hasReconnectContent, labels = {
1133
1134
  thinking: "Thinking",
1134
1135
  resuming: "Resuming",
1135
1136
  stillWorking: "Still working",
@@ -1143,6 +1144,15 @@ export function resolveAssistantChatRunningStatusLabel({ runningActivityLabel, i
1143
1144
  if (runningActivityLabel === "Contacting model") {
1144
1145
  return labels.contactingModel ?? "Contacting model";
1145
1146
  }
1147
+ // `tool_start` stores the English `runningToolLabel(tool)`. The tool name
1148
+ // rides along in state, so re-derive the label here instead of shipping the
1149
+ // stored English through to the status line.
1150
+ const activityTool = runningActivityTool?.trim();
1151
+ if (activityTool &&
1152
+ labels.runningTool &&
1153
+ runningActivityLabel === runningToolLabel(activityTool)) {
1154
+ return labels.runningTool(activityTool);
1155
+ }
1146
1156
  const localizedActivityPatterns = [
1147
1157
  [/^Starting (.+)\.\.\.$/, labels.starting],
1148
1158
  [/^Preparing (.+)\.\.\.$/, labels.preparing],
@@ -1151,8 +1161,16 @@ export function resolveAssistantChatRunningStatusLabel({ runningActivityLabel, i
1151
1161
  ];
1152
1162
  for (const [pattern, translateActivity] of localizedActivityPatterns) {
1153
1163
  const match = runningActivityLabel.match(pattern);
1154
- if (match?.[1] && translateActivity)
1155
- return translateActivity(match[1]);
1164
+ if (match?.[1] && translateActivity) {
1165
+ // The captured activity is the derived action name. When it matches the
1166
+ // tool this run is on, prefer the app's catalog label for it.
1167
+ const activity = activityTool &&
1168
+ labels.toolDisplayName &&
1169
+ match[1] === humanizeToolName(activityTool)
1170
+ ? labels.toolDisplayName(activityTool)
1171
+ : match[1];
1172
+ return translateActivity(activity);
1173
+ }
1156
1174
  }
1157
1175
  return runningActivityLabel;
1158
1176
  }
@@ -2123,6 +2141,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
2123
2141
  // ongoing work instead of exposing "Reconnecting" mid-chat.
2124
2142
  const runningStatusLabel = resolveAssistantChatRunningStatusLabel({
2125
2143
  runningActivityLabel,
2144
+ runningActivityTool,
2126
2145
  isAutoResuming,
2127
2146
  isReconnecting,
2128
2147
  hasReconnectContent: reconnectContent.length > 0,
@@ -2136,6 +2155,10 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
2136
2155
  preparing: (activity) => t("agentChat.status.preparing", { activity }),
2137
2156
  writing: (activity) => t("agentChat.status.writing", { activity }),
2138
2157
  stillGenerating: (activity) => t("agentChat.status.stillGenerating", { activity }),
2158
+ runningTool: (toolName) => t("agentChat.status.runningTool", {
2159
+ activity: toolLabel(t, toolName),
2160
+ }),
2161
+ toolDisplayName: (toolName) => toolLabel(t, toolName),
2139
2162
  },
2140
2163
  });
2141
2164
  const reconnectActivityContent = useMemo(() => isReconnecting || reconnectFrozen