@agent-native/core 0.84.25 → 0.84.28

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 (82) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +18 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +4 -2
  5. package/corpus/core/src/agent/run-manager.ts +4 -3
  6. package/corpus/core/src/agent/types.ts +9 -2
  7. package/corpus/core/src/client/AssistantChat.tsx +15 -1
  8. package/corpus/core/src/client/FeedbackButton.tsx +2 -2
  9. package/corpus/core/src/client/MultiTabAssistantChat.tsx +17 -2
  10. package/corpus/core/src/client/agent-chat.ts +118 -0
  11. package/corpus/core/src/client/components/PresenceBar.tsx +7 -2
  12. package/corpus/core/src/client/composer/ComposerPlusMenu.tsx +23 -9
  13. package/corpus/core/src/client/index.ts +4 -0
  14. package/corpus/core/src/client/sharing/ShareButton.tsx +22 -3
  15. package/corpus/core/src/client/sharing/ShareDialog.tsx +13 -1
  16. package/corpus/core/src/client/sse-event-processor.ts +49 -20
  17. package/corpus/core/src/sharing/actions/set-resource-visibility.ts +3 -3
  18. package/corpus/templates/design/actions/insert-asset.ts +14 -2
  19. package/corpus/templates/design/actions/insert-design-native-asset.ts +16 -4
  20. package/corpus/templates/design/actions/insert-figma-library-asset.ts +14 -2
  21. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +14 -0
  22. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +54 -10
  23. package/corpus/templates/design/app/components/design/EditPanel.tsx +23 -16
  24. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +7 -2
  25. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +9 -5
  26. package/corpus/templates/design/app/components/layout/Header.tsx +38 -2
  27. package/corpus/templates/design/app/pages/DesignEditor.tsx +281 -105
  28. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +15 -1
  29. package/corpus/templates/design/app/pages/Index.tsx +20 -9
  30. package/corpus/templates/design/app/routes/settings.tsx +1 -1
  31. package/corpus/templates/design/changelog/2026-07-01-design-editor-polish-makes-zoom-assets-sharing-and-agent-handoffs-clearer.md +6 -0
  32. package/corpus/templates/design/changelog/2026-07-01-design-qa-fixes-make-exports-permissions-and-tools-clearer.md +6 -0
  33. package/corpus/templates/design/changelog/2026-07-01-zoom-menus-in-the-design-editor-now-open-independently-from-.md +6 -0
  34. package/dist/agent/production-agent.d.ts.map +1 -1
  35. package/dist/agent/production-agent.js +4 -3
  36. package/dist/agent/production-agent.js.map +1 -1
  37. package/dist/agent/run-manager.d.ts.map +1 -1
  38. package/dist/agent/run-manager.js +4 -4
  39. package/dist/agent/run-manager.js.map +1 -1
  40. package/dist/agent/types.d.ts +3 -0
  41. package/dist/agent/types.d.ts.map +1 -1
  42. package/dist/agent/types.js.map +1 -1
  43. package/dist/client/AssistantChat.d.ts.map +1 -1
  44. package/dist/client/AssistantChat.js +16 -1
  45. package/dist/client/AssistantChat.js.map +1 -1
  46. package/dist/client/FeedbackButton.js +2 -2
  47. package/dist/client/FeedbackButton.js.map +1 -1
  48. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  49. package/dist/client/MultiTabAssistantChat.js +14 -2
  50. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  51. package/dist/client/agent-chat.d.ts +23 -0
  52. package/dist/client/agent-chat.d.ts.map +1 -1
  53. package/dist/client/agent-chat.js +79 -0
  54. package/dist/client/agent-chat.js.map +1 -1
  55. package/dist/client/components/PresenceBar.d.ts.map +1 -1
  56. package/dist/client/components/PresenceBar.js +6 -2
  57. package/dist/client/components/PresenceBar.js.map +1 -1
  58. package/dist/client/composer/ComposerPlusMenu.d.ts.map +1 -1
  59. package/dist/client/composer/ComposerPlusMenu.js +15 -7
  60. package/dist/client/composer/ComposerPlusMenu.js.map +1 -1
  61. package/dist/client/index.d.ts +1 -1
  62. package/dist/client/index.d.ts.map +1 -1
  63. package/dist/client/index.js +1 -1
  64. package/dist/client/index.js.map +1 -1
  65. package/dist/client/sharing/ShareButton.js +15 -2
  66. package/dist/client/sharing/ShareButton.js.map +1 -1
  67. package/dist/client/sharing/ShareDialog.js +16 -1
  68. package/dist/client/sharing/ShareDialog.js.map +1 -1
  69. package/dist/client/sse-event-processor.d.ts.map +1 -1
  70. package/dist/client/sse-event-processor.js +42 -16
  71. package/dist/client/sse-event-processor.js.map +1 -1
  72. package/dist/collab/awareness.d.ts +2 -2
  73. package/dist/collab/awareness.d.ts.map +1 -1
  74. package/dist/collab/routes.d.ts +1 -1
  75. package/dist/notifications/routes.d.ts +3 -3
  76. package/dist/observability/routes.d.ts +3 -3
  77. package/dist/progress/routes.d.ts +1 -1
  78. package/dist/resources/handlers.d.ts +1 -1
  79. package/dist/server/transcribe-voice.d.ts +1 -1
  80. package/dist/sharing/actions/set-resource-visibility.js +3 -3
  81. package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
  82. package/package.json +1 -1
@@ -150,8 +150,8 @@ export const SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS = 90_000;
150
150
 
151
151
  type ActivityTrailEntry = AgentActivityTrailEntry;
152
152
 
153
- type PreparingActionState = {
154
- tool?: string;
153
+ type PreparingActionEntry = {
154
+ tool: string;
155
155
  startedAt?: number;
156
156
  lastProgressBytes?: number;
157
157
  /**
@@ -166,6 +166,10 @@ type PreparingActionState = {
166
166
  lastProgressAt?: number;
167
167
  };
168
168
 
169
+ type PreparingActionState = {
170
+ entries?: Map<string, PreparingActionEntry>;
171
+ };
172
+
169
173
  function formatProgressBytes(bytes: number): string {
170
174
  if (bytes < 1024) return `${bytes} B`;
171
175
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
@@ -270,21 +274,35 @@ function updatePreparingActionState(
270
274
  if (ev.type === "activity" && isPreparingActionActivity(ev)) {
271
275
  const tool = ev.tool?.trim() || undefined;
272
276
  if (!tool) return false;
273
- if (state.tool !== tool || state.startedAt === undefined) {
274
- state.tool = tool;
275
- state.startedAt = now;
276
- state.lastProgressAt = undefined;
277
- state.lastProgressBytes = undefined;
277
+ const key = ev.id?.trim() || tool;
278
+ const entries = state.entries ?? new Map<string, PreparingActionEntry>();
279
+ state.entries = entries;
280
+ let entry = entries.get(key);
281
+ if (!entry) {
282
+ entry = {
283
+ tool,
284
+ startedAt: now,
285
+ lastProgressAt: undefined,
286
+ lastProgressBytes: undefined,
287
+ };
288
+ entries.set(key, entry);
278
289
  }
279
290
  const progressBytes = activityProgressBytes(ev);
280
- const previousBytes = state.lastProgressBytes ?? 0;
291
+ const previousBytes = entry.lastProgressBytes ?? 0;
281
292
  if (progressBytes !== undefined) {
282
- state.lastProgressBytes = Math.max(previousBytes, progressBytes);
293
+ entry.lastProgressBytes = Math.max(previousBytes, progressBytes);
294
+ }
295
+ if (!ev.id?.trim()) {
296
+ if (progressBytes !== undefined && progressBytes > 0) {
297
+ entry.lastProgressAt = now;
298
+ return true;
299
+ }
300
+ return false;
283
301
  }
284
302
  if (progressBytes !== undefined && progressBytes > previousBytes) {
285
303
  // A byte increase is proof the model is still streaming this action's
286
304
  // argument. Repeated zero-byte prep activity is only a heartbeat.
287
- state.lastProgressAt = now;
305
+ entry.lastProgressAt = now;
288
306
  return true;
289
307
  }
290
308
  return false;
@@ -299,10 +317,17 @@ function updatePreparingActionState(
299
317
  ev.type === "error" ||
300
318
  ev.type === "missing_api_key"
301
319
  ) {
302
- state.tool = undefined;
303
- state.startedAt = undefined;
304
- state.lastProgressAt = undefined;
305
- state.lastProgressBytes = undefined;
320
+ if (ev.type === "tool_start" || ev.type === "tool_done") {
321
+ const tool = ev.tool?.trim();
322
+ const id = ev.id?.trim();
323
+ for (const [key, entry] of state.entries ?? []) {
324
+ if ((id && key === id) || (!id && entry.tool === tool)) {
325
+ state.entries?.delete(key);
326
+ }
327
+ }
328
+ } else {
329
+ state.entries?.clear();
330
+ }
306
331
  }
307
332
  return undefined;
308
333
  }
@@ -313,12 +338,16 @@ function hasStalledPreparingAction(state: PreparingActionState, now: number) {
313
338
  // streaming for a long time. `lastProgressAt` advances on every delta
314
339
  // heartbeat, so an actively-streaming large output keeps resetting this and
315
340
  // survives; a genuinely stuck prep (keepalive-only, no deltas) trips it.
316
- return (
317
- state.tool !== undefined &&
318
- state.startedAt !== undefined &&
319
- now - (state.lastProgressAt ?? state.startedAt) >=
320
- SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS
321
- );
341
+ for (const entry of state.entries?.values() ?? []) {
342
+ if (
343
+ entry.startedAt !== undefined &&
344
+ now - (entry.lastProgressAt ?? entry.startedAt) >=
345
+ SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS
346
+ ) {
347
+ return true;
348
+ }
349
+ }
350
+ return false;
322
351
  }
323
352
 
324
353
  async function readChunkWithProgressTimeout(
@@ -16,9 +16,9 @@ import {
16
16
 
17
17
  export default defineAction({
18
18
  description:
19
- "Change the coarse visibility of a shareable resource: 'private' keeps it owner-only, 'org' shares it with all members of the owner's organization, 'public' makes it accessible to anyone with the link. Owner or admin role required.",
20
- // (audit H5) Visibility changes are admin-tier and can flip a private
21
- // resource org-wide or public. Refuse from the tools iframe bridge.
19
+ "Change the coarse visibility of a shareable resource: 'private' keeps it owner-only, 'org' shares it with all members of the owner's organization, 'public' makes it accessible to anyone with the link. Visibility changes require owner or admin role.",
20
+ // (audit H5) Visibility changes can flip a private resource org-wide or
21
+ // public. Refuse from the tools iframe bridge.
22
22
  toolCallable: false,
23
23
  mcpApp: {
24
24
  compactCatalog: true,
@@ -57,6 +57,14 @@ function escapeHtml(value: string): string {
57
57
  .replace(/"/g, "&quot;");
58
58
  }
59
59
 
60
+ function createInsertedNodeId(prefix: string): string {
61
+ const random =
62
+ typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
63
+ ? crypto.randomUUID().replace(/-/g, "").slice(0, 12)
64
+ : Math.random().toString(36).slice(2, 14);
65
+ return `inserted-${prefix}-${random}`;
66
+ }
67
+
60
68
  function insertBeforeClosingTag(
61
69
  html: string,
62
70
  closingTag: "main" | "body",
@@ -70,6 +78,7 @@ function insertBeforeClosingTag(
70
78
  function appendAssetMarkup(
71
79
  html: string,
72
80
  args: z.infer<typeof schemaInput>,
81
+ nodeId: string,
73
82
  ): string {
74
83
  const label = args.title?.trim() || args.altText?.trim() || "Generated asset";
75
84
  const escapedUrl = escapeHtml(args.assetUrl);
@@ -82,7 +91,7 @@ function appendAssetMarkup(
82
91
  ? `<video src="${escapedUrl}" controls class="w-full rounded-xl object-cover"></video>`
83
92
  : `<img src="${escapedUrl}" alt="${escapeHtml(args.altText?.trim() || label)}" class="w-full rounded-xl object-cover" />`;
84
93
  const snippet = `
85
- <section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset${assetIdAttr}>
94
+ <section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset data-agent-native-node-id="${escapeHtml(nodeId)}" data-agent-native-layer-name="${escapedLabel}"${assetIdAttr}>
86
95
  <figure class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
87
96
  ${media}
88
97
  <figcaption class="px-4 py-3 text-sm text-slate-600">${escapedLabel}</figcaption>
@@ -191,7 +200,8 @@ export default defineAction({
191
200
  // Collab read is best-effort; fall back to stored content.
192
201
  }
193
202
 
194
- const content = appendAssetMarkup(base, args);
203
+ const insertedNodeId = createInsertedNodeId("asset");
204
+ const content = appendAssetMarkup(base, args, insertedNodeId);
195
205
  const now = new Date().toISOString();
196
206
  await db
197
207
  .update(schema.designFiles)
@@ -213,6 +223,8 @@ export default defineAction({
213
223
  fileId: file.id,
214
224
  filename: file.filename,
215
225
  inserted: true,
226
+ insertedNodeId,
227
+ insertedSelector: `[data-agent-native-node-id="${insertedNodeId}"]`,
216
228
  assetId: args.assetId ?? null,
217
229
  assetUrl: args.assetUrl,
218
230
  };
@@ -50,9 +50,17 @@ function insertBeforeClosingTag(
50
50
  return html.replace(pattern, `${snippet}\n</${closingTag}>`);
51
51
  }
52
52
 
53
- function nativeSnippet(kind: DesignNativeAssetKind): string {
53
+ function createInsertedNodeId(prefix: string): string {
54
+ const random =
55
+ typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
56
+ ? crypto.randomUUID().replace(/-/g, "").slice(0, 12)
57
+ : Math.random().toString(36).slice(2, 14);
58
+ return `inserted-${prefix}-${random}`;
59
+ }
60
+
61
+ function nativeSnippet(kind: DesignNativeAssetKind, nodeId: string): string {
54
62
  const attrs = (componentName: string) =>
55
- `data-agent-native-native-asset data-agent-native-component="${componentName}" data-agent-native-layer-name="${componentName}"`;
63
+ `data-agent-native-native-asset data-agent-native-node-id="${nodeId}" data-agent-native-component="${componentName}" data-agent-native-layer-name="${componentName}"`;
56
64
  switch (kind) {
57
65
  case "section-frame":
58
66
  return `
@@ -137,8 +145,9 @@ function nativeSnippet(kind: DesignNativeAssetKind): string {
137
145
  function appendNativeAssetMarkup(
138
146
  html: string,
139
147
  kind: DesignNativeAssetKind,
148
+ nodeId: string,
140
149
  ): string {
141
- const snippet = nativeSnippet(kind);
150
+ const snippet = nativeSnippet(kind, nodeId);
142
151
  return (
143
152
  insertBeforeClosingTag(html, "main", snippet) ??
144
153
  insertBeforeClosingTag(html, "body", snippet) ??
@@ -239,7 +248,8 @@ export default defineAction({
239
248
  // Collab read is best-effort; fall back to stored content.
240
249
  }
241
250
 
242
- const content = appendNativeAssetMarkup(base, args.kind);
251
+ const insertedNodeId = createInsertedNodeId("native");
252
+ const content = appendNativeAssetMarkup(base, args.kind, insertedNodeId);
243
253
  const now = new Date().toISOString();
244
254
  await db
245
255
  .update(schema.designFiles)
@@ -261,6 +271,8 @@ export default defineAction({
261
271
  fileId: file.id,
262
272
  filename: file.filename,
263
273
  inserted: true,
274
+ insertedNodeId,
275
+ insertedSelector: `[data-agent-native-node-id="${insertedNodeId}"]`,
264
276
  source: "design-native",
265
277
  kind: args.kind,
266
278
  };
@@ -66,6 +66,14 @@ function escapeHtml(value: string): string {
66
66
  .replace(/"/g, "&quot;");
67
67
  }
68
68
 
69
+ function createInsertedNodeId(prefix: string): string {
70
+ const random =
71
+ typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
72
+ ? crypto.randomUUID().replace(/-/g, "").slice(0, 12)
73
+ : Math.random().toString(36).slice(2, 14);
74
+ return `inserted-${prefix}-${random}`;
75
+ }
76
+
69
77
  function insertBeforeClosingTag(
70
78
  html: string,
71
79
  closingTag: "main" | "body",
@@ -83,6 +91,7 @@ function optionalDataAttribute(name: string, value: string | undefined) {
83
91
  function appendFigmaAssetMarkup(
84
92
  html: string,
85
93
  args: z.infer<typeof schemaInput>,
94
+ nodeId: string,
86
95
  ): string {
87
96
  const label = args.name?.trim() || "Figma library asset";
88
97
  const description = args.description?.trim();
@@ -90,7 +99,7 @@ function appendFigmaAssetMarkup(
90
99
  ? `<a href="${escapeHtml(args.sourceUrl)}" target="_blank" rel="noreferrer" class="text-slate-500 underline decoration-slate-300 underline-offset-2 hover:text-slate-800">Open in Figma</a>`
91
100
  : "";
92
101
  const snippet = `
93
- <section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset-source="figma" data-agent-native-figma-asset data-figma-file-key="${escapeHtml(args.fileKey)}"${optionalDataAttribute("data-figma-node-id", args.nodeId)}${optionalDataAttribute("data-figma-component-key", args.componentKey)} data-figma-asset-kind="${escapeHtml(args.kind)}">
102
+ <section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset-source="figma" data-agent-native-figma-asset data-agent-native-node-id="${escapeHtml(nodeId)}" data-agent-native-layer-name="${escapeHtml(label)}" data-figma-file-key="${escapeHtml(args.fileKey)}"${optionalDataAttribute("data-figma-node-id", args.nodeId)}${optionalDataAttribute("data-figma-component-key", args.componentKey)} data-figma-asset-kind="${escapeHtml(args.kind)}">
94
103
  <figure class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
95
104
  <img src="${escapeHtml(args.renderUrl)}" alt="${escapeHtml(label)}" class="w-full rounded-t-2xl object-contain" />
96
105
  <figcaption class="flex flex-wrap items-center gap-x-3 gap-y-1 px-4 py-3 text-sm text-slate-600">
@@ -201,7 +210,8 @@ export default defineAction({
201
210
  // Collab read is best-effort; fall back to stored content.
202
211
  }
203
212
 
204
- const content = appendFigmaAssetMarkup(base, args);
213
+ const insertedNodeId = createInsertedNodeId("figma");
214
+ const content = appendFigmaAssetMarkup(base, args, insertedNodeId);
205
215
  const now = new Date().toISOString();
206
216
  await db
207
217
  .update(schema.designFiles)
@@ -223,6 +233,8 @@ export default defineAction({
223
233
  fileId: file.id,
224
234
  filename: file.filename,
225
235
  inserted: true,
236
+ insertedNodeId,
237
+ insertedSelector: `[data-agent-native-node-id="${insertedNodeId}"]`,
226
238
  source: "figma",
227
239
  fileKey: args.fileKey,
228
240
  nodeId: args.nodeId ?? null,
@@ -1775,6 +1775,11 @@ export function DesignCanvas({
1775
1775
  // explicit viewport width (e.g. 390 / 768 / 1280 side-by-side breakpoints).
1776
1776
  const resolvedWidth =
1777
1777
  previewWidthPx != null ? `${previewWidthPx}px` : iframeWidth;
1778
+ const focusScrollSurface = useCallback(() => {
1779
+ const surface = scrollContainerRef.current;
1780
+ if (!surface || document.activeElement === surface) return;
1781
+ surface.focus({ preventScroll: true });
1782
+ }, []);
1778
1783
 
1779
1784
  // Wrap the iframe in a positioned container so DrawOverlay /
1780
1785
  // CanvasCommentPins can absolutely-position themselves on top of the
@@ -1901,6 +1906,9 @@ export function DesignCanvas({
1901
1906
  return (
1902
1907
  <div
1903
1908
  ref={scrollContainerRef}
1909
+ tabIndex={-1}
1910
+ onPointerEnter={focusScrollSurface}
1911
+ onMouseEnter={focusScrollSurface}
1904
1912
  className="relative h-full w-full overflow-hidden"
1905
1913
  >
1906
1914
  {iframeElement}
@@ -1915,6 +1923,9 @@ export function DesignCanvas({
1915
1923
  return (
1916
1924
  <div
1917
1925
  ref={scrollContainerRef}
1926
+ tabIndex={-1}
1927
+ onPointerEnter={focusScrollSurface}
1928
+ onMouseEnter={focusScrollSurface}
1918
1929
  className="relative h-full w-full overflow-hidden"
1919
1930
  style={{
1920
1931
  width: embeddedFrame.displayWidth,
@@ -1945,6 +1956,9 @@ export function DesignCanvas({
1945
1956
  return (
1946
1957
  <div
1947
1958
  ref={scrollContainerRef}
1959
+ tabIndex={-1}
1960
+ onPointerEnter={focusScrollSurface}
1961
+ onMouseEnter={focusScrollSurface}
1948
1962
  className="relative flex-1 h-full overflow-auto"
1949
1963
  >
1950
1964
  {/* Canvas area. "none" mode fills the canvas (responsive preview);
@@ -108,6 +108,12 @@ export interface DesignExtensionSlotContext extends Record<string, unknown> {
108
108
  content: string,
109
109
  updatedAt?: string,
110
110
  ) => void;
111
+ onAssetInserted?: (selection: {
112
+ fileId?: string;
113
+ nodeId?: string;
114
+ selector?: string;
115
+ title?: string;
116
+ }) => void;
111
117
  }
112
118
 
113
119
  interface DesignExtensionsPanelProps {
@@ -301,6 +307,7 @@ interface FirstPartyRowProps {
301
307
 
302
308
  function FirstPartyExtRow({
303
309
  label,
310
+ description,
304
311
  icon,
305
312
  badge,
306
313
  isOpen,
@@ -321,14 +328,14 @@ function FirstPartyExtRow({
321
328
  <span className="block truncate text-sm font-medium leading-tight text-foreground">
322
329
  {label}
323
330
  </span>
324
- <span className="mt-0.5 block truncate text-xs leading-none text-muted-foreground">
325
- <span className="truncate">Built-in</span>
331
+ <span className="mt-0.5 line-clamp-1 text-xs leading-snug text-muted-foreground">
332
+ {description}
326
333
  </span>
327
334
  </span>
328
335
  {badge}
329
336
  </button>
330
337
  {isOpen && (
331
- <div className="mb-2 ml-[3.75rem] overflow-hidden rounded-md border border-border/70 bg-background/70">
338
+ <div className="mb-2 mt-1 overflow-hidden rounded-md border border-border/70 bg-background/70">
332
339
  {children}
333
340
  </div>
334
341
  )}
@@ -348,6 +355,12 @@ function ToolFilterMenu<T extends string>({
348
355
  onChange: (value: T) => void;
349
356
  }) {
350
357
  const selected = options.find((option) => option.value === value);
358
+ const triggerLabel =
359
+ value === "all"
360
+ ? label === "Category"
361
+ ? "All categories"
362
+ : `All ${label.toLowerCase()}s`
363
+ : (selected?.label ?? label);
351
364
  return (
352
365
  <DropdownMenu>
353
366
  <DropdownMenuTrigger asChild>
@@ -358,7 +371,7 @@ function ToolFilterMenu<T extends string>({
358
371
  className="h-8 cursor-pointer gap-1.5 rounded-md bg-transparent px-2.5 text-sm font-medium"
359
372
  >
360
373
  <IconAdjustmentsHorizontal className="size-4 text-muted-foreground" />
361
- <span>{selected?.label ?? label}</span>
374
+ <span>{triggerLabel}</span>
362
375
  <IconChevronDown className="size-3.5 text-muted-foreground" />
363
376
  </Button>
364
377
  </DropdownMenuTrigger>
@@ -463,6 +476,25 @@ export function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
463
476
  : undefined,
464
477
  { enabled: Boolean(figmaRequest) },
465
478
  );
479
+ const notifyAssetInserted = useCallback(
480
+ (result: unknown, title: string) => {
481
+ if (!result || typeof result !== "object") return;
482
+ const row = result as Record<string, unknown>;
483
+ context.onAssetInserted?.({
484
+ fileId: typeof row.fileId === "string" ? row.fileId : undefined,
485
+ nodeId:
486
+ typeof row.insertedNodeId === "string"
487
+ ? row.insertedNodeId
488
+ : undefined,
489
+ selector:
490
+ typeof row.insertedSelector === "string"
491
+ ? row.insertedSelector
492
+ : undefined,
493
+ title,
494
+ });
495
+ },
496
+ [context],
497
+ );
466
498
 
467
499
  const handleReady = useCallback(
468
500
  (_payload: unknown, _event: MessageEvent, ref: EmbeddedAppRef) => {
@@ -508,7 +540,8 @@ export function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
508
540
  fileId: context.activeFileId || undefined,
509
541
  },
510
542
  {
511
- onSuccess: () => {
543
+ onSuccess: (result) => {
544
+ notifyAssetInserted(result, title ?? altText ?? "Asset");
512
545
  toast.success("Asset inserted into design.");
513
546
  },
514
547
  onError: () => {
@@ -517,7 +550,7 @@ export function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
517
550
  },
518
551
  );
519
552
  },
520
- [context.designId, context.activeFileId, insertAsset],
553
+ [context.designId, context.activeFileId, insertAsset, notifyAssetInserted],
521
554
  );
522
555
 
523
556
  const handleInsertNativeAsset = (asset: DesignNativeAsset) => {
@@ -528,7 +561,8 @@ export function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
528
561
  fileId: context.activeFileId || undefined,
529
562
  },
530
563
  {
531
- onSuccess: () => {
564
+ onSuccess: (result) => {
565
+ notifyAssetInserted(result, asset.title);
532
566
  toast.success(`${asset.title} inserted into design.`);
533
567
  },
534
568
  onError: (error) => {
@@ -566,7 +600,8 @@ export function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
566
600
  fileId: context.activeFileId || undefined,
567
601
  },
568
602
  {
569
- onSuccess: () => {
603
+ onSuccess: (result) => {
604
+ notifyAssetInserted(result, asset.name);
570
605
  toast.success("Figma asset inserted into design.");
571
606
  },
572
607
  onError: (error) => {
@@ -1267,6 +1302,11 @@ export function DesignExtensionsPanel({
1267
1302
  visibleFirstPartyRows.length > 0 ||
1268
1303
  visibleInstalls.length > 0 ||
1269
1304
  visibleInstallable.length > 0;
1305
+ const showPluginsEmptyState =
1306
+ !hasAnyVisibleTool &&
1307
+ categoryFilter === "plugins" &&
1308
+ !normalizedSearch &&
1309
+ sourceFilter !== "built-in";
1270
1310
 
1271
1311
  return (
1272
1312
  <div className={cn("flex min-h-0 flex-1 flex-col", className)}>
@@ -1412,10 +1452,14 @@ export function DesignExtensionsPanel({
1412
1452
  <IconSearch className="size-4 text-muted-foreground" />
1413
1453
  </div>
1414
1454
  <p className="text-sm font-medium text-foreground">
1415
- No tools found
1455
+ {showPluginsEmptyState
1456
+ ? "No plugins installed"
1457
+ : "No tools found"}
1416
1458
  </p>
1417
1459
  <p className="mx-auto mt-1 max-w-52 text-xs leading-5 text-muted-foreground">
1418
- Try another search or clear the filters.
1460
+ {showPluginsEmptyState
1461
+ ? "Create a plugin or clear the Category filter to browse built-in tools."
1462
+ : "Try another search or clear the filters."}
1419
1463
  </p>
1420
1464
  </div>
1421
1465
  ) : null}
@@ -3189,22 +3189,29 @@ function TypographyDetailsPopover({
3189
3189
 
3190
3190
  return (
3191
3191
  <Popover open={open} onOpenChange={setOpen}>
3192
- <PopoverTrigger asChild>
3193
- <Button
3194
- type="button"
3195
- variant="ghost"
3196
- size="icon"
3197
- aria-label={"Typography details" /* i18n-ignore design action */}
3198
- aria-pressed={open}
3199
- className={cn(
3200
- "h-6 min-w-6 cursor-pointer rounded-md text-muted-foreground hover:bg-[var(--design-editor-panel-raised-bg)] hover:text-foreground",
3201
- open &&
3202
- "bg-[var(--design-editor-accent-color)]/20 text-[var(--design-editor-accent-color)] hover:text-[var(--design-editor-accent-color)]",
3203
- )}
3204
- >
3205
- <IconLayoutSettings className="size-3.5" />
3206
- </Button>
3207
- </PopoverTrigger>
3192
+ <Tooltip>
3193
+ <TooltipTrigger asChild>
3194
+ <PopoverTrigger asChild>
3195
+ <Button
3196
+ type="button"
3197
+ variant="ghost"
3198
+ size="icon"
3199
+ aria-label={"Typography details" /* i18n-ignore design action */}
3200
+ aria-pressed={open}
3201
+ className={cn(
3202
+ "h-6 min-w-6 cursor-pointer rounded-md text-muted-foreground hover:bg-[var(--design-editor-panel-raised-bg)] hover:text-foreground",
3203
+ open &&
3204
+ "bg-[var(--design-editor-accent-color)]/20 text-[var(--design-editor-accent-color)] hover:text-[var(--design-editor-accent-color)]",
3205
+ )}
3206
+ >
3207
+ <IconLayoutSettings className="size-3.5" />
3208
+ </Button>
3209
+ </PopoverTrigger>
3210
+ </TooltipTrigger>
3211
+ <TooltipContent>
3212
+ {"Typography details" /* i18n-ignore design action */}
3213
+ </TooltipContent>
3214
+ </Tooltip>
3208
3215
  <PopoverContent
3209
3216
  side="left"
3210
3217
  align="end"
@@ -4825,7 +4825,11 @@ export function MultiScreenCanvas({
4825
4825
  className="relative h-full w-full select-none overflow-hidden outline-none"
4826
4826
  onMouseDownCapture={handleMouseDown}
4827
4827
  onMouseMove={handleMouseMove}
4828
- style={{ cursor: surfaceCursor, touchAction: "none" }}
4828
+ style={{
4829
+ cursor: surfaceCursor,
4830
+ overscrollBehavior: "none",
4831
+ touchAction: "none",
4832
+ }}
4829
4833
  >
4830
4834
  {showPixelGrid ? (
4831
4835
  <div
@@ -5769,6 +5773,7 @@ const Screen = memo(function Screen({
5769
5773
  onMouseEnter={() => updateDirectHover(true)}
5770
5774
  onMouseLeave={() => updateDirectHover(false)}
5771
5775
  style={{
5776
+ width: labelInfoMaxWidth,
5772
5777
  maxWidth: labelInfoMaxWidth,
5773
5778
  transform: `translateY(-50%) scale(${chromeScale})`,
5774
5779
  transformOrigin: "left center",
@@ -5784,7 +5789,7 @@ const Screen = memo(function Screen({
5784
5789
  <span
5785
5790
  data-frame-title
5786
5791
  className={cn(
5787
- "min-w-0 truncate !text-[11px] font-medium",
5792
+ "min-w-0 flex-1 truncate !text-[11px] font-medium",
5788
5793
  emphasized
5789
5794
  ? "text-[var(--design-editor-accent-color)]"
5790
5795
  : activeOrEmphasized
@@ -63,7 +63,8 @@ export function InspectorAiActions({
63
63
  routeSourceFile,
64
64
  designId,
65
65
  };
66
- const disabled = !canEdit || !request.trim();
66
+ const copyDisabled = !request.trim();
67
+ const askDisabled = !canEdit || copyDisabled;
67
68
 
68
69
  return (
69
70
  <Collapsible open={open} onOpenChange={setOpen} className="w-full">
@@ -97,9 +98,12 @@ export function InspectorAiActions({
97
98
  : t("designEditor.localSourceEdit.describeChange")
98
99
  }
99
100
  className="min-h-[64px] resize-none text-xs"
100
- disabled={!canEdit}
101
101
  onKeyDown={(e) => {
102
- if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && !disabled) {
102
+ if (
103
+ e.key === "Enter" &&
104
+ (e.metaKey || e.ctrlKey) &&
105
+ !askDisabled
106
+ ) {
103
107
  e.preventDefault();
104
108
  void sendEdit(args);
105
109
  setRequest("");
@@ -112,7 +116,7 @@ export function InspectorAiActions({
112
116
  size="sm"
113
117
  variant="default"
114
118
  className="flex-1 gap-1.5 text-xs"
115
- disabled={disabled}
119
+ disabled={askDisabled}
116
120
  onClick={() => {
117
121
  void sendEdit(args);
118
122
  setRequest("");
@@ -126,7 +130,7 @@ export function InspectorAiActions({
126
130
  size="sm"
127
131
  variant="outline"
128
132
  className="gap-1.5 text-xs"
129
- disabled={disabled}
133
+ disabled={copyDisabled}
130
134
  onClick={() => {
131
135
  void copyPrompt(args);
132
136
  }}
@@ -1,6 +1,12 @@
1
- import { useActionQuery, useT } from "@agent-native/core/client";
1
+ import {
2
+ requestAgentChatThreadOpen,
3
+ requestAgentTaskOpen,
4
+ useActionQuery,
5
+ useT,
6
+ } from "@agent-native/core/client";
2
7
  import { AgentToggleButton } from "@agent-native/core/client";
3
8
  import { RunsTray } from "@agent-native/core/client/progress";
9
+ import { useCallback } from "react";
4
10
  import { useLocation } from "react-router";
5
11
 
6
12
  import { useHeaderTitle, useHeaderActions } from "./HeaderActions";
@@ -12,6 +18,11 @@ const pageTitleKeys: Record<string, string> = {
12
18
  "/settings": "navigation.settings",
13
19
  };
14
20
 
21
+ type HeaderAgentRun = {
22
+ title?: string;
23
+ metadata?: Record<string, unknown> | null;
24
+ };
25
+
15
26
  function DesignTitle({ id }: { id: string }) {
16
27
  const { data } = useActionQuery<{ title?: string }>("get-design", { id });
17
28
  const title = data?.title ?? "Design";
@@ -42,6 +53,31 @@ function ResolvedTitle() {
42
53
  export function Header() {
43
54
  const title = useHeaderTitle();
44
55
  const actions = useHeaderActions();
56
+ const openRunThread = useCallback(
57
+ (threadId: string, run?: HeaderAgentRun) => {
58
+ const metadata = run?.metadata ?? {};
59
+ const parentThreadId =
60
+ typeof metadata.parentThreadId === "string"
61
+ ? metadata.parentThreadId.trim()
62
+ : "";
63
+ const isAgentTeam =
64
+ metadata.kind === "agent-team" || metadata.source === "agent-teams";
65
+ if (isAgentTeam && parentThreadId && parentThreadId !== threadId) {
66
+ requestAgentTaskOpen({
67
+ threadId,
68
+ parentThreadId,
69
+ description:
70
+ typeof metadata.description === "string"
71
+ ? metadata.description
72
+ : run?.title || "",
73
+ name: typeof metadata.name === "string" ? metadata.name : "",
74
+ });
75
+ return;
76
+ }
77
+ requestAgentChatThreadOpen({ threadId });
78
+ },
79
+ [],
80
+ );
45
81
 
46
82
  return (
47
83
  <header className="flex h-12 items-center gap-3 border-b border-border bg-background px-4 lg:px-6 shrink-0">
@@ -50,7 +86,7 @@ export function Header() {
50
86
  </div>
51
87
  <div className="flex items-center gap-2 shrink-0">
52
88
  {actions}
53
- <RunsTray pollMs={1500} />
89
+ <RunsTray pollMs={1500} onOpenThread={openRunThread} />
54
90
  <AgentToggleButton />
55
91
  </div>
56
92
  </header>