@agent-native/core 0.149.1 → 0.149.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +2 -0
  3. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +150 -46
  4. package/corpus/templates/analytics/app/global.css +1 -1
  5. package/corpus/templates/analytics/app/i18n-data.ts +37 -0
  6. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
  7. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -0
  8. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +2 -0
  9. package/corpus/templates/analytics/changelog/2026-08-10-dashboard-tables-can-be-copied-and-sorted-by-multiple-column.md +6 -0
  10. package/corpus/templates/analytics/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  11. package/corpus/templates/assets/app/global.css +1 -1
  12. package/corpus/templates/assets/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  13. package/corpus/templates/brain/app/global.css +1 -1
  14. package/corpus/templates/brain/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  15. package/corpus/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  16. package/corpus/templates/clips/changelog/2026-08-10-clips-desktop-sign-in-now-starts-with-magic-links-and-google.md +6 -0
  17. package/corpus/templates/clips/chrome-extension/src/overlay.css +1 -0
  18. package/corpus/templates/clips/desktop/src/app.tsx +274 -137
  19. package/corpus/templates/clips/desktop/src/styles.css +35 -0
  20. package/corpus/templates/content/app/global.css +1 -0
  21. package/corpus/templates/crm/app/global.css +1 -1
  22. package/corpus/templates/crm/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  23. package/corpus/templates/dispatch/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  24. package/corpus/templates/factory/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  25. package/corpus/templates/forms/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  26. package/corpus/templates/plan/app/global.css +1 -0
  27. package/corpus/templates/plan/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  28. package/corpus/templates/plan/server/ui-plan-html.ts +4 -1
  29. package/corpus/templates/slides/AGENTS.md +11 -10
  30. package/corpus/templates/slides/actions/add-deck.ts +2 -0
  31. package/corpus/templates/slides/actions/add-slide.ts +15 -1
  32. package/corpus/templates/slides/actions/create-deck.ts +18 -6
  33. package/corpus/templates/slides/actions/get-deck.ts +29 -1
  34. package/corpus/templates/slides/actions/patch-deck.ts +122 -9
  35. package/corpus/templates/slides/actions/save-deck.ts +24 -1
  36. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +134 -618
  37. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +69 -60
  38. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -1
  39. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +13 -1
  40. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +56 -174
  41. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +239 -116
  42. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +3 -3
  43. package/corpus/templates/slides/app/components/layout/AgentWorkIndicator.tsx +1 -1
  44. package/corpus/templates/slides/app/components/layout/Layout.tsx +34 -35
  45. package/corpus/templates/slides/app/components/layout/layout-route-policy.ts +2 -5
  46. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +45 -40
  47. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +63 -16
  48. package/corpus/templates/slides/app/hooks/use-generating-slide-preview.ts +25 -3
  49. package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
  50. package/corpus/templates/slides/app/lib/create-deck-generation.ts +5 -4
  51. package/corpus/templates/slides/app/lib/recent-references.ts +23 -1
  52. package/corpus/templates/slides/app/lib/slide-animation-elements.ts +41 -3
  53. package/corpus/templates/slides/app/pages/DeckEditor.tsx +4 -27
  54. package/corpus/templates/slides/app/pages/Index.tsx +240 -89
  55. package/corpus/templates/slides/changelog/2026-08-10-ai-editing-avatar-badge.md +6 -0
  56. package/corpus/templates/slides/changelog/2026-08-10-generated-decks-keep-a-clear-human-readable-title.md +6 -0
  57. package/corpus/templates/slides/changelog/2026-08-10-new-deck-creation-now-shows-progress-opens-the-editor-direct.md +6 -0
  58. package/corpus/templates/slides/changelog/2026-08-10-presentation-reveals-now-follow-valid-targets.md +6 -0
  59. package/corpus/templates/slides/changelog/2026-08-10-slides-editing-controls-are-consolidated-into-one-focused-me.md +6 -0
  60. package/corpus/templates/slides/changelog/2026-08-10-slides-now-verify-and-restyle-every-imported-slide-in-one-pa.md +6 -0
  61. package/corpus/templates/slides/changelog/2026-08-10-slides-only-suggests-a-google-connection-for-relevant-reques.md +6 -0
  62. package/corpus/templates/slides/changelog/2026-08-10-the-google-slides-import-option-is-now-labeled-slides.md +6 -0
  63. package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
  64. package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -3
  65. package/corpus/templates/slides/shared/api.ts +2 -1
  66. package/corpus/templates/slides/shared/deck-title.ts +135 -0
  67. package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +4 -1
  68. package/dist/agent/engine/ai-sdk-engine.js +1 -1
  69. package/dist/agent/production-agent.d.ts +21 -1
  70. package/dist/agent/production-agent.js +129 -10
  71. package/dist/agent/tool-approval-store.d.ts +18 -0
  72. package/dist/agent/tool-approval-store.js +122 -0
  73. package/dist/agent/types.d.ts +1 -1
  74. package/dist/cli/code-agent-executor.js +11 -2
  75. package/dist/client/AgentPanel.js +31 -6
  76. package/dist/client/AssistantChat.d.ts +3 -2
  77. package/dist/client/AssistantChat.js +7 -20
  78. package/dist/client/agent-chat/index.d.ts +1 -1
  79. package/dist/client/agent-chat-adapter.js +11 -0
  80. package/dist/client/agent-chat.d.ts +1 -1
  81. package/dist/client/chat/markdown-renderer.js +14 -2
  82. package/dist/client/chat/message-components.d.ts +6 -2
  83. package/dist/client/chat/message-components.js +25 -47
  84. package/dist/client/chat/tool-call-display.d.ts +3 -5
  85. package/dist/client/chat/tool-call-display.js +7 -212
  86. package/dist/client/chat-first/apps-rail.js +3 -3
  87. package/dist/client/chat-first/browser-pane.js +4 -1
  88. package/dist/client/chat-first/chat-history.d.ts +3 -1
  89. package/dist/client/chat-first/chat-history.js +2 -2
  90. package/dist/client/chat-first/surface-tabs.d.ts +1 -1
  91. package/dist/client/chat-first/surface-tabs.js +52 -41
  92. package/dist/client/chat-first/types.d.ts +3 -0
  93. package/dist/client/chat-first.d.ts +10 -0
  94. package/dist/client/chat-first.js +37 -2
  95. package/dist/client/components/ui/message-scroller.js +4 -1
  96. package/dist/client/i18n.js +3 -1
  97. package/dist/client/resources/McpConnectionSuggestion.js +9 -1
  98. package/dist/client/sse-event-processor.js +10 -0
  99. package/dist/client/use-chat-models.js +156 -80
  100. package/dist/collab/struct-routes.d.ts +1 -1
  101. package/dist/integrations/webhook-handler.js +1 -1
  102. package/dist/observability/routes.d.ts +1 -1
  103. package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
  104. package/dist/resources/handlers.d.ts +1 -1
  105. package/dist/secrets/routes.d.ts +9 -9
  106. package/dist/server/auth.js +262 -31
  107. package/dist/server/onboarding-html.js +56 -9
  108. package/dist/shared/reasoning-effort.d.ts +8 -8
  109. package/dist/shared/reasoning-effort.js +10 -10
  110. package/dist/shared/streaming-text-smoothing.js +41 -11
  111. package/dist/styles/agent-native.css +19 -32
  112. package/dist/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  113. package/docs/content/template-chat.mdx +1 -1
  114. package/package.json +2 -2
  115. package/src/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
@@ -20,207 +20,12 @@ import { isBuiltinDataWidgetActionRenderer, resolveBuiltinActionChatRenderer, re
20
20
  export const ChatRunningContext = React.createContext(false);
21
21
  export const ChatRunDurationContext = React.createContext(null);
22
22
  export const ASSISTANT_VISIBLE_TOOL_CALL_LIMIT = 3;
23
- const TOOL_CALL_ENTRY_DURATION_MS = 220;
24
- const TOOL_CALL_STACK_MOTION_DURATION_MS = 220;
25
- const TOOL_CALL_STACK_EASING = "cubic-bezier(0.23, 1, 0.32, 1)";
26
- function toolStackMotionKey(element, index) {
27
- const toolCallId = element.dataset.agentToolCallId;
28
- if (toolCallId)
29
- return `tool:${toolCallId}`;
30
- const summaryId = element.dataset.agentToolSummary;
31
- if (summaryId)
32
- return `summary:${summaryId}`;
33
- return `anonymous:${index}`;
34
- }
35
- function prefersReducedMotionForToolStack() {
36
- return (typeof window !== "undefined" &&
37
- typeof window.matchMedia === "function" &&
38
- window.matchMedia("(prefers-reduced-motion: reduce)").matches);
39
- }
40
- function startToolStackAnimation(element, keyframes) {
41
- if (typeof element.animate !== "function")
42
- return null;
43
- return element.animate(keyframes, {
44
- duration: TOOL_CALL_STACK_MOTION_DURATION_MS,
45
- easing: TOOL_CALL_STACK_EASING,
46
- fill: "both",
47
- });
48
- }
49
- function prepareToolStackExitClone(clone, snapshot) {
50
- clone.classList.remove("agent-tool-call--entering", "agent-tool-call--stack-entering");
51
- clone
52
- .querySelectorAll(".agent-tool-call--entering, .agent-tool-call--stack-entering")
53
- .forEach((element) => {
54
- element.classList.remove("agent-tool-call--entering", "agent-tool-call--stack-entering");
55
- });
56
- clone.classList.add("agent-tool-call-stack__exit");
57
- clone.setAttribute("aria-hidden", "true");
58
- clone.removeAttribute("data-agent-tool-call-id");
59
- clone.removeAttribute("data-agent-tool-summary");
60
- clone.removeAttribute("data-agent-tool-motion-token");
61
- clone.style.left = `${snapshot.left}px`;
62
- clone.style.top = `${snapshot.top}px`;
63
- clone.style.width = `${snapshot.width}px`;
64
- clone.style.height = `${snapshot.height}px`;
65
- clone.style.transform = "translate3d(0, 0, 0)";
66
- clone.style.transition = "none";
67
- clone.style.animation = "none";
68
- clone.style.opacity = "1";
69
- }
70
23
  /**
71
- * Keeps the visible tool stack spatially continuous as streaming calls change
72
- * which rows belong to the collapsed history bucket. The rendered data stays
73
- * untouched; this only animates the DOM's before/after positions.
24
+ * Keeps the tool-call stack layout-transparent. Tool-entry motion is disabled
25
+ * until it can stay stable while streaming calls are added and summarized.
74
26
  */
75
27
  export function ToolCallStackMotion({ children, className, }) {
76
- const stackRef = useRef(null);
77
- const previousRef = useRef(new Map());
78
- const previousStructureRef = useRef([]);
79
- const activeMotionsRef = useRef(new Map());
80
- const exitAnimationsRef = useRef(new Map());
81
- useLayoutEffect(() => {
82
- const stack = stackRef.current;
83
- if (!stack)
84
- return;
85
- const previous = previousRef.current;
86
- const visualPrevious = new Map(previous);
87
- for (const [key, activeMotion] of activeMotionsRef.current) {
88
- if (stack.contains(activeMotion.element)) {
89
- const previousSnapshot = previous.get(key);
90
- if (previousSnapshot) {
91
- const rect = activeMotion.element.getBoundingClientRect();
92
- visualPrevious.set(key, {
93
- ...previousSnapshot,
94
- top: rect.top,
95
- left: rect.left,
96
- });
97
- }
98
- }
99
- activeMotion.animation.cancel();
100
- }
101
- activeMotionsRef.current.clear();
102
- for (const [ghost, animation] of exitAnimationsRef.current) {
103
- animation.cancel();
104
- ghost.remove();
105
- }
106
- exitAnimationsRef.current.clear();
107
- const elements = Array.from(stack.querySelectorAll("[data-agent-tool-call-id], [data-agent-tool-summary]"));
108
- const structure = elements.map((element, index) => toolStackMotionKey(element, index));
109
- const previousStructure = previousStructureRef.current;
110
- const structureChanged = structure.length !== previousStructure.length ||
111
- structure.some((key, index) => key !== previousStructure[index]);
112
- previousStructureRef.current = structure;
113
- const current = new Map();
114
- for (const [index, element] of elements.entries()) {
115
- const rect = element.getBoundingClientRect();
116
- current.set(toolStackMotionKey(element, index), {
117
- top: rect.top,
118
- left: rect.left,
119
- width: rect.width,
120
- height: rect.height,
121
- clone: element.cloneNode(true),
122
- });
123
- }
124
- previousRef.current = current;
125
- // Entry motion changes the measured transform without changing the stack
126
- // structure. Do not turn that animation's cleanup into a second FLIP pass.
127
- if (previous.size === 0 ||
128
- !structureChanged ||
129
- prefersReducedMotionForToolStack())
130
- return;
131
- const summary = elements.find((element) => element.dataset.agentToolSummary !== undefined);
132
- const summaryRect = summary?.getBoundingClientRect();
133
- const elementsByKey = new Map(elements.map((element, index) => [
134
- toolStackMotionKey(element, index),
135
- element,
136
- ]));
137
- for (const [key, after] of current.entries()) {
138
- const before = visualPrevious.get(key);
139
- if (!before) {
140
- const node = elementsByKey.get(key);
141
- if (!node)
142
- continue;
143
- if (node.dataset.agentToolSummary !== undefined)
144
- continue;
145
- if (node.dataset.agentToolCallId !== undefined &&
146
- !node.classList.contains("agent-tool-call--entering")) {
147
- node.classList.add("agent-tool-call--stack-entering");
148
- window.setTimeout(() => {
149
- node.classList.remove("agent-tool-call--stack-entering");
150
- }, TOOL_CALL_ENTRY_DURATION_MS);
151
- }
152
- continue;
153
- }
154
- const dx = before.left - after.left;
155
- const dy = before.top - after.top;
156
- if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5)
157
- continue;
158
- const node = elementsByKey.get(key);
159
- if (!node)
160
- continue;
161
- if (node.dataset.agentToolSummary !== undefined)
162
- continue;
163
- const animation = startToolStackAnimation(node, [
164
- { transform: `translate3d(${dx}px, ${dy}px, 0)` },
165
- { transform: "translate3d(0, 0, 0)" },
166
- ]);
167
- if (!animation)
168
- continue;
169
- activeMotionsRef.current.set(key, { element: node, animation });
170
- const clearMotion = () => {
171
- if (activeMotionsRef.current.get(key)?.animation === animation) {
172
- activeMotionsRef.current.delete(key);
173
- }
174
- };
175
- animation.onfinish = clearMotion;
176
- animation.oncancel = clearMotion;
177
- }
178
- for (const [key, before] of visualPrevious.entries()) {
179
- if (current.has(key) || !key.startsWith("tool:"))
180
- continue;
181
- const ghost = before.clone;
182
- prepareToolStackExitClone(ghost, before);
183
- document.body?.appendChild(ghost);
184
- if (!ghost.isConnected)
185
- continue;
186
- const targetTop = summaryRect?.top ?? before.top - 8;
187
- const targetLeft = summaryRect?.left ?? before.left;
188
- const animation = startToolStackAnimation(ghost, [
189
- { transform: "translate3d(0, 0, 0)", opacity: 1 },
190
- {
191
- transform: `translate3d(${targetLeft - before.left}px, ${targetTop - before.top}px, 0)`,
192
- opacity: 0,
193
- },
194
- ]);
195
- if (!animation) {
196
- ghost.remove();
197
- continue;
198
- }
199
- exitAnimationsRef.current.set(ghost, animation);
200
- const clearExit = () => {
201
- if (exitAnimationsRef.current.get(ghost) === animation) {
202
- exitAnimationsRef.current.delete(ghost);
203
- }
204
- ghost.remove();
205
- };
206
- animation.onfinish = clearExit;
207
- animation.oncancel = clearExit;
208
- }
209
- });
210
- useEffect(() => {
211
- return () => {
212
- for (const { animation } of activeMotionsRef.current.values()) {
213
- animation.cancel();
214
- }
215
- activeMotionsRef.current.clear();
216
- for (const [ghost, animation] of exitAnimationsRef.current) {
217
- animation.cancel();
218
- ghost.remove();
219
- }
220
- exitAnimationsRef.current.clear();
221
- };
222
- }, []);
223
- return (_jsx("div", { ref: stackRef, className: cn("agent-tool-call-stack", className), children: children }));
28
+ return (_jsx("div", { className: cn("agent-tool-call-stack", className), children: children }));
224
29
  }
225
30
  export const ApprovalContext = React.createContext(null);
226
31
  /** Pending human-in-the-loop gate still waiting for Approve/Deny. */
@@ -231,18 +36,8 @@ export function toolCallHasPendingApproval(part) {
231
36
  approval.dismissed !== true);
232
37
  }
233
38
  export const TOOL_LONG_RUNNING_HINT_DELAY_MS = 45_000;
234
- export function ToolActivityPresentation({ toolName, isRunning, isActiveTail, toolCallId, suppressLongRunningHint = false, children, }) {
39
+ export function ToolActivityPresentation({ toolName, isRunning, toolCallId, suppressLongRunningHint = false, children, }) {
235
40
  const [showLongRunningHint, setShowLongRunningHint] = useState(false);
236
- // A batched update can first reveal a tool with its result already attached.
237
- // Presentation follows the active chat tail rather than execution state so
238
- // that newly revealed completed tools still get their entrance motion.
239
- const [animateEntry, setAnimateEntry] = useState(isActiveTail);
240
- useEffect(() => {
241
- if (!animateEntry)
242
- return;
243
- const timeout = window.setTimeout(() => setAnimateEntry(false), TOOL_CALL_ENTRY_DURATION_MS);
244
- return () => window.clearTimeout(timeout);
245
- }, [animateEntry]);
246
41
  useEffect(() => {
247
42
  if (!isRunning || suppressLongRunningHint) {
248
43
  setShowLongRunningHint(false);
@@ -254,7 +49,7 @@ export function ToolActivityPresentation({ toolName, isRunning, isActiveTail, to
254
49
  }, TOOL_LONG_RUNNING_HINT_DELAY_MS);
255
50
  return () => window.clearTimeout(timeout);
256
51
  }, [isRunning, suppressLongRunningHint, toolName]);
257
- return (_jsx("div", { className: cn("agent-tool-call", animateEntry && "agent-tool-call--entering"), "data-agent-tool-call-id": toolCallId, "data-running": isRunning ? "true" : undefined, children: _jsxs("div", { className: "agent-tool-call__content", children: [children, isRunning && showLongRunningHint && (_jsx("div", { className: "mt-0.5 px-2.5 pb-2 text-[11px] leading-snug text-muted-foreground/80", children: "Still working. Large updates can take a minute or two." }))] }) }));
52
+ return (_jsx("div", { className: "agent-tool-call", "data-agent-tool-call-id": toolCallId, "data-running": isRunning ? "true" : undefined, children: _jsxs("div", { className: "agent-tool-call__content", children: [children, isRunning && showLongRunningHint && (_jsx("div", { className: "mt-0.5 px-2.5 pb-2 text-[11px] leading-snug text-muted-foreground/80", children: "Still working. Large updates can take a minute or two." }))] }) }));
258
53
  }
259
54
  function stringifyToolValue(value, pretty = false) {
260
55
  if (typeof value === "string")
@@ -480,7 +275,7 @@ export function ToolCallDisplay({ toolName, toolCallId, argsText, args, result,
480
275
  // These must be separate components so hook order in ToolCallDisplayGeneric
481
276
  // is always stable (no conditional hook calls).
482
277
  const toolKind = structuredMeta?.toolKind;
483
- const wrapToolDisplay = (children) => (_jsx(ToolActivityPresentation, { toolName: toolName, isRunning: isRunning, isActiveTail: showActiveTail, toolCallId: toolCallId, suppressLongRunningHint: toolName === "call-agent" || toolName.startsWith("agent:"), children: children }));
278
+ const wrapToolDisplay = (children) => (_jsx(ToolActivityPresentation, { toolName: toolName, isRunning: isRunning, toolCallId: toolCallId, suppressLongRunningHint: toolName === "call-agent" || toolName.startsWith("agent:"), children: children }));
484
279
  if (toolKind === "bash") {
485
280
  return wrapToolDisplay(_jsx(BashCell, { meta: structuredMeta, output: result, isRunning: isRunning }));
486
281
  }
@@ -642,7 +437,7 @@ function AgentCallCell({ agentName, activity, progress, responseText, isRunning,
642
437
  function AgentActivityToolCallRow({ tool, isActiveTail, }) {
643
438
  const isRunning = tool.status === "running";
644
439
  const ToolIcon = resolveToolIcon(tool.name);
645
- return (_jsx(ToolActivityPresentation, { toolName: tool.name, isRunning: isRunning, isActiveTail: isActiveTail, toolCallId: tool.id, suppressLongRunningHint: true, children: _jsxs("div", { className: "my-0.5 flex w-full items-center gap-1.5 rounded-md py-0.5 text-left text-[13px] text-muted-foreground", children: [_jsx("span", { className: "flex size-4 shrink-0 items-center justify-center", children: isRunning ? (_jsx(IconLoader2, { className: "size-3.5 animate-spin" })) : (_jsx(ToolIcon, { className: "size-3.5" })) }), _jsx("span", { className: cn("min-w-0 truncate font-normal", isActiveTail && "agent-running-shimmer"), children: humanizeToolName(tool.name) })] }) }));
440
+ return (_jsx(ToolActivityPresentation, { toolName: tool.name, isRunning: isRunning, toolCallId: tool.id, suppressLongRunningHint: true, children: _jsxs("div", { className: "my-0.5 flex w-full items-center gap-1.5 rounded-md py-0.5 text-left text-[13px] text-muted-foreground", children: [_jsx("span", { className: "flex size-4 shrink-0 items-center justify-center", children: isRunning ? (_jsx(IconLoader2, { className: "size-3.5 animate-spin" })) : (_jsx(ToolIcon, { className: "size-3.5" })) }), _jsx("span", { className: cn("min-w-0 truncate font-normal", isActiveTail && "agent-running-shimmer"), children: humanizeToolName(tool.name) })] }) }));
646
441
  }
647
442
  // ─── ToolCallFallback ──────────────────────────────────────────────────────────
648
443
  export function ToolCallFallback({ toolName, toolCallId, args, argsText, result, ...rest }) {
@@ -15,12 +15,12 @@ function AppRows({ apps, activeAppId, layout, onDragStart, onDrop, onDragEnd, on
15
15
  const active = activeAppId === app.id;
16
16
  const pinned = layout.pinnedIds.includes(app.id);
17
17
  const index = orderedApps.indexOf(app);
18
- return (_jsxs(ContextMenu, { children: [_jsx(ContextMenuTrigger, { asChild: true, children: _jsxs("li", { draggable: true, "data-chat-first-app": true, "data-app-id": app.id, className: cn("group flex h-8 w-full min-w-0 items-center gap-1 rounded-md px-1 text-sm", active
18
+ return (_jsxs(ContextMenu, { children: [_jsx(ContextMenuTrigger, { asChild: true, children: _jsxs("li", { draggable: true, "data-chat-first-app": true, "data-app-id": app.id, className: cn("group flex h-8 w-full min-w-0 items-center gap-1 rounded-md px-0 text-sm", active
19
19
  ? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
20
20
  : "text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onDragStart: (event) => {
21
21
  event.dataTransfer.effectAllowed = "move";
22
22
  onDragStart(app.id);
23
- }, onDragOver: (event) => event.preventDefault(), onDrop: () => onDrop(app.id), onDragEnd: onDragEnd, children: [_jsxs("button", { type: "button", className: "flex h-full min-w-0 flex-1 items-center gap-2 px-1 text-start", onClick: () => onOpenApp(app), onKeyDown: (event) => {
23
+ }, onDragOver: (event) => event.preventDefault(), onDrop: () => onDrop(app.id), onDragEnd: onDragEnd, children: [_jsxs("button", { type: "button", className: "flex h-full min-w-0 flex-1 items-center gap-2 px-2 text-start", onClick: () => onOpenApp(app), onKeyDown: (event) => {
24
24
  if (!event.altKey)
25
25
  return;
26
26
  if (event.key === "ArrowUp") {
@@ -99,5 +99,5 @@ export const ChatFirstAppsRail = memo(function ChatFirstAppsRail({ apps, activeA
99
99
  ? "bg-sidebar-accent text-sidebar-accent-foreground"
100
100
  : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onClick: () => onOpenApp(app), "aria-label": copy("openApp", { name: app.name }), title: app.name, children: renderIcon(app) }, app.id))), onOpenAllApps ? (_jsx("button", { type: "button", "data-chat-first-all-apps": true, className: "flex size-9 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", onClick: onOpenAllApps, "aria-label": copy("allApps"), title: copy("allApps"), children: _jsx(IconApps, { size: 16, "aria-hidden": "true" }) })) : null, error ? (_jsx("span", { className: "size-1.5 rounded-full bg-destructive", title: error, "aria-label": error })) : null] }));
101
101
  }
102
- return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "mt-3 px-2 pb-2 pt-2", "aria-label": copy("workspaceApps"), children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 px-1 text-[11px] font-medium text-sidebar-foreground/50", children: [_jsx("span", { children: copy("workspaceApps") }), _jsx("span", { className: "ml-auto", children: createTrigger })] }), loading && apps.length === 0 ? (_jsx("div", { className: "space-y-0.5 px-1", children: [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "h-8 w-full rounded-md" }, index))) })) : apps.length === 0 ? (_jsxs("div", { className: "px-1", children: [_jsx("p", { className: "text-xs text-sidebar-foreground/55", children: copy("noWorkspaceApps") }), createAppTrigger ? (createTrigger) : onCreateApp ? (_jsxs("button", { type: "button", className: "mt-1 inline-flex h-6 items-center gap-1.5 rounded-md border border-sidebar-border px-2 text-xs text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onCreateApp, children: [_jsx(IconPlus, { size: 13, "aria-hidden": "true" }), copy("createApp")] })) : (createTrigger)] })) : (_jsx(AppRows, { apps: visibleApps, activeAppId: activeAppId, layout: layout, onDragStart: setDraggedAppId, onDrop: reorderApps, onDragEnd: () => setDraggedAppId(null), onOpenApp: onOpenApp, onTogglePinned: togglePinned, onMove: moveApp, renderIcon: renderIcon, copy: copy })), hasMoreApps || showAllApps ? (_jsxs("button", { type: "button", className: "mt-0.5 flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: () => setShowAllApps((value) => !value), children: [showAllApps ? (_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" })), showAllApps ? copy("showLess") : copy("showMore")] })) : null, onOpenAllApps ? (_jsx("div", { className: "mt-1 border-t border-sidebar-border/60 pt-1", children: _jsxs("button", { type: "button", "data-chat-first-all-apps": true, className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onOpenAllApps, children: [_jsx(IconApps, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: copy("allApps") })] }) })) : null, error ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "mt-1 flex items-center gap-1 px-1 text-[10px] text-destructive/80", onClick: onRetry, "aria-label": error, children: [_jsx("span", { className: "size-1.5 rounded-full bg-destructive" }), onRetry ? copy("retry") : copy("appsLoadError")] }) }), _jsx(TooltipContent, { side: "right", children: error })] })) : null] }));
102
+ return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "mt-3 px-0 pb-2 pt-2", "aria-label": copy("workspaceApps"), children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 px-2 text-[11px] font-medium text-sidebar-foreground/50", children: [_jsx("span", { children: copy("workspaceApps") }), _jsx("span", { className: "ml-auto", children: createTrigger })] }), loading && apps.length === 0 ? (_jsx("div", { className: "space-y-0.5 px-2", children: [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "h-8 w-full rounded-md" }, index))) })) : apps.length === 0 ? (_jsxs("div", { className: "px-2", children: [_jsx("p", { className: "text-xs text-sidebar-foreground/55", children: copy("noWorkspaceApps") }), createAppTrigger ? (createTrigger) : onCreateApp ? (_jsxs("button", { type: "button", className: "mt-1 inline-flex h-6 items-center gap-1.5 rounded-md border border-sidebar-border px-2 text-xs text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onCreateApp, children: [_jsx(IconPlus, { size: 13, "aria-hidden": "true" }), copy("createApp")] })) : (createTrigger)] })) : (_jsx(AppRows, { apps: visibleApps, activeAppId: activeAppId, layout: layout, onDragStart: setDraggedAppId, onDrop: reorderApps, onDragEnd: () => setDraggedAppId(null), onOpenApp: onOpenApp, onTogglePinned: togglePinned, onMove: moveApp, renderIcon: renderIcon, copy: copy })), hasMoreApps || showAllApps ? (_jsxs("button", { type: "button", className: "mt-0.5 flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: () => setShowAllApps((value) => !value), children: [showAllApps ? (_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" })), showAllApps ? copy("showLess") : copy("showMore")] })) : null, onOpenAllApps ? (_jsx("div", { className: "mt-1 border-t border-sidebar-border/60 pt-1", children: _jsxs("button", { type: "button", "data-chat-first-all-apps": true, className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onOpenAllApps, children: [_jsx(IconApps, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: copy("allApps") })] }) })) : null, error ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "mt-1 flex items-center gap-1 px-2 text-[10px] text-destructive/80", onClick: onRetry, "aria-label": error, children: [_jsx("span", { className: "size-1.5 rounded-full bg-destructive" }), onRetry ? copy("retry") : copy("appsLoadError")] }) }), _jsx(TooltipContent, { side: "right", children: error })] })) : null] }));
103
103
  });
@@ -17,6 +17,9 @@ export function ChatFirstBrowserPane({ url, title, status, statusMessage, onClos
17
17
  setHistoryIndex(0);
18
18
  setNavigationError(null);
19
19
  }, [url]);
20
+ const currentTarget = resolveChatFirstBrowserTarget({ url: currentUrl });
21
+ const isExternalOnly = currentTarget.status === "ready" &&
22
+ currentTarget.target.openExternally === true;
20
23
  function navigate(event) {
21
24
  event.preventDefault();
22
25
  const target = resolveChatFirstBrowserTarget({ url: draftUrl });
@@ -56,7 +59,7 @@ export function ChatFirstBrowserPane({ url, title, status, statusMessage, onClos
56
59
  }
57
60
  return (_jsxs("section", { "data-chat-first-browser-pane": true, className: "flex h-full min-h-0 w-full flex-col overflow-hidden bg-background", "aria-label": copy("surface.browser.label"), children: [_jsxs("header", { className: "flex h-12 shrink-0 items-center gap-1.5 border-b border-border bg-card px-2", children: [_jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [_jsx("button", { type: "button", disabled: historyIndex <= 0, onClick: goBack, "aria-label": copy("browserBack"), title: copy("browserBack"), className: "flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-35", children: _jsx(IconArrowLeft, { size: 15 }) }), _jsx("button", { type: "button", disabled: historyIndex >= history.length - 1, onClick: goForward, "aria-label": copy("browserForward"), title: copy("browserForward"), className: "flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-35", children: _jsx(IconArrowRight, { size: 15 }) }), _jsx("button", { type: "button", onClick: () => setReloadKey((key) => key + 1), "aria-label": copy("browserReload"), title: copy("browserReload"), className: "flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconRefresh, { size: 15 }) })] }), _jsxs("form", { className: "flex min-w-0 flex-1 items-center gap-1.5 rounded-md border border-input bg-background px-2", onSubmit: navigate, children: [_jsx(IconWorld, { size: 13, className: "shrink-0 text-muted-foreground", "aria-hidden": "true" }), _jsx("input", { value: draftUrl, onChange: (event) => setDraftUrl(event.target.value), "aria-label": copy("browserAddress"), spellCheck: false, className: "h-7 min-w-0 flex-1 bg-transparent text-xs text-foreground outline-none placeholder:text-muted-foreground" })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [title ? (_jsx("span", { className: "hidden max-w-28 truncate px-1 text-xs text-muted-foreground xl:block", title: title, children: title })) : null, _jsx("button", { type: "button", onClick: () => window.open(currentUrl, "_blank", "noopener,noreferrer"), "aria-label": copy("browserOpenExternal"), title: copy("browserOpenExternal"), className: "flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconExternalLink, { size: 15 }) }), _jsx("button", { type: "button", onClick: onClose, "aria-label": copy("browserClose"), title: copy("browserClose"), className: "flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconX, { size: 15 }) })] })] }), navigationError ? (_jsx("p", { className: "border-b border-destructive/30 bg-destructive/10 px-3 py-1.5 text-xs text-destructive", role: "alert", children: navigationError })) : null, _jsx("div", { className: "relative min-h-0 flex-1 overflow-hidden", children: status === "starting" || status === "error" ? (_jsx("div", { className: "flex h-full items-center justify-center px-6 text-center", role: status === "error" ? "alert" : "status", "aria-live": "polite", children: _jsxs("div", { className: "max-w-xs space-y-1", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: status === "starting"
58
61
  ? copy("browserPreviewStarting")
59
- : copy("browserPreviewError") }), statusMessage ? (_jsx("p", { className: "text-xs text-muted-foreground", children: statusMessage })) : null] }) })) : (renderEmbed({
62
+ : copy("browserPreviewError") }), statusMessage ? (_jsx("p", { className: "text-xs text-muted-foreground", children: statusMessage })) : null] }) })) : isExternalOnly ? (_jsx("div", { className: "flex h-full items-center justify-center p-6 text-center", children: _jsxs("div", { className: "max-w-sm space-y-3", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: copy("browserPreviewError") }), _jsxs("a", { href: currentUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent", children: [_jsx(IconExternalLink, { size: 14, "aria-hidden": "true" }), copy("browserOpenExternal")] })] }) })) : (renderEmbed({
60
63
  url: currentUrl,
61
64
  title: title || copy("browserPage"),
62
65
  key: `${currentUrl}:${reloadKey}`,
@@ -3,10 +3,12 @@ import { type ChatHistoryItem, type ChatHistoryListProps } from "../chat/ChatHis
3
3
  export interface ChatFirstChatHistoryProps extends Omit<ChatHistoryListProps, "items" | "sections" | "variant"> {
4
4
  items: ChatHistoryItem[];
5
5
  label?: ReactNode;
6
+ /** Optional contextual actions for the Chats section header. */
7
+ headerAction?: ReactNode;
6
8
  }
7
9
  /**
8
10
  * The single chat-list presentation used by the chat-first rail on every
9
11
  * host. Hosts still own thread fetching and actions, but row rhythm and the
10
12
  * Chats section treatment stay identical.
11
13
  */
12
- export declare function ChatFirstChatHistory({ items, label, className, ...props }: ChatFirstChatHistoryProps): import("react").JSX.Element | null;
14
+ export declare function ChatFirstChatHistory({ items, label, headerAction, className, ...props }: ChatFirstChatHistoryProps): import("react").JSX.Element | null;
@@ -5,8 +5,8 @@ import { ChatHistoryList, } from "../chat/ChatHistoryList.js";
5
5
  * host. Hosts still own thread fetching and actions, but row rhythm and the
6
6
  * Chats section treatment stay identical.
7
7
  */
8
- export function ChatFirstChatHistory({ items, label = "Chats", className, ...props }) {
8
+ export function ChatFirstChatHistory({ items, label = "Chats", headerAction, className, ...props }) {
9
9
  if (items.length === 0)
10
10
  return null;
11
- return (_jsxs("section", { "data-chat-first-chat-history": true, className: ["min-h-0 min-w-0", className].filter(Boolean).join(" "), children: [_jsx("p", { className: "mb-1 px-2 text-[11px] font-medium text-sidebar-foreground/50", children: label }), _jsx(ChatHistoryList, { ...props, items: items, variant: "rail", className: "min-w-0" })] }));
11
+ return (_jsxs("section", { "data-chat-first-chat-history": true, className: ["min-h-0 min-w-0", className].filter(Boolean).join(" "), children: [_jsxs("div", { "data-chat-first-chat-history-header": true, className: "group mb-1 flex min-h-6 items-center justify-between gap-2 px-2", children: [_jsx("p", { className: "min-w-0 text-[11px] font-medium text-sidebar-foreground/50", children: label }), headerAction ? (_jsx("div", { className: "shrink-0 opacity-0 transition-opacity duration-100 group-hover:opacity-100 group-focus-within:opacity-100", children: headerAction })) : null] }), _jsx(ChatHistoryList, { ...props, items: items, variant: "rail", className: "min-w-0" })] }));
12
12
  }
@@ -1,7 +1,7 @@
1
1
  import type { ReactNode } from "react";
2
2
  import { type ChatFirstSurfaceTab } from "../chat-first.js";
3
3
  import type { ChatFirstSurfaceTabsProps } from "./types.js";
4
- export declare function ChatFirstSurfaceTabs({ tabs, activeTabId, onActivate, onClose, onCloseOthers, onCloseToRight, onCloseAll, onOpenSurface, copy, }: ChatFirstSurfaceTabsProps): import("react").JSX.Element;
4
+ export declare function ChatFirstSurfaceTabs({ tabs, activeTabId, onActivate, onClose, onCloseOthers, onCloseToRight, onCloseAll, onOpenSurface, apps, onOpenApp, renderAppIcon, copy, }: ChatFirstSurfaceTabsProps): import("react").JSX.Element;
5
5
  export declare function ChatFirstSurfaceContent({ tabs, activeTabId, renderTab, }: {
6
6
  tabs: readonly ChatFirstSurfaceTab[];
7
7
  activeTabId: string | null;
@@ -17,54 +17,65 @@ function SurfaceIcon({ kind }) {
17
17
  const Icon = ICONS[kind];
18
18
  return _jsx(Icon, { size: 14, "aria-hidden": "true" });
19
19
  }
20
- export function ChatFirstSurfaceTabs({ tabs, activeTabId, onActivate, onClose, onCloseOthers, onCloseToRight, onCloseAll, onOpenSurface, copy = defaultChatFirstCopy, }) {
21
- return (_jsx(TooltipProvider, { delayDuration: 400, children: _jsx("div", { "data-chat-first-surface-tabs": true, className: "shrink-0 border-b border-border bg-card", children: tabs.length > 0 ? (_jsx("div", { className: "flex min-w-0 items-center gap-1 overflow-x-auto px-2 py-1.5", role: "tablist", "aria-label": copy("openSideSurfaces"), children: tabs.map((tab, index) => (_jsxs(ContextMenu, { children: [_jsx(ContextMenuTrigger, { asChild: true, children: _jsxs("div", { role: "tab", tabIndex: activeTabId === tab.id ? 0 : -1, "aria-selected": activeTabId === tab.id, "data-surface-tab-id": tab.id, "data-active": activeTabId === tab.id ? "true" : "false", className: cn("group flex h-8 min-w-0 max-w-48 shrink-0 items-center rounded-md text-xs transition-colors", activeTabId === tab.id
20
+ export function ChatFirstSurfaceTabs({ tabs, activeTabId, onActivate, onClose, onCloseOthers, onCloseToRight, onCloseAll, onOpenSurface, apps = [], onOpenApp, renderAppIcon, copy = defaultChatFirstCopy, }) {
21
+ return (_jsx(TooltipProvider, { delayDuration: 400, children: _jsx("div", { "data-chat-first-surface-tabs": true, className: cn("min-w-0 border-b border-border bg-card", tabs.length > 0
22
+ ? "shrink-0"
23
+ : "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden"), children: tabs.length > 0 ? (_jsx("div", { className: "flex min-w-0 items-center gap-1 overflow-x-auto px-2 py-1.5", role: "tablist", "aria-label": copy("openSideSurfaces"), children: tabs.map((tab, index) => (_jsxs(ContextMenu, { children: [_jsx(ContextMenuTrigger, { asChild: true, children: _jsxs("div", { "data-surface-tab-id": tab.id, "data-active": activeTabId === tab.id ? "true" : "false", className: cn("group flex h-8 min-w-0 max-w-48 shrink-0 items-center rounded-md text-xs transition-colors", activeTabId === tab.id
22
24
  ? "bg-accent text-foreground"
23
- : "text-muted-foreground hover:bg-accent hover:text-foreground"), onKeyDown: (event) => {
24
- const nextIndex = event.key === "ArrowRight"
25
- ? (index + 1) % tabs.length
26
- : event.key === "ArrowLeft"
27
- ? (index - 1 + tabs.length) % tabs.length
28
- : event.key === "Home"
29
- ? 0
30
- : event.key === "End"
31
- ? tabs.length - 1
32
- : -1;
33
- if (nextIndex < 0)
34
- return;
35
- event.preventDefault();
36
- onActivate(tabs[nextIndex]);
37
- requestAnimationFrame(() => {
38
- document
39
- .querySelector(`[data-chat-first-surface-tab-id="${CSS.escape(tabs[nextIndex].id)}"]`)
40
- ?.focus();
41
- });
42
- }, "data-chat-first-surface-tab-id": tab.id, onMouseDown: (event) => {
25
+ : "text-muted-foreground hover:bg-accent hover:text-foreground"), onMouseDown: (event) => {
43
26
  if (event.button === 1) {
44
27
  event.preventDefault();
45
28
  onClose(tab);
46
29
  }
47
- }, title: tab.title, children: [_jsxs("button", { type: "button", className: "flex min-w-0 flex-1 items-center gap-1.5 truncate px-2 text-start", onClick: () => onActivate(tab), tabIndex: -1, children: [_jsx(SurfaceIcon, { kind: tab.kind }), _jsx("span", { className: "truncate", children: tab.title })] }), _jsx("button", { type: "button", className: "mr-1 flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground/65 opacity-0 transition-opacity hover:bg-background/70 hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100", "aria-label": copy("closeTab", { name: tab.title }), title: copy("closeTab", { name: tab.title }), onClick: (event) => {
30
+ }, title: tab.title, children: [_jsxs("button", { type: "button", role: "tab", tabIndex: activeTabId === tab.id ? 0 : -1, "aria-selected": activeTabId === tab.id, "data-chat-first-surface-tab-id": tab.id, className: "flex min-w-0 flex-1 items-center gap-1.5 truncate rounded-md px-2 text-start focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", onKeyDown: (event) => {
31
+ if (event.key === "Enter" || event.key === " ") {
32
+ event.preventDefault();
33
+ onActivate(tab);
34
+ return;
35
+ }
36
+ const nextIndex = event.key === "ArrowRight"
37
+ ? (index + 1) % tabs.length
38
+ : event.key === "ArrowLeft"
39
+ ? (index - 1 + tabs.length) % tabs.length
40
+ : event.key === "Home"
41
+ ? 0
42
+ : event.key === "End"
43
+ ? tabs.length - 1
44
+ : -1;
45
+ if (nextIndex < 0)
46
+ return;
47
+ event.preventDefault();
48
+ onActivate(tabs[nextIndex]);
49
+ requestAnimationFrame(() => {
50
+ document
51
+ .querySelector(`[data-chat-first-surface-tab-id="${CSS.escape(tabs[nextIndex].id)}"]`)
52
+ ?.focus();
53
+ });
54
+ }, onClick: () => onActivate(tab), children: [_jsx(SurfaceIcon, { kind: tab.kind }), _jsx("span", { className: "truncate", children: tab.title })] }), _jsx("button", { type: "button", className: "mr-1 flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground/65 opacity-0 transition-opacity hover:bg-background/70 hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100", "aria-label": copy("closeTab", { name: tab.title }), title: copy("closeTab", { name: tab.title }), onClick: (event) => {
48
55
  event.stopPropagation();
49
56
  onClose(tab);
50
- }, children: _jsx(IconX, { size: 12, "aria-hidden": "true" }) })] }) }), _jsxs(ContextMenuContent, { children: [_jsx(ContextMenuItem, { onSelect: () => onClose(tab), children: copy("close") }), _jsx(ContextMenuItem, { disabled: tabs.length < 2, onSelect: () => onCloseOthers(tab), children: copy("closeOthers") }), _jsx(ContextMenuItem, { disabled: tabs[tabs.length - 1]?.id === tab.id, onSelect: () => onCloseToRight(tab), children: copy("closeToRight") }), _jsx(ContextMenuSeparator, {}), _jsx(ContextMenuItem, { onSelect: onCloseAll, children: copy("closeAll") })] })] }, tab.id))) })) : (_jsx("div", { className: "flex flex-col px-2 py-1", "data-surface-empty-state": true, "aria-label": copy("openSideSurfaces"), children: CHAT_FIRST_SURFACE_CATALOG.map((surface) => {
51
- const label = copy(`surface.${surface.kind}.label`);
52
- const reason = surface.availability === "deferred"
53
- ? copy(`surface.${surface.kind}.reason`)
54
- : copy(`surface.${surface.kind}.reason`);
55
- const isDeferred = surface.availability === "deferred";
56
- const row = (_jsxs("div", { className: cn("flex h-7 min-w-0 items-center gap-2 rounded px-2 text-xs transition-colors", surface.kind === "agents" && onOpenSurface
57
- ? "text-foreground hover:bg-accent"
58
- : isDeferred
59
- ? "text-muted-foreground/60"
60
- : "text-muted-foreground hover:bg-accent hover:text-foreground"), title: !isDeferred ? reason : undefined, "aria-label": `${label}: ${reason}`, children: [_jsx(SurfaceIcon, { kind: surface.kind }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: label }), isDeferred ? (_jsx("span", { "data-surface-availability": "deferred", className: "size-1.5 shrink-0 rounded-full bg-muted-foreground/45", "aria-label": copy("deferred") })) : null] }));
61
- if (surface.kind === "agents" && onOpenSurface) {
62
- return (_jsx("button", { type: "button", className: "text-start", title: copy("openActivity"), "aria-label": copy("openActivity"), onClick: () => onOpenSurface(surface.kind), children: row }, surface.kind));
63
- }
64
- if (!isDeferred)
65
- return _jsx("div", { children: row }, surface.kind);
66
- return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("div", { className: "text-start", children: row }) }), _jsx(TooltipContent, { side: "left", className: "max-w-72", children: reason })] }, surface.kind));
67
- }) })) }) }));
57
+ }, children: _jsx(IconX, { size: 12, "aria-hidden": "true" }) })] }) }), _jsxs(ContextMenuContent, { children: [_jsx(ContextMenuItem, { onSelect: () => onClose(tab), children: copy("close") }), _jsx(ContextMenuItem, { disabled: tabs.length < 2, onSelect: () => onCloseOthers(tab), children: copy("closeOthers") }), _jsx(ContextMenuItem, { disabled: tabs[tabs.length - 1]?.id === tab.id, onSelect: () => onCloseToRight(tab), children: copy("closeToRight") }), _jsx(ContextMenuSeparator, {}), _jsx(ContextMenuItem, { onSelect: onCloseAll, children: copy("closeAll") })] })] }, tab.id))) })) : (_jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-y-auto px-2 py-1", "data-surface-empty-state": true, "aria-label": copy("openSideSurfaces"), children: [CHAT_FIRST_SURFACE_CATALOG.map((surface) => {
58
+ const label = copy(`surface.${surface.kind}.label`);
59
+ const reason = surface.availability === "deferred"
60
+ ? copy(`surface.${surface.kind}.reason`)
61
+ : copy(`surface.${surface.kind}.reason`);
62
+ const isDeferred = surface.availability === "deferred";
63
+ const canOpenSurface = onOpenSurface &&
64
+ (surface.kind === "browser" ||
65
+ surface.kind === "side-chat" ||
66
+ surface.kind === "agents");
67
+ const row = (_jsxs("div", { className: cn("flex h-7 min-w-0 items-center gap-2 rounded px-2 text-xs transition-colors", canOpenSurface
68
+ ? "text-foreground hover:bg-accent"
69
+ : isDeferred
70
+ ? "text-muted-foreground/60"
71
+ : "text-muted-foreground hover:bg-accent hover:text-foreground"), title: !isDeferred ? reason : undefined, "aria-label": `${label}: ${reason}`, children: [_jsx(SurfaceIcon, { kind: surface.kind }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: label }), isDeferred ? (_jsx("span", { "data-surface-availability": "deferred", className: "size-1.5 shrink-0 rounded-full bg-muted-foreground/45", "aria-label": copy("deferred") })) : null] }));
72
+ if (canOpenSurface) {
73
+ return (_jsx("button", { type: "button", className: "block w-full text-start", title: surface.kind === "browser" ? reason : copy("openActivity"), "aria-label": surface.kind === "browser" ? label : copy("openActivity"), onClick: () => onOpenSurface(surface.kind === "side-chat" ? "agents" : surface.kind), children: row }, surface.kind));
74
+ }
75
+ if (!isDeferred)
76
+ return _jsx("div", { children: row }, surface.kind);
77
+ return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("div", { className: "text-start", children: row }) }), _jsx(TooltipContent, { side: "left", className: "max-w-72", children: reason })] }, surface.kind));
78
+ }), apps.length > 0 && onOpenApp ? (_jsxs("div", { className: "mt-1 border-t border-border pt-1", children: [_jsx("p", { className: "px-2 py-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: copy("workspaceApps") }), _jsx("div", { className: "space-y-0.5", children: apps.map((app) => (_jsxs("button", { type: "button", "data-chat-first-surface-app": app.id, className: "flex h-8 w-full min-w-0 items-center gap-2 rounded px-2 text-start text-xs text-foreground transition-colors hover:bg-accent", title: copy("openApp", { name: app.name }), "aria-label": copy("openApp", { name: app.name }), onClick: () => onOpenApp(app), children: [renderAppIcon?.(app) ?? _jsx(SurfaceIcon, { kind: "app" }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: app.name })] }, app.id))) })] })) : null] })) }) }));
68
79
  }
69
80
  export function ChatFirstSurfaceContent({ tabs, activeTabId, renderTab, }) {
70
81
  return (_jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: tabs.map((tab) => {
@@ -31,6 +31,9 @@ export interface ChatFirstSurfaceTabsProps {
31
31
  onCloseToRight: (tab: ChatFirstSurfaceTab) => void;
32
32
  onCloseAll: () => void;
33
33
  onOpenSurface?: (kind: ChatFirstSurfaceKind) => void;
34
+ apps?: readonly ChatFirstAppItem[];
35
+ onOpenApp?: (app: ChatFirstAppItem) => void;
36
+ renderAppIcon?: (app: ChatFirstAppItem) => ReactNode;
34
37
  copy?: ChatFirstCopy;
35
38
  }
36
39
  export interface ChatFirstAppRailProps {
@@ -36,11 +36,14 @@ export interface ChatFirstSessionWatchStore {
36
36
  close: () => void;
37
37
  }
38
38
  export type ChatFirstSurfaceKind = "app" | "browser" | "terminal" | "diff" | "files" | "side-chat" | "agents";
39
+ export type ChatFirstAppSurfacePlacement = "main" | "side";
39
40
  export interface ChatFirstSurfaceTab {
40
41
  id: string;
41
42
  kind: ChatFirstSurfaceKind;
42
43
  title: string;
43
44
  appId?: string;
45
+ /** App tabs can either replace the main chat or sit beside it. */
46
+ placement?: ChatFirstAppSurfacePlacement;
44
47
  path?: string;
45
48
  view?: string;
46
49
  url?: string;
@@ -128,6 +131,7 @@ export type ChatFirstBrowserResolution = {
128
131
  target: {
129
132
  url: string;
130
133
  title?: string;
134
+ openExternally?: boolean;
131
135
  };
132
136
  } | {
133
137
  status: "unresolved";
@@ -202,6 +206,12 @@ export declare function subscribeChatFirstOpenApp(listener: ChatFirstOpenAppList
202
206
  export declare function emitChatFirstOpenApp(detail: ChatFirstOpenAppDetail): ChatFirstOpenAppDelivery;
203
207
  export declare function subscribeChatFirstOpenBrowser(listener: ChatFirstOpenBrowserListener): () => void;
204
208
  export declare function emitChatFirstOpenBrowser(detail: ChatFirstOpenBrowserDetail): ChatFirstOpenAppDelivery;
209
+ /**
210
+ * Builder's web app sends frame-blocking headers. Its branch/editor URLs are
211
+ * open-browser targets, not the iframe-backed Fusion preview URLs served from
212
+ * builder.cloud, so keep them out of the chat-first browser iframe.
213
+ */
214
+ export declare function shouldOpenChatFirstBrowserExternally(value: string): boolean;
205
215
  export declare function resolveChatFirstAppTarget(detail: ChatFirstOpenAppDetail, apps: readonly ChatFirstAppRegistration[], options?: {
206
216
  currentOrigin?: string;
207
217
  }): ChatFirstAppResolution;
@@ -338,6 +338,9 @@ export function resolveChatFirstBrowserTarget(detail) {
338
338
  target: {
339
339
  url: url.href,
340
340
  ...(detail.title?.trim() ? { title: detail.title.trim() } : {}),
341
+ ...(shouldOpenChatFirstBrowserExternally(url.href)
342
+ ? { openExternally: true }
343
+ : {}),
341
344
  },
342
345
  };
343
346
  }
@@ -464,9 +467,16 @@ function readChatFirstSurfaceTabsState(scope) {
464
467
  return { tabs: [], activeTabId: null };
465
468
  }
466
469
  const value = scoped;
470
+ let migratedLegacyPlacement = false;
467
471
  const tabs = Array.isArray(value.tabs)
468
472
  ? value.tabs.flatMap((candidate) => {
469
473
  const tab = normalizePersistedChatFirstSurfaceTab(candidate);
474
+ if (tab?.kind === "app" &&
475
+ candidate !== null &&
476
+ typeof candidate === "object" &&
477
+ !("placement" in candidate)) {
478
+ migratedLegacyPlacement = true;
479
+ }
470
480
  return tab ? [tab] : [];
471
481
  })
472
482
  : [];
@@ -474,7 +484,10 @@ function readChatFirstSurfaceTabsState(scope) {
474
484
  tabs.some((tab) => tab.id === value.activeTabId)
475
485
  ? value.activeTabId
476
486
  : (tabs[0]?.id ?? null);
477
- return { tabs, activeTabId };
487
+ const state = { tabs, activeTabId };
488
+ if (migratedLegacyPlacement)
489
+ writeChatFirstSurfaceTabsState(scope, state);
490
+ return state;
478
491
  }
479
492
  catch {
480
493
  return { tabs: [], activeTabId: null };
@@ -498,11 +511,23 @@ function normalizePersistedChatFirstSurfaceTab(candidate) {
498
511
  const appId = typeof value.appId === "string" ? value.appId.trim() : "";
499
512
  if (!appId)
500
513
  return null;
514
+ const placement = value.placement;
515
+ if (placement !== undefined &&
516
+ placement !== "main" &&
517
+ placement !== "side") {
518
+ return null;
519
+ }
501
520
  if (value.path !== undefined &&
502
521
  (typeof value.path !== "string" || normalizedPath(value.path) === null)) {
503
522
  return null;
504
523
  }
505
- return { ...tab, id, title, appId };
524
+ return {
525
+ ...tab,
526
+ id,
527
+ title,
528
+ appId,
529
+ placement: placement ?? "main",
530
+ };
506
531
  }
507
532
  if (kind === "browser") {
508
533
  const url = typeof value.url === "string" ? value.url.trim() : "";
@@ -700,6 +725,16 @@ function parseAbsoluteHttpUrl(value) {
700
725
  return null;
701
726
  }
702
727
  }
728
+ /**
729
+ * Builder's web app sends frame-blocking headers. Its branch/editor URLs are
730
+ * open-browser targets, not the iframe-backed Fusion preview URLs served from
731
+ * builder.cloud, so keep them out of the chat-first browser iframe.
732
+ */
733
+ export function shouldOpenChatFirstBrowserExternally(value) {
734
+ const url = parseAbsoluteHttpUrl(value);
735
+ return Boolean(url &&
736
+ (url.hostname === "builder.io" || url.hostname.endsWith(".builder.io")));
737
+ }
703
738
  function registrationUrls(app, currentOrigin) {
704
739
  const values = [app.url, app.devUrl];
705
740
  if (app.path && currentOrigin)
@@ -10,7 +10,10 @@ function MessageScroller({ className, ...props }) {
10
10
  }
11
11
  function MessageScrollerViewport({ className, children, ...props }) {
12
12
  const { start: hasContentAbove } = useMessageScrollerScrollable();
13
- return (_jsx(ShadcnMessageScroller.Viewport, { className: cn("min-h-0 flex-1 overflow-y-auto overflow-x-hidden", hasContentAbove && "message-scroller-viewport--top-fade", className), ...props, children: children }));
13
+ return (_jsx(ShadcnMessageScroller.Viewport, { className: cn("min-h-0 flex-1 overflow-y-auto overflow-x-hidden",
14
+ // The base class carries the mask itself so it is never added or
15
+ // removed; the modifier only widens the fade. See agent-native.css.
16
+ "message-scroller-viewport", hasContentAbove && "message-scroller-viewport--top-fade", className), ...props, children: children }));
14
17
  }
15
18
  function MessageScrollerContent({ className, spacerClassName, ...props }) {
16
19
  return (_jsx(ShadcnMessageScroller.Content, { className: cn("flex flex-col", className), spacerClassName: cn("shrink-0", spacerClassName), ...props }));