@agent-native/core 0.91.2 → 0.92.0

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 (216) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +17 -0
  3. package/corpus/core/docs/content/faq.mdx +17 -0
  4. package/corpus/core/docs/content/notifications.mdx +12 -6
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/agent/production-agent.ts +140 -46
  7. package/corpus/core/src/agent/tool-search.ts +71 -4
  8. package/corpus/core/src/cli/index.ts +20 -0
  9. package/corpus/core/src/cli/upgrade.ts +798 -0
  10. package/corpus/core/src/client/AssistantChat.tsx +1 -1
  11. package/corpus/core/src/client/HighlightedCodeBlock.tsx +20 -3
  12. package/corpus/core/src/client/agent-chat-adapter.ts +31 -6
  13. package/corpus/core/src/client/chat/message-components.tsx +85 -8
  14. package/corpus/core/src/client/chat/tool-call-display.tsx +355 -144
  15. package/corpus/core/src/client/code-agent-chat-adapter.ts +2 -9
  16. package/corpus/core/src/client/session-replay.ts +3 -97
  17. package/corpus/core/src/client/settings/SettingsPanel.tsx +533 -389
  18. package/corpus/core/src/client/settings/SettingsSection.tsx +154 -17
  19. package/corpus/core/src/client/sse-event-processor.ts +29 -2
  20. package/corpus/core/src/extensions/actions.ts +113 -5
  21. package/corpus/core/src/notifications/channels.ts +88 -25
  22. package/corpus/core/src/notifications/registry.ts +17 -3
  23. package/corpus/core/src/notifications/types.ts +4 -1
  24. package/corpus/core/src/server/agent-chat-plugin.ts +7 -3
  25. package/corpus/core/src/server/request-context.ts +7 -0
  26. package/corpus/core/src/styles/agent-conversation.css +1 -1
  27. package/corpus/core/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  28. package/corpus/core/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  29. package/corpus/core/src/templates/default/AGENTS.md +10 -3
  30. package/corpus/core/src/templates/default/package.json +2 -1
  31. package/corpus/core/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  32. package/corpus/core/src/templates/headless/AGENTS.md +9 -3
  33. package/corpus/core/src/templates/headless/package.json +1 -0
  34. package/corpus/core/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  35. package/corpus/core/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  36. package/corpus/core/src/templates/workspace-root/AGENTS.md +10 -4
  37. package/corpus/core/src/templates/workspace-root/package.json +1 -0
  38. package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +20 -2
  39. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +5 -6
  40. package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  41. package/corpus/templates/analytics/AGENTS.md +7 -5
  42. package/corpus/templates/analytics/actions/save-monitor.ts +14 -0
  43. package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +41 -6
  44. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +122 -11
  45. package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +8 -2
  46. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +151 -32
  47. package/corpus/templates/analytics/app/pages/monitoring/uptime/i18n.ts +17 -2
  48. package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +4 -0
  49. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +54 -190
  50. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-tables-keep-their-size-while-loading.md +6 -0
  51. package/corpus/templates/analytics/changelog/2026-07-08-monitoring-uptime-and-errors-tabs-now-clearly-show-which-view-is-active.md +6 -0
  52. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-uses-stock-rrweb-sizing-again-so-recorded.md +6 -0
  53. package/corpus/templates/analytics/changelog/2026-07-08-sidebar-dashboard-menus-now-open-above-the-dashboard-canvas.md +6 -0
  54. package/corpus/templates/analytics/changelog/2026-07-08-uptime-alerting-clarifies-in-app-inbox-vs-email-and-lets-you-paste-slack-or-webhook-urls-per-monitor.md +6 -0
  55. package/corpus/templates/analytics/docs/uptime-monitoring.md +13 -11
  56. package/corpus/templates/analytics/server/db/schema-monitoring.ts +4 -0
  57. package/corpus/templates/analytics/server/lib/uptime-monitors.ts +60 -12
  58. package/corpus/templates/analytics/server/plugins/db.ts +10 -0
  59. package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +20 -2
  60. package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  61. package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +20 -2
  62. package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  63. package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +20 -2
  64. package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  65. package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +20 -2
  66. package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  67. package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +20 -2
  68. package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  69. package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-notes-stay-active-from-tray-and-shortcut-controls.md +6 -0
  70. package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-popovers-can-start-notes-and-join-the-meetin.md +6 -0
  71. package/corpus/templates/clips/changelog/2026-07-08-meeting-notes-keep-their-recording-state-when-the-desktop.md +6 -0
  72. package/corpus/templates/clips/changelog/2026-07-08-meeting-reminder-dropdowns-now-expand-fully-instead-of-being.md +6 -0
  73. package/corpus/templates/clips/desktop/src/app.tsx +123 -3
  74. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +15 -1
  75. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +23 -0
  76. package/corpus/templates/clips/desktop/src/styles.css +23 -1
  77. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +1 -3
  78. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +40 -6
  79. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +4 -2
  80. package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +3 -5
  81. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +23 -19
  82. package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +3 -3
  83. package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +0 -1
  84. package/corpus/templates/clips/desktop/src-tauri/src/state.rs +5 -0
  85. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +18 -6
  86. package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +20 -2
  87. package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  88. package/corpus/templates/content/actions/_batch-utils.ts +8 -0
  89. package/corpus/templates/content/actions/_builder-cms-read-client.ts +7 -5
  90. package/corpus/templates/content/actions/_database-source-utils.ts +776 -319
  91. package/corpus/templates/content/actions/_database-utils.ts +6 -1
  92. package/corpus/templates/content/actions/_property-utils.ts +168 -0
  93. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +19 -12
  94. package/corpus/templates/content/actions/bind-content-database-source-field.ts +19 -12
  95. package/corpus/templates/content/actions/delete-document.ts +9 -13
  96. package/corpus/templates/content/actions/refresh-content-database-source.ts +5 -5
  97. package/corpus/templates/content/app/components/editor/body-hydration.ts +35 -11
  98. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +47 -5
  99. package/corpus/templates/content/app/hooks/use-documents.ts +8 -2
  100. package/corpus/templates/content/changelog/2026-07-08-builder-source-sync-now-completes-reliably-on-hosted-databases.md +6 -0
  101. package/corpus/templates/content/changelog/2026-07-08-fixed-builder-database-sync-so-large-sources-continue-past-t.md +6 -0
  102. package/corpus/templates/content/server/plugins/db.ts +19 -0
  103. package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +20 -2
  104. package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  105. package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +20 -2
  106. package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  107. package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +20 -2
  108. package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  109. package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  110. package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +20 -2
  111. package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  112. package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +20 -2
  113. package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  114. package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +20 -2
  115. package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  116. package/corpus/templates/videos/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  117. package/corpus/templates/voice/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  118. package/dist/agent/production-agent.d.ts +5 -1
  119. package/dist/agent/production-agent.d.ts.map +1 -1
  120. package/dist/agent/production-agent.js +112 -42
  121. package/dist/agent/production-agent.js.map +1 -1
  122. package/dist/agent/tool-search.d.ts +2 -0
  123. package/dist/agent/tool-search.d.ts.map +1 -1
  124. package/dist/agent/tool-search.js +60 -4
  125. package/dist/agent/tool-search.js.map +1 -1
  126. package/dist/cli/index.js +19 -0
  127. package/dist/cli/index.js.map +1 -1
  128. package/dist/cli/upgrade.d.ts +100 -0
  129. package/dist/cli/upgrade.d.ts.map +1 -0
  130. package/dist/cli/upgrade.js +678 -0
  131. package/dist/cli/upgrade.js.map +1 -0
  132. package/dist/client/AssistantChat.js +1 -1
  133. package/dist/client/AssistantChat.js.map +1 -1
  134. package/dist/client/HighlightedCodeBlock.d.ts +2 -0
  135. package/dist/client/HighlightedCodeBlock.d.ts.map +1 -1
  136. package/dist/client/HighlightedCodeBlock.js +10 -1
  137. package/dist/client/HighlightedCodeBlock.js.map +1 -1
  138. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  139. package/dist/client/agent-chat-adapter.js +32 -4
  140. package/dist/client/agent-chat-adapter.js.map +1 -1
  141. package/dist/client/chat/message-components.d.ts.map +1 -1
  142. package/dist/client/chat/message-components.js +61 -8
  143. package/dist/client/chat/message-components.js.map +1 -1
  144. package/dist/client/chat/tool-call-display.d.ts +12 -0
  145. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  146. package/dist/client/chat/tool-call-display.js +136 -42
  147. package/dist/client/chat/tool-call-display.js.map +1 -1
  148. package/dist/client/code-agent-chat-adapter.js +2 -9
  149. package/dist/client/code-agent-chat-adapter.js.map +1 -1
  150. package/dist/client/session-replay.d.ts +0 -8
  151. package/dist/client/session-replay.d.ts.map +1 -1
  152. package/dist/client/session-replay.js +3 -80
  153. package/dist/client/session-replay.js.map +1 -1
  154. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  155. package/dist/client/settings/SettingsPanel.js +52 -19
  156. package/dist/client/settings/SettingsPanel.js.map +1 -1
  157. package/dist/client/settings/SettingsSection.d.ts +19 -4
  158. package/dist/client/settings/SettingsSection.d.ts.map +1 -1
  159. package/dist/client/settings/SettingsSection.js +33 -4
  160. package/dist/client/settings/SettingsSection.js.map +1 -1
  161. package/dist/client/sse-event-processor.d.ts +9 -0
  162. package/dist/client/sse-event-processor.d.ts.map +1 -1
  163. package/dist/client/sse-event-processor.js +20 -2
  164. package/dist/client/sse-event-processor.js.map +1 -1
  165. package/dist/collab/routes.d.ts +1 -1
  166. package/dist/extensions/actions.d.ts.map +1 -1
  167. package/dist/extensions/actions.js +85 -5
  168. package/dist/extensions/actions.js.map +1 -1
  169. package/dist/notifications/channels.d.ts +8 -3
  170. package/dist/notifications/channels.d.ts.map +1 -1
  171. package/dist/notifications/channels.js +61 -22
  172. package/dist/notifications/channels.js.map +1 -1
  173. package/dist/notifications/registry.d.ts.map +1 -1
  174. package/dist/notifications/registry.js +14 -3
  175. package/dist/notifications/registry.js.map +1 -1
  176. package/dist/notifications/routes.d.ts +1 -1
  177. package/dist/notifications/types.d.ts +4 -1
  178. package/dist/notifications/types.d.ts.map +1 -1
  179. package/dist/notifications/types.js.map +1 -1
  180. package/dist/observability/routes.d.ts +1 -1
  181. package/dist/resources/handlers.d.ts +1 -1
  182. package/dist/server/agent-chat-plugin.d.ts +5 -1
  183. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  184. package/dist/server/agent-chat-plugin.js +2 -2
  185. package/dist/server/agent-chat-plugin.js.map +1 -1
  186. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  187. package/dist/server/request-context.d.ts +7 -0
  188. package/dist/server/request-context.d.ts.map +1 -1
  189. package/dist/server/request-context.js.map +1 -1
  190. package/dist/styles/agent-conversation.css +1 -1
  191. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  192. package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  193. package/dist/templates/default/AGENTS.md +10 -3
  194. package/dist/templates/default/package.json +2 -1
  195. package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  196. package/dist/templates/headless/AGENTS.md +9 -3
  197. package/dist/templates/headless/package.json +1 -0
  198. package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  199. package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  200. package/dist/templates/workspace-root/AGENTS.md +10 -4
  201. package/dist/templates/workspace-root/package.json +1 -0
  202. package/docs/content/faq.mdx +17 -0
  203. package/docs/content/notifications.mdx +12 -6
  204. package/package.json +2 -2
  205. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  206. package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  207. package/src/templates/default/AGENTS.md +10 -3
  208. package/src/templates/default/package.json +2 -1
  209. package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  210. package/src/templates/headless/AGENTS.md +9 -3
  211. package/src/templates/headless/package.json +1 -0
  212. package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  213. package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  214. package/src/templates/workspace-root/AGENTS.md +10 -4
  215. package/src/templates/workspace-root/package.json +1 -0
  216. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-clamps-ultra-wide-viewports-while-keeping-the.md +0 -6
@@ -6,12 +6,14 @@ import {
6
6
  IconLoader2,
7
7
  IconCircleX,
8
8
  IconCheck,
9
- IconSquareFilled,
10
- IconChevronDown,
9
+ IconChevronRight,
11
10
  IconCopy,
11
+ IconCode,
12
+ IconBrandSlack,
13
+ IconTerminal2,
14
+ IconDatabase,
12
15
  IconSearch,
13
- IconArrowsMaximize,
14
- IconArrowsMinimize,
16
+ IconFileCode,
15
17
  IconShieldCheck,
16
18
  IconX,
17
19
  } from "@tabler/icons-react";
@@ -19,7 +21,7 @@ import React, {
19
21
  useState,
20
22
  useEffect,
21
23
  useCallback,
22
- useMemo,
24
+ useLayoutEffect,
23
25
  useRef,
24
26
  } from "react";
25
27
 
@@ -27,6 +29,7 @@ import type { ActionChatUIConfig } from "../../action-ui.js";
27
29
  import type { AgentMcpAppPayload } from "../../mcp-client/app-result.js";
28
30
  import { AgentTaskCard } from "../AgentTaskCard.js";
29
31
  import { writeClipboardText } from "../clipboard.js";
32
+ import { Dialog, DialogContent, DialogTitle } from "../components/ui/dialog.js";
30
33
  import { ConnectBuilderCard } from "../ConnectBuilderCard.js";
31
34
  import { McpAppRenderer } from "../mcp-apps/McpAppRenderer.js";
32
35
  import type { ContentPart } from "../sse-event-processor.js";
@@ -225,53 +228,97 @@ export function toolResultPayload(
225
228
  };
226
229
  }
227
230
 
228
- // ─── Search highlight helpers ─────────────────────────────────────────────────
231
+ // ─── Tool icon helpers ────────────────────────────────────────────────────────
229
232
 
230
- function escapeRegExp(value: string): string {
231
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
232
- }
233
-
234
- function countTextMatches(text: string, query: string): number {
235
- const needle = query.trim();
236
- if (!needle) return 0;
237
- return Array.from(text.matchAll(new RegExp(escapeRegExp(needle), "gi")))
238
- .length;
239
- }
233
+ type ToolIconComponent = React.ComponentType<{
234
+ className?: string;
235
+ size?: number | string;
236
+ }>;
240
237
 
241
- function renderHighlightedSearchText(
242
- text: string,
243
- query: string,
244
- ): React.ReactNode {
245
- const needle = query.trim();
246
- if (!needle) return text;
247
- const regex = new RegExp(escapeRegExp(needle), "gi");
248
- const parts: React.ReactNode[] = [];
249
- let lastIndex = 0;
250
- let match: RegExpExecArray | null;
251
- while ((match = regex.exec(text))) {
252
- if (match.index > lastIndex) {
253
- parts.push(text.slice(lastIndex, match.index));
254
- }
255
- parts.push(<mark key={`${match.index}-${match[0]}`}>{match[0]}</mark>);
256
- lastIndex = match.index + match[0].length;
257
- if (match[0].length === 0) regex.lastIndex += 1;
238
+ function resolveToolIcon(toolName: string): ToolIconComponent {
239
+ const name = toolName.toLowerCase();
240
+ if (name.includes("slack")) return IconBrandSlack;
241
+ if (
242
+ name.includes("bash") ||
243
+ name.includes("shell") ||
244
+ name.includes("terminal") ||
245
+ name.includes("run-code") ||
246
+ name.includes("exec")
247
+ ) {
248
+ return IconTerminal2;
249
+ }
250
+ if (
251
+ name.includes("sql") ||
252
+ name.includes("bigquery") ||
253
+ name.includes("db-query") ||
254
+ name.includes("query")
255
+ ) {
256
+ return IconDatabase;
258
257
  }
259
- if (lastIndex < text.length) parts.push(text.slice(lastIndex));
260
- return parts;
258
+ if (
259
+ name.includes("search") ||
260
+ name.includes("find") ||
261
+ name.includes("grep")
262
+ ) {
263
+ return IconSearch;
264
+ }
265
+ if (
266
+ name.includes("file") ||
267
+ name.includes("read") ||
268
+ name.includes("write") ||
269
+ name.includes("edit")
270
+ ) {
271
+ return IconFileCode;
272
+ }
273
+ return IconCode;
261
274
  }
262
275
 
263
- // ─── ToolDetailViewer ──────────────────────────────────────────────────────────
276
+ // ─── Simple code viewer (Codex-style gray box) ────────────────────────────────
264
277
 
265
- function ToolDetailViewer({ payload }: { payload: ToolDetailPayload }) {
266
- const [expanded, setExpanded] = useState(false);
267
- const [searchOpen, setSearchOpen] = useState(false);
268
- const [search, setSearch] = useState("");
278
+ function SimpleCodeViewer({
279
+ text,
280
+ lang,
281
+ className,
282
+ maxHeightClass = "max-h-56",
283
+ }: {
284
+ text: string;
285
+ lang: string;
286
+ className?: string;
287
+ maxHeightClass?: string;
288
+ }) {
289
+ return (
290
+ <div
291
+ className={cn(
292
+ "agent-tool-code overflow-auto rounded-md bg-muted/70 font-mono text-[11px] leading-relaxed text-foreground",
293
+ maxHeightClass,
294
+ className,
295
+ )}
296
+ >
297
+ {lang !== "text" && (
298
+ <div className="sticky top-0 z-[1] flex items-center justify-between border-b border-border/40 bg-muted/90 px-2.5 py-1">
299
+ <span className="font-mono text-[10px] uppercase tracking-wide text-muted-foreground/80">
300
+ {lang}
301
+ </span>
302
+ </div>
303
+ )}
304
+ <HighlightedCodeBlock code={text} lang={lang} />
305
+ </div>
306
+ );
307
+ }
308
+
309
+ function ToolOutputModal({
310
+ open,
311
+ onOpenChange,
312
+ title,
313
+ payload,
314
+ }: {
315
+ open: boolean;
316
+ onOpenChange: (open: boolean) => void;
317
+ title: string;
318
+ payload: ToolDetailPayload;
319
+ }) {
269
320
  const [copied, setCopied] = useState(false);
270
321
  const copyResetRef = useRef<ReturnType<typeof setTimeout> | null>(null);
271
- const matchCount = useMemo(
272
- () => countTextMatches(payload.text, search),
273
- [payload.text, search],
274
- );
275
322
 
276
323
  useEffect(() => {
277
324
  return () => {
@@ -292,81 +339,95 @@ function ToolDetailViewer({ payload }: { payload: ToolDetailPayload }) {
292
339
  }, [payload.copyText]);
293
340
 
294
341
  return (
295
- <div className="rounded-md border border-border/50 bg-background/60">
296
- <div className="flex min-h-9 flex-wrap items-center gap-2 border-b border-border/50 px-2.5 py-1.5">
297
- <div className="min-w-0 flex-1">
298
- <div className="flex min-w-0 items-center gap-1.5">
299
- <span className="truncate text-[11px] font-medium text-foreground/85">
300
- {payload.title}
301
- </span>
302
- {payload.lang !== "text" && (
303
- <span className="shrink-0 rounded border border-border/60 px-1 py-0.5 font-mono text-[9px] uppercase leading-none text-muted-foreground">
304
- {payload.lang}
305
- </span>
306
- )}
307
- </div>
342
+ <Dialog open={open} onOpenChange={onOpenChange}>
343
+ <DialogContent className="flex h-[min(80vh,720px)] w-[min(92vw,760px)] max-w-[760px] flex-col gap-0 overflow-hidden p-0">
344
+ <div className="flex shrink-0 items-center justify-between gap-3 border-b border-border px-5 py-3 pr-12">
345
+ <DialogTitle className="truncate text-sm font-medium">
346
+ {title}
347
+ </DialogTitle>
348
+ <button
349
+ type="button"
350
+ onClick={copyValue}
351
+ className="inline-flex h-7 items-center gap-1.5 rounded-md px-2 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
352
+ >
353
+ {copied ? <IconCheck size={13} /> : <IconCopy size={13} />}
354
+ {copied ? "Copied" : "Copy"}
355
+ </button>
308
356
  </div>
309
- <button
310
- type="button"
311
- onClick={() => setSearchOpen((v) => !v)}
312
- aria-label={`Search ${payload.title.toLowerCase()}`}
313
- aria-pressed={searchOpen}
314
- className={cn(
315
- "inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground",
316
- searchOpen && "bg-accent text-foreground",
317
- )}
318
- >
319
- <IconSearch size={12} />
320
- </button>
321
- <button
322
- type="button"
323
- onClick={() => setExpanded((v) => !v)}
324
- aria-label={expanded ? "Shrink code viewer" : "Expand code viewer"}
325
- aria-pressed={expanded}
326
- className="inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground"
327
- >
328
- {expanded ? (
329
- <IconArrowsMinimize size={12} />
330
- ) : (
331
- <IconArrowsMaximize size={12} />
332
- )}
333
- </button>
334
- <button
335
- type="button"
336
- onClick={copyValue}
337
- className="inline-flex h-6 items-center gap-1 rounded-md px-1.5 font-sans text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground"
338
- >
339
- {copied ? <IconCheck size={12} /> : <IconCopy size={12} />}
340
- {copied ? "Copied" : "Copy"}
341
- </button>
342
- </div>
343
- {searchOpen && (
344
- <div className="flex items-center gap-2 border-b border-border/50 px-2.5 py-2">
345
- <input
346
- value={search}
347
- onChange={(e) => setSearch(e.target.value)}
348
- placeholder="Find"
349
- className="h-7 min-w-0 flex-1 rounded-md border border-border bg-background px-2 text-xs text-foreground outline-none placeholder:text-muted-foreground focus:ring-1 focus:ring-ring"
357
+ <div className="min-h-0 flex-1 overflow-auto p-4">
358
+ <SimpleCodeViewer
359
+ text={payload.text}
360
+ lang={payload.lang}
361
+ maxHeightClass="max-h-none"
350
362
  />
351
- <span className="shrink-0 text-[11px] text-muted-foreground">
352
- {search.trim() ? matchCount : ""}
353
- </span>
354
363
  </div>
355
- )}
356
- <div
357
- className={cn(
358
- "agent-tool-code overflow-auto font-mono text-[11px] leading-relaxed text-foreground",
359
- expanded ? "max-h-[70vh]" : "max-h-72",
360
- )}
361
- >
362
- {search.trim() ? (
363
- <pre>
364
- <code>{renderHighlightedSearchText(payload.text, search)}</code>
365
- </pre>
366
- ) : (
367
- <HighlightedCodeBlock code={payload.text} lang={payload.lang} />
368
- )}
369
- </div>
364
+ </DialogContent>
365
+ </Dialog>
366
+ );
367
+ }
368
+
369
+ // ─── Collapsible height animation ─────────────────────────────────────────────
370
+
371
+ function AnimatedCollapse({
372
+ open,
373
+ children,
374
+ }: {
375
+ open: boolean;
376
+ children: React.ReactNode;
377
+ }) {
378
+ const ref = useRef<HTMLDivElement>(null);
379
+ const [height, setHeight] = useState<number | "auto">(open ? "auto" : 0);
380
+ const [mounted, setMounted] = useState(open);
381
+ const reduceMotionRef = useRef(false);
382
+
383
+ useEffect(() => {
384
+ reduceMotionRef.current =
385
+ typeof window !== "undefined" &&
386
+ !!window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
387
+ }, []);
388
+
389
+ useLayoutEffect(() => {
390
+ if (open) setMounted(true);
391
+ }, [open]);
392
+
393
+ useLayoutEffect(() => {
394
+ const el = ref.current;
395
+ if (!el || !mounted) return;
396
+ if (reduceMotionRef.current) {
397
+ setHeight(open ? "auto" : 0);
398
+ if (!open) setMounted(false);
399
+ return;
400
+ }
401
+ if (open) {
402
+ const full = el.scrollHeight;
403
+ setHeight(0);
404
+ const frame = requestAnimationFrame(() => setHeight(full));
405
+ return () => cancelAnimationFrame(frame);
406
+ }
407
+ setHeight(el.scrollHeight);
408
+ const frame = requestAnimationFrame(() => setHeight(0));
409
+ return () => cancelAnimationFrame(frame);
410
+ }, [open, mounted]);
411
+
412
+ const onTransitionEnd = useCallback(
413
+ (event: React.TransitionEvent<HTMLDivElement>) => {
414
+ if (event.propertyName !== "height") return;
415
+ if (open) setHeight("auto");
416
+ else setMounted(false);
417
+ },
418
+ [open],
419
+ );
420
+
421
+ if (!mounted) return null;
422
+
423
+ return (
424
+ <div
425
+ ref={ref}
426
+ className="overflow-hidden transition-[height] duration-200 ease-out"
427
+ style={{ height: height === "auto" ? "auto" : `${height}px` }}
428
+ onTransitionEnd={onTransitionEnd}
429
+ >
430
+ {children}
370
431
  </div>
371
432
  );
372
433
  }
@@ -548,6 +609,7 @@ function ToolCallDisplayGeneric({
548
609
 
549
610
  const isAgentCall = toolName.startsWith("agent:");
550
611
  const [expanded, setExpanded] = useState(isAgentCall);
612
+ const [outputOpen, setOutputOpen] = useState(false);
551
613
  const agentName = isAgentCall ? toolName.slice(6) : null;
552
614
  const isAgentError = isAgentCall && result === "Error calling agent";
553
615
  const agentStreamText = isAgentCall ? (argsText ?? "") : "";
@@ -659,34 +721,47 @@ function ToolCallDisplayGeneric({
659
721
  ? hasStreamText
660
722
  : hasArgs || result !== undefined;
661
723
  const isExpanded = isAgentCall ? hasStreamText && expanded : expanded;
724
+ const ToolIcon = resolveToolIcon(toolName);
725
+ const outputTitle = `Raw ${toolName} tool call output`;
662
726
 
663
727
  return (
664
- <div className="my-1 w-full overflow-hidden">
728
+ <div className="group/tool my-0.5 w-full overflow-hidden">
665
729
  {mcpApp && <McpAppRenderer app={mcpApp} className="mb-1.5" />}
666
730
  <button
731
+ type="button"
667
732
  onClick={() => canExpand && setExpanded(!isExpanded)}
668
733
  aria-expanded={canExpand ? isExpanded : undefined}
669
734
  className={cn(
670
- "flex items-center gap-2 rounded-md px-2.5 py-1.5 text-xs font-mono w-full text-left overflow-hidden",
671
- isRunning
672
- ? "bg-muted text-muted-foreground"
673
- : "bg-muted text-muted-foreground hover:bg-accent",
735
+ "flex w-full items-center gap-1.5 rounded-md py-0.5 text-left text-[13px] text-muted-foreground transition-colors",
736
+ canExpand && "hover:text-foreground",
737
+ isRunning && "text-muted-foreground",
674
738
  )}
675
739
  >
676
- <span className="shrink-0">
740
+ <span className="relative flex size-4 shrink-0 items-center justify-center">
677
741
  {isRunning ? (
678
- <IconLoader2 className="h-3 w-3 animate-spin" />
742
+ <IconLoader2 className="size-3.5 animate-spin" />
679
743
  ) : isAgentError ? (
680
- <IconCircleX className="h-3 w-3 text-destructive" />
681
- ) : result !== undefined ? (
682
- <IconCheck className="h-3 w-3 text-emerald-500" />
744
+ <IconCircleX className="size-3.5 text-destructive" />
683
745
  ) : (
684
- <IconSquareFilled className="h-3 w-3 text-muted-foreground" />
746
+ <>
747
+ <ToolIcon
748
+ className={cn(
749
+ "size-3.5 transition-opacity",
750
+ canExpand && "group-hover/tool:opacity-0",
751
+ )}
752
+ />
753
+ {canExpand && (
754
+ <IconChevronRight
755
+ className={cn(
756
+ "absolute size-3.5 opacity-0 transition-all group-hover/tool:opacity-100",
757
+ isExpanded && "rotate-90",
758
+ )}
759
+ />
760
+ )}
761
+ </>
685
762
  )}
686
763
  </span>
687
- <span className="truncate min-w-0">
688
- <span className="font-medium">{displayName}</span>
689
- </span>
764
+ <span className="min-w-0 truncate font-normal">{displayName}</span>
690
765
  {repeatCount && repeatCount > 1 && (
691
766
  <span
692
767
  className="shrink-0 rounded border border-border/60 px-1.5 py-0.5 text-[10px] leading-none text-muted-foreground"
@@ -695,16 +770,8 @@ function ToolCallDisplayGeneric({
695
770
  {repeatCount}x
696
771
  </span>
697
772
  )}
698
- {canExpand && (
699
- <IconChevronDown
700
- className={cn(
701
- "ml-auto h-3 w-3 shrink-0 opacity-40",
702
- isExpanded && "rotate-180",
703
- )}
704
- />
705
- )}
706
773
  </button>
707
- {isExpanded && isAgentCall && hasStreamText && (
774
+ <AnimatedCollapse open={isExpanded && isAgentCall && hasStreamText}>
708
775
  <div
709
776
  ref={streamRef}
710
777
  className="mt-1 rounded-md bg-muted/50 px-3 py-2 text-xs text-muted-foreground break-words max-h-48 overflow-y-auto agent-markdown prose prose-sm prose-invert max-w-none"
@@ -721,12 +788,36 @@ function ToolCallDisplayGeneric({
721
788
  <span style={{ whiteSpace: "pre-wrap" }}>{agentStreamText}</span>
722
789
  )}
723
790
  </div>
724
- )}
725
- {isExpanded && !isAgentCall && (hasArgs || result !== undefined) && (
726
- <div className="mt-1 space-y-2 rounded-md bg-muted/50 px-3 py-2 text-xs text-muted-foreground">
727
- {inputPayload && <ToolDetailViewer payload={inputPayload} />}
728
- {resultPayload && <ToolDetailViewer payload={resultPayload} />}
791
+ </AnimatedCollapse>
792
+ <AnimatedCollapse
793
+ open={isExpanded && !isAgentCall && (hasArgs || result !== undefined)}
794
+ >
795
+ <div className="mt-1 space-y-2 pl-5">
796
+ {inputPayload && (
797
+ <SimpleCodeViewer
798
+ text={inputPayload.text}
799
+ lang={inputPayload.lang}
800
+ />
801
+ )}
802
+ {resultPayload && (
803
+ <button
804
+ type="button"
805
+ onClick={() => setOutputOpen(true)}
806
+ aria-label={`View ${toolName} output`}
807
+ className="inline-flex size-6 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-accent hover:text-foreground"
808
+ >
809
+ <IconCode className="size-3.5" />
810
+ </button>
811
+ )}
729
812
  </div>
813
+ </AnimatedCollapse>
814
+ {resultPayload && (
815
+ <ToolOutputModal
816
+ open={outputOpen}
817
+ onOpenChange={setOutputOpen}
818
+ title={outputTitle}
819
+ payload={resultPayload}
820
+ />
730
821
  )}
731
822
  {approval && (
732
823
  <ApprovalAffordance toolName={toolName} approval={approval} />
@@ -788,6 +879,8 @@ export function ReconnectStreamMessage({
788
879
  const chatRunning = React.useContext(ChatRunningContext);
789
880
  const streamingTextPartIndex =
790
881
  content.at(-1)?.type === "text" ? content.length - 1 : -1;
882
+ const streamingReasoningPartIndex =
883
+ content.at(-1)?.type === "reasoning" ? content.length - 1 : -1;
791
884
 
792
885
  return (
793
886
  <div className="flex justify-start">
@@ -805,6 +898,15 @@ export function ReconnectStreamMessage({
805
898
  />
806
899
  );
807
900
  }
901
+ if (part.type === "reasoning") {
902
+ return (
903
+ <ReasoningCell
904
+ key={`reconnect-reasoning-${i}`}
905
+ text={part.text}
906
+ isStreaming={chatRunning && i === streamingReasoningPartIndex}
907
+ />
908
+ );
909
+ }
808
910
  if (part.type === "tool-call") {
809
911
  return (
810
912
  <ToolCallDisplay
@@ -832,6 +934,115 @@ export function ReconnectStreamMessage({
832
934
  );
833
935
  }
834
936
 
937
+ // ─── Reasoning / Thinking cell ────────────────────────────────────────────────
938
+
939
+ export function ReasoningCell({
940
+ text,
941
+ isStreaming = false,
942
+ defaultOpen,
943
+ }: {
944
+ text: string;
945
+ isStreaming?: boolean;
946
+ defaultOpen?: boolean;
947
+ }) {
948
+ const [open, setOpen] = useState(defaultOpen ?? isStreaming);
949
+ const trimmed = text.trim();
950
+ if (!trimmed && !isStreaming) return null;
951
+
952
+ return (
953
+ <div className="my-0.5 w-full">
954
+ <button
955
+ type="button"
956
+ onClick={() => setOpen((v) => !v)}
957
+ aria-expanded={open}
958
+ className="flex items-center gap-1.5 py-0.5 text-[13px] text-muted-foreground transition-colors hover:text-foreground"
959
+ >
960
+ <IconChevronRight
961
+ className={cn(
962
+ "size-3.5 shrink-0 transition-transform",
963
+ open && "rotate-90",
964
+ )}
965
+ />
966
+ <span>{isStreaming ? "Thinking" : "Thought"}</span>
967
+ </button>
968
+ <AnimatedCollapse open={open}>
969
+ <div className="pl-5 pb-1 text-[13px] leading-relaxed text-muted-foreground whitespace-pre-wrap">
970
+ {trimmed || (isStreaming ? "…" : "")}
971
+ </div>
972
+ </AnimatedCollapse>
973
+ </div>
974
+ );
975
+ }
976
+
977
+ // ─── Worked-for duration helpers ──────────────────────────────────────────────
978
+
979
+ export function formatWorkedDuration(ms: number): string {
980
+ const totalSeconds = Math.max(0, Math.round(ms / 1000));
981
+ if (totalSeconds < 60) {
982
+ return totalSeconds <= 1 ? "1s" : `${totalSeconds}s`;
983
+ }
984
+ const minutes = Math.floor(totalSeconds / 60);
985
+ const seconds = totalSeconds % 60;
986
+ if (minutes < 60) {
987
+ if (seconds === 0) return `${minutes}m`;
988
+ return `${minutes}m ${seconds}s`;
989
+ }
990
+ const hours = Math.floor(minutes / 60);
991
+ const remMinutes = minutes % 60;
992
+ if (remMinutes === 0) return `${hours}h`;
993
+ return `${hours}h ${remMinutes}m`;
994
+ }
995
+
996
+ export function WorkedForSummary({
997
+ durationMs,
998
+ autoCollapse = false,
999
+ children,
1000
+ }: {
1001
+ durationMs?: number | null;
1002
+ /** When true, start open then animate closed (post-run collapse). */
1003
+ autoCollapse?: boolean;
1004
+ children: React.ReactNode;
1005
+ }) {
1006
+ const [open, setOpen] = useState(autoCollapse);
1007
+ const didAutoCollapseRef = useRef(false);
1008
+
1009
+ useEffect(() => {
1010
+ if (!autoCollapse || didAutoCollapseRef.current) return;
1011
+ didAutoCollapseRef.current = true;
1012
+ const frame = requestAnimationFrame(() => {
1013
+ setOpen(false);
1014
+ });
1015
+ return () => cancelAnimationFrame(frame);
1016
+ }, [autoCollapse]);
1017
+
1018
+ const label =
1019
+ durationMs != null && durationMs >= 1000
1020
+ ? `Worked for ${formatWorkedDuration(durationMs)}`
1021
+ : "Worked";
1022
+
1023
+ return (
1024
+ <div className="my-1 w-full">
1025
+ <button
1026
+ type="button"
1027
+ onClick={() => setOpen((v) => !v)}
1028
+ aria-expanded={open}
1029
+ className="flex items-center gap-1.5 py-0.5 text-[13px] text-muted-foreground transition-colors hover:text-foreground"
1030
+ >
1031
+ <span>{label}</span>
1032
+ <IconChevronRight
1033
+ className={cn(
1034
+ "size-3.5 shrink-0 transition-transform",
1035
+ open && "rotate-90",
1036
+ )}
1037
+ />
1038
+ </button>
1039
+ <AnimatedCollapse open={open}>
1040
+ <div className="pt-1">{children}</div>
1041
+ </AnimatedCollapse>
1042
+ </div>
1043
+ );
1044
+ }
1045
+
835
1046
  // ─── Re-export for AssistantMessage ───────────────────────────────────────────
836
1047
  // AssistantMessage in AssistantChat.tsx uses FilesChangedSummary directly, so
837
1048
  // re-export it so AssistantChat.tsx can import from one place.
@@ -357,16 +357,9 @@ function thinkingContentPartForCodeAgentTranscriptItem(
357
357
  ): ContentPart | null {
358
358
  const text = item.text.trim();
359
359
  if (!text) return null;
360
- // Surface as a special "thinking" tool-call-like part so the chat renderer
361
- // can display a collapsed-by-default "Thinking…" cell. Using a distinct
362
- // toolName makes it easy to style in AssistantChat without a new content type.
363
360
  return {
364
- type: "tool-call",
365
- toolCallId: `thinking-${item.id}`,
366
- toolName: "thinking",
367
- argsText: "",
368
- args: {},
369
- result: text,
361
+ type: "reasoning",
362
+ text,
370
363
  };
371
364
  }
372
365