@stigmer/react 0.0.52 → 0.0.54

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 (160) hide show
  1. package/deployment-mode.d.ts +35 -0
  2. package/deployment-mode.d.ts.map +1 -0
  3. package/deployment-mode.js +41 -0
  4. package/deployment-mode.js.map +1 -0
  5. package/execution/ApprovalCard.d.ts +8 -6
  6. package/execution/ApprovalCard.d.ts.map +1 -1
  7. package/execution/ApprovalCard.js +34 -96
  8. package/execution/ApprovalCard.js.map +1 -1
  9. package/execution/ArtifactCard.d.ts +11 -1
  10. package/execution/ArtifactCard.d.ts.map +1 -1
  11. package/execution/ArtifactCard.js +22 -3
  12. package/execution/ArtifactCard.js.map +1 -1
  13. package/execution/ArtifactPreviewModal.d.ts.map +1 -1
  14. package/execution/ArtifactPreviewModal.js +1 -1
  15. package/execution/ArtifactPreviewModal.js.map +1 -1
  16. package/execution/ArtifactsWidget.d.ts +26 -19
  17. package/execution/ArtifactsWidget.d.ts.map +1 -1
  18. package/execution/ArtifactsWidget.js +24 -26
  19. package/execution/ArtifactsWidget.js.map +1 -1
  20. package/execution/McpToolDetail.d.ts +48 -0
  21. package/execution/McpToolDetail.d.ts.map +1 -0
  22. package/execution/McpToolDetail.js +159 -0
  23. package/execution/McpToolDetail.js.map +1 -0
  24. package/execution/MessageThread.d.ts +10 -1
  25. package/execution/MessageThread.d.ts.map +1 -1
  26. package/execution/MessageThread.js +19 -17
  27. package/execution/MessageThread.js.map +1 -1
  28. package/execution/SandboxContext.d.ts +32 -0
  29. package/execution/SandboxContext.d.ts.map +1 -0
  30. package/execution/SandboxContext.js +26 -0
  31. package/execution/SandboxContext.js.map +1 -0
  32. package/execution/ToolArgsView.d.ts +41 -0
  33. package/execution/ToolArgsView.d.ts.map +1 -0
  34. package/execution/ToolArgsView.js +134 -0
  35. package/execution/ToolArgsView.js.map +1 -0
  36. package/execution/ToolCallDetail.d.ts +11 -4
  37. package/execution/ToolCallDetail.d.ts.map +1 -1
  38. package/execution/ToolCallDetail.js +32 -101
  39. package/execution/ToolCallDetail.js.map +1 -1
  40. package/execution/ToolCallGroup.d.ts.map +1 -1
  41. package/execution/ToolCallGroup.js +3 -2
  42. package/execution/ToolCallGroup.js.map +1 -1
  43. package/execution/ToolCallItem.d.ts +2 -0
  44. package/execution/ToolCallItem.d.ts.map +1 -1
  45. package/execution/ToolCallItem.js +13 -3
  46. package/execution/ToolCallItem.js.map +1 -1
  47. package/execution/WriteBackCard.d.ts +34 -0
  48. package/execution/WriteBackCard.d.ts.map +1 -0
  49. package/execution/WriteBackCard.js +75 -0
  50. package/execution/WriteBackCard.js.map +1 -0
  51. package/execution/WriteBacksWidget.d.ts +49 -0
  52. package/execution/WriteBacksWidget.d.ts.map +1 -0
  53. package/execution/WriteBacksWidget.js +44 -0
  54. package/execution/WriteBacksWidget.js.map +1 -0
  55. package/execution/__tests__/file-path-resolver.test.d.ts +2 -0
  56. package/execution/__tests__/file-path-resolver.test.d.ts.map +1 -0
  57. package/execution/__tests__/file-path-resolver.test.js +180 -0
  58. package/execution/__tests__/file-path-resolver.test.js.map +1 -0
  59. package/execution/file-path-resolver.d.ts +3 -3
  60. package/execution/file-path-resolver.d.ts.map +1 -1
  61. package/execution/file-path-resolver.js +23 -12
  62. package/execution/file-path-resolver.js.map +1 -1
  63. package/execution/index.d.ts +16 -1
  64. package/execution/index.d.ts.map +1 -1
  65. package/execution/index.js +9 -1
  66. package/execution/index.js.map +1 -1
  67. package/execution/sandbox-path-normalizer.d.ts +46 -0
  68. package/execution/sandbox-path-normalizer.d.ts.map +1 -0
  69. package/execution/sandbox-path-normalizer.js +73 -0
  70. package/execution/sandbox-path-normalizer.js.map +1 -0
  71. package/execution/tool-categories.d.ts +35 -8
  72. package/execution/tool-categories.d.ts.map +1 -1
  73. package/execution/tool-categories.js +76 -10
  74. package/execution/tool-categories.js.map +1 -1
  75. package/execution/tool-rendering-primitives.d.ts +61 -0
  76. package/execution/tool-rendering-primitives.d.ts.map +1 -0
  77. package/execution/tool-rendering-primitives.js +106 -0
  78. package/execution/tool-rendering-primitives.js.map +1 -0
  79. package/execution/useArtifactContent.d.ts +5 -1
  80. package/execution/useArtifactContent.d.ts.map +1 -1
  81. package/execution/useArtifactContent.js +6 -2
  82. package/execution/useArtifactContent.js.map +1 -1
  83. package/execution/useWorkspaceWriteBacks.d.ts +40 -0
  84. package/execution/useWorkspaceWriteBacks.d.ts.map +1 -0
  85. package/execution/useWorkspaceWriteBacks.js +41 -0
  86. package/execution/useWorkspaceWriteBacks.js.map +1 -0
  87. package/github/GitHubRepoPicker.d.ts +5 -2
  88. package/github/GitHubRepoPicker.d.ts.map +1 -1
  89. package/github/GitHubRepoPicker.js +133 -36
  90. package/github/GitHubRepoPicker.js.map +1 -1
  91. package/github/index.d.ts +1 -0
  92. package/github/index.d.ts.map +1 -1
  93. package/github/index.js +1 -0
  94. package/github/index.js.map +1 -1
  95. package/github/useGitHubSearch.d.ts +20 -0
  96. package/github/useGitHubSearch.d.ts.map +1 -0
  97. package/github/useGitHubSearch.js +127 -0
  98. package/github/useGitHubSearch.js.map +1 -0
  99. package/index.d.ts +9 -6
  100. package/index.d.ts.map +1 -1
  101. package/index.js +7 -3
  102. package/index.js.map +1 -1
  103. package/internal/CloudFeatureNotice.d.ts +19 -0
  104. package/internal/CloudFeatureNotice.d.ts.map +1 -0
  105. package/internal/CloudFeatureNotice.js +21 -0
  106. package/internal/CloudFeatureNotice.js.map +1 -0
  107. package/mcp-server/McpServerDetailView.d.ts +15 -1
  108. package/mcp-server/McpServerDetailView.d.ts.map +1 -1
  109. package/mcp-server/McpServerDetailView.js +11 -3
  110. package/mcp-server/McpServerDetailView.js.map +1 -1
  111. package/package.json +4 -4
  112. package/provider.d.ts +14 -2
  113. package/provider.d.ts.map +1 -1
  114. package/provider.js +3 -2
  115. package/provider.js.map +1 -1
  116. package/session/index.d.ts +4 -0
  117. package/session/index.d.ts.map +1 -1
  118. package/session/index.js +2 -0
  119. package/session/index.js.map +1 -1
  120. package/session/useSessionArtifacts.d.ts +73 -0
  121. package/session/useSessionArtifacts.d.ts.map +1 -0
  122. package/session/useSessionArtifacts.js +95 -0
  123. package/session/useSessionArtifacts.js.map +1 -0
  124. package/session/useSessionWriteBacks.d.ts +56 -0
  125. package/session/useSessionWriteBacks.d.ts.map +1 -0
  126. package/session/useSessionWriteBacks.js +56 -0
  127. package/session/useSessionWriteBacks.js.map +1 -0
  128. package/src/deployment-mode.ts +46 -0
  129. package/src/execution/ApprovalCard.tsx +130 -283
  130. package/src/execution/ArtifactCard.tsx +40 -0
  131. package/src/execution/ArtifactPreviewModal.tsx +2 -0
  132. package/src/execution/ArtifactsWidget.tsx +51 -43
  133. package/src/execution/McpToolDetail.tsx +283 -0
  134. package/src/execution/MessageThread.tsx +18 -0
  135. package/src/execution/SandboxContext.ts +47 -0
  136. package/src/execution/ToolArgsView.tsx +279 -0
  137. package/src/execution/ToolCallDetail.tsx +54 -220
  138. package/src/execution/ToolCallGroup.tsx +3 -2
  139. package/src/execution/ToolCallItem.tsx +21 -3
  140. package/src/execution/WriteBackCard.tsx +210 -0
  141. package/src/execution/WriteBacksWidget.tsx +82 -0
  142. package/src/execution/__tests__/file-path-resolver.test.ts +295 -0
  143. package/src/execution/file-path-resolver.ts +24 -12
  144. package/src/execution/index.ts +38 -0
  145. package/src/execution/sandbox-path-normalizer.ts +80 -0
  146. package/src/execution/tool-categories.ts +89 -9
  147. package/src/execution/tool-rendering-primitives.tsx +253 -0
  148. package/src/execution/useArtifactContent.ts +6 -1
  149. package/src/execution/useWorkspaceWriteBacks.ts +56 -0
  150. package/src/github/GitHubRepoPicker.tsx +413 -108
  151. package/src/github/index.ts +5 -0
  152. package/src/github/useGitHubSearch.ts +162 -0
  153. package/src/index.ts +27 -0
  154. package/src/internal/CloudFeatureNotice.tsx +60 -0
  155. package/src/mcp-server/McpServerDetailView.tsx +24 -2
  156. package/src/provider.tsx +18 -2
  157. package/src/session/index.ts +12 -0
  158. package/src/session/useSessionArtifacts.ts +143 -0
  159. package/src/session/useSessionWriteBacks.ts +94 -0
  160. package/styles.css +1 -1
@@ -0,0 +1,279 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import { cn } from "@stigmer/theme";
5
+ import {
6
+ resolveToolCategory,
7
+ extractWriteContentFromPreview,
8
+ } from "./tool-categories";
9
+ import type { ToolCategory, ToolCategoryInfo } from "./tool-categories";
10
+ import { FilePathLink } from "./FilePathLink";
11
+ import { McpArgsView, McpMetadataRow } from "./McpToolDetail";
12
+ import { useSandboxNormalize } from "./SandboxContext";
13
+ import {
14
+ CollapsibleCode,
15
+ FilePathIcon,
16
+ formatJson,
17
+ } from "./tool-rendering-primitives";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Public API
21
+ // ---------------------------------------------------------------------------
22
+
23
+ export interface ToolArgsViewProps {
24
+ /**
25
+ * Raw tool name as it appears on the ToolCall or PendingApproval.
26
+ * Used for category resolution and MCP metadata display.
27
+ */
28
+ readonly toolName: string;
29
+ /**
30
+ * Parsed tool arguments — either from `ToolCall.args` or
31
+ * `JSON.parse(PendingApproval.argsPreview)`.
32
+ */
33
+ readonly args: Record<string, unknown> | null;
34
+ /** MCP server slug for MCP tool classification and metadata. */
35
+ readonly mcpServerSlug?: string;
36
+ readonly className?: string;
37
+ }
38
+
39
+ /**
40
+ * Unified tool-arguments renderer used by both {@link ApprovalCard}
41
+ * (pre-execution) and {@link ToolCallDetail} (post-execution).
42
+ *
43
+ * Resolves the tool category from `toolName` + `mcpServerSlug`,
44
+ * extracts the relevant primary argument, and dispatches to the
45
+ * appropriate category-specific view:
46
+ *
47
+ * - **Shell** — terminal-style command block
48
+ * - **File (read/write/edit/delete)** — file icon + path + optional content
49
+ * - **Search / List** — pattern display
50
+ * - **MCP** — metadata row + scalar key-value grid + collapsible JSON
51
+ * - **Generic / Unknown** — formatted JSON args
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * // In approval card (from argsPreview string):
56
+ * const args = JSON.parse(pendingApproval.argsPreview);
57
+ * <ToolArgsView toolName={toolName} args={args} mcpServerSlug={slug} />
58
+ *
59
+ * // In detail view (from ToolCall):
60
+ * <ToolArgsView toolName={tc.name} args={tc.args} mcpServerSlug={tc.mcpServerSlug} />
61
+ * ```
62
+ */
63
+ export function ToolArgsView({
64
+ toolName,
65
+ args,
66
+ mcpServerSlug,
67
+ className,
68
+ }: ToolArgsViewProps) {
69
+ const categoryInfo = useMemo(
70
+ () => resolveToolCategory(toolName, mcpServerSlug),
71
+ [toolName, mcpServerSlug],
72
+ );
73
+
74
+ const primaryArg = useMemo(
75
+ () => extractPrimaryArgValue(args, categoryInfo),
76
+ [args, categoryInfo],
77
+ );
78
+
79
+ return (
80
+ <div className={cn("text-xs", className)}>
81
+ <CategoryArgsDispatch
82
+ category={categoryInfo.category}
83
+ toolName={toolName}
84
+ args={args}
85
+ primaryArg={primaryArg}
86
+ mcpServerSlug={mcpServerSlug}
87
+ />
88
+ </div>
89
+ );
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Dispatch
94
+ // ---------------------------------------------------------------------------
95
+
96
+ function CategoryArgsDispatch({
97
+ category,
98
+ toolName,
99
+ args,
100
+ primaryArg,
101
+ mcpServerSlug,
102
+ }: {
103
+ category: ToolCategory;
104
+ toolName: string;
105
+ args: Record<string, unknown> | null;
106
+ primaryArg: string | null;
107
+ mcpServerSlug?: string;
108
+ }) {
109
+ switch (category) {
110
+ case "shell":
111
+ return primaryArg ? <ShellArgsView command={primaryArg} /> : null;
112
+
113
+ case "read":
114
+ case "write":
115
+ case "edit":
116
+ case "delete":
117
+ return primaryArg ? (
118
+ <FileArgsView path={primaryArg} category={category} args={args} />
119
+ ) : null;
120
+
121
+ case "search":
122
+ case "list":
123
+ return primaryArg ? <SearchArgsView pattern={primaryArg} /> : null;
124
+
125
+ case "mcp":
126
+ return (
127
+ <McpArgsPreview
128
+ toolName={toolName}
129
+ args={args}
130
+ mcpServerSlug={mcpServerSlug ?? ""}
131
+ />
132
+ );
133
+
134
+ default:
135
+ return args && Object.keys(args).length > 0 ? (
136
+ <GenericArgsView args={args} />
137
+ ) : null;
138
+ }
139
+ }
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // Category-specific views
143
+ // ---------------------------------------------------------------------------
144
+
145
+ function ShellArgsView({ command }: { command: string }) {
146
+ const normalize = useSandboxNormalize();
147
+ return (
148
+ <div className="rounded-md border border-border bg-[var(--stgm-terminal-bg,#1a1a2e)] p-2.5">
149
+ <pre className="whitespace-pre-wrap break-words font-mono text-xs text-[var(--stgm-terminal-fg,#e0e0e0)]">
150
+ <span className="select-none text-[var(--stgm-terminal-prompt,#6b7280)]">
151
+ ${" "}
152
+ </span>
153
+ {normalize(command)}
154
+ </pre>
155
+ </div>
156
+ );
157
+ }
158
+
159
+ function FileArgsView({
160
+ path,
161
+ category,
162
+ args,
163
+ }: {
164
+ path: string;
165
+ category: string;
166
+ args: Record<string, unknown> | null;
167
+ }) {
168
+ const writeContent = useMemo(() => {
169
+ if (category !== "write" && category !== "edit") return null;
170
+ if (!args) return null;
171
+ return extractWriteContentFromArgs(args);
172
+ }, [category, args]);
173
+
174
+ return (
175
+ <div className="space-y-1.5">
176
+ <div className="flex items-center gap-1.5 text-xs">
177
+ <FilePathIcon />
178
+ <FilePathLink path={path} className="text-xs" />
179
+ </div>
180
+ {writeContent && (
181
+ <CollapsibleCode label="Content" content={writeContent} />
182
+ )}
183
+ </div>
184
+ );
185
+ }
186
+
187
+ function SearchArgsView({ pattern }: { pattern: string }) {
188
+ return (
189
+ <div className="flex items-center gap-1.5 text-xs">
190
+ <span className="text-muted-foreground">Pattern:</span>
191
+ <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-foreground">
192
+ {pattern}
193
+ </code>
194
+ </div>
195
+ );
196
+ }
197
+
198
+ function McpArgsPreview({
199
+ toolName,
200
+ args,
201
+ mcpServerSlug,
202
+ }: {
203
+ toolName: string;
204
+ args: Record<string, unknown> | null;
205
+ mcpServerSlug: string;
206
+ }) {
207
+ return (
208
+ <div className="space-y-2">
209
+ <McpMetadataRow
210
+ mcpServerSlug={mcpServerSlug}
211
+ toolName={toolName}
212
+ duration={null}
213
+ />
214
+ {args && Object.keys(args).length > 0 && <McpArgsView args={args} />}
215
+ </div>
216
+ );
217
+ }
218
+
219
+ function GenericArgsView({ args }: { args: Record<string, unknown> }) {
220
+ return (
221
+ <CollapsibleCode label="Arguments" content={formatJson(args)} />
222
+ );
223
+ }
224
+
225
+ // ---------------------------------------------------------------------------
226
+ // Utilities
227
+ // ---------------------------------------------------------------------------
228
+
229
+ const WRITE_CONTENT_FIELDS = [
230
+ "contents",
231
+ "content",
232
+ "file_content",
233
+ "new_text",
234
+ "new_string",
235
+ "replacement",
236
+ ] as const;
237
+
238
+ function extractWriteContentFromArgs(
239
+ args: Record<string, unknown>,
240
+ ): string | null {
241
+ for (const field of WRITE_CONTENT_FIELDS) {
242
+ const val = args[field];
243
+ if (typeof val === "string" && val.length > 0) return val;
244
+ }
245
+ return null;
246
+ }
247
+
248
+ function extractPrimaryArgValue(
249
+ args: Record<string, unknown> | null,
250
+ info: ToolCategoryInfo,
251
+ ): string | null {
252
+ if (!args) return null;
253
+
254
+ const tryField = (field: string): string | null => {
255
+ const val = args[field];
256
+ if (typeof val === "string" && val.length > 0) return val;
257
+ return null;
258
+ };
259
+
260
+ if (info.primaryArgField) {
261
+ const v = tryField(info.primaryArgField);
262
+ if (v) return v;
263
+ }
264
+
265
+ for (const fb of info.fallbackArgFields) {
266
+ const v = tryField(fb);
267
+ if (v) return v;
268
+ }
269
+
270
+ if (info.category === "unknown" || info.category === "mcp") {
271
+ const keys = Object.keys(args);
272
+ if (keys.length > 0) {
273
+ const val = args[keys[0]];
274
+ if (typeof val === "string") return val;
275
+ }
276
+ }
277
+
278
+ return null;
279
+ }
@@ -1,28 +1,39 @@
1
1
  "use client";
2
2
 
3
- import { useState } from "react";
4
3
  import type { ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
5
4
  import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
6
5
  import { cn } from "@stigmer/theme";
7
- import { resolveToolCategory, extractPrimaryArg } from "./tool-categories";
8
- import { FilePathLink } from "./FilePathLink";
6
+ import { resolveToolCategory } from "./tool-categories";
7
+ import { McpToolDetail } from "./McpToolDetail";
8
+ import { useSandboxNormalize } from "./SandboxContext";
9
+ import { ToolArgsView } from "./ToolArgsView";
10
+ import {
11
+ CollapsibleCode,
12
+ CollapsiblePre,
13
+ formatResult,
14
+ } from "./tool-rendering-primitives";
9
15
 
10
16
  export interface ToolCallDetailProps {
11
17
  readonly toolCall: ToolCall;
12
18
  readonly className?: string;
13
19
  }
14
20
 
15
- const TRUNCATION_LINE_LIMIT = 10;
16
-
17
21
  /**
18
22
  * Renders the detail panel for a single tool call with
19
23
  * category-specific visual treatments.
20
24
  *
21
- * - **Shell tools**: terminal-style code block for command + output
22
- * - **File tools (read/write/edit)**: file path header + content block
23
- * - **Search tools (grep/glob)**: pattern header + results list
25
+ * Arguments are rendered through the shared {@link ToolArgsView}
26
+ * dispatch (same component used by {@link ApprovalCard}), ensuring
27
+ * visual parity between pre-execution approval previews and
28
+ * post-execution detail views. Result/output sections are layered
29
+ * on top by this component.
30
+ *
31
+ * - **Shell tools**: terminal-style command + output
32
+ * - **File tools (read/write/edit/delete)**: file path + content + result
33
+ * - **Search tools (grep/glob)**: pattern + results
24
34
  * - **Think**: muted italic thought block
25
- * - **Unknown/MCP tools**: generic args + result JSON rendering
35
+ * - **MCP tools**: structured args + parsed result via {@link McpToolDetail}
36
+ * - **Unknown tools**: generic args + result JSON rendering
26
37
  *
27
38
  * Used inside {@link ToolCallItem} when expanded, but also
28
39
  * independently importable by platform builders who compose
@@ -34,7 +45,7 @@ const TRUNCATION_LINE_LIMIT = 10;
34
45
  * ```
35
46
  */
36
47
  export function ToolCallDetail({ toolCall, className }: ToolCallDetailProps) {
37
- const category = resolveToolCategory(toolCall.name);
48
+ const category = resolveToolCategory(toolCall.name, toolCall.mcpServerSlug);
38
49
  const isFailed = toolCall.status === ToolCallStatus.TOOL_CALL_FAILED;
39
50
 
40
51
  return (
@@ -55,6 +66,12 @@ export function ToolCallDetail({ toolCall, className }: ToolCallDetailProps) {
55
66
 
56
67
  // ---------------------------------------------------------------------------
57
68
  // Category-specific renderers
69
+ //
70
+ // Each renderer composes:
71
+ // MetadataRow (duration, slug) + ToolArgsView (shared args) + result section
72
+ //
73
+ // Think and MCP have fully custom rendering that doesn't fit the
74
+ // MetadataRow + ToolArgsView + Result pattern.
58
75
  // ---------------------------------------------------------------------------
59
76
 
60
77
  function CategoryRenderer({
@@ -80,44 +97,33 @@ function CategoryRenderer({
80
97
  return <SearchToolDetail toolCall={toolCall} />;
81
98
  case "think":
82
99
  return <ThinkToolDetail toolCall={toolCall} />;
100
+ case "mcp":
101
+ return <McpToolDetail toolCall={toolCall} />;
83
102
  default:
84
103
  return <GenericToolDetail toolCall={toolCall} />;
85
104
  }
86
105
  }
87
106
 
88
- /**
89
- * Terminal-style rendering for shell/execute tools.
90
- * Shows the command in a dark terminal block and output below.
91
- */
92
107
  function ShellToolDetail({ toolCall }: { toolCall: ToolCall }) {
93
- const command = extractPrimaryArg(toolCall);
94
108
  const duration = formatDuration(toolCall.startedAt, toolCall.completedAt);
109
+ const normalize = useSandboxNormalize();
95
110
 
96
111
  return (
97
112
  <>
98
- {/* Metadata */}
99
113
  <MetadataRow toolCall={toolCall} duration={duration} />
100
114
 
101
- {/* Command in terminal-style block */}
102
- {command && (
103
- <div className="space-y-1">
104
- <span className="font-medium text-muted-foreground">Command</span>
105
- <div className="rounded-md border border-border bg-[var(--stgm-terminal-bg,#1a1a2e)] p-2.5">
106
- <pre className="whitespace-pre-wrap break-words font-mono text-[var(--stgm-terminal-fg,#e0e0e0)]">
107
- <span className="select-none text-[var(--stgm-terminal-prompt,#6b7280)]">$ </span>
108
- {command}
109
- </pre>
110
- </div>
111
- </div>
112
- )}
115
+ <ToolArgsView
116
+ toolName={toolCall.name}
117
+ args={toolCall.args as Record<string, unknown> | null}
118
+ mcpServerSlug={toolCall.mcpServerSlug}
119
+ />
113
120
 
114
- {/* Output */}
115
121
  {toolCall.result && (
116
122
  <div className="space-y-1">
117
123
  <span className="font-medium text-muted-foreground">Output</span>
118
124
  <div className="rounded-md border border-border bg-[var(--stgm-terminal-bg,#1a1a2e)] p-2.5">
119
125
  <CollapsiblePre
120
- content={toolCall.result}
126
+ content={normalize(toolCall.result)}
121
127
  className="text-[var(--stgm-terminal-fg,#e0e0e0)]"
122
128
  />
123
129
  </div>
@@ -128,17 +134,8 @@ function ShellToolDetail({ toolCall }: { toolCall: ToolCall }) {
128
134
  }
129
135
 
130
136
  /**
131
- * File-oriented rendering for read/write/edit/delete tools.
132
- *
133
- * For **read** mode: shows only the metadata row and a clickable
134
- * path. Content is intentionally omitted — the Read tool's purpose
135
- * is for the *agent* to consume the file, and the content is either
136
- * truncated, omitted, or simply noise for the user. The clickable
137
- * path provides direct access to the source file.
138
- *
139
- * For **write/edit/delete** modes: shows the clickable path followed
140
- * by the content block (what was written/edited) and any result
141
- * confirmation.
137
+ * For **read**: metadata + path only (content is noise for the user).
138
+ * For **write/edit/delete**: metadata + path + content (via ToolArgsView) + result.
142
139
  */
143
140
  function FileToolDetail({
144
141
  toolCall,
@@ -147,49 +144,19 @@ function FileToolDetail({
147
144
  toolCall: ToolCall;
148
145
  mode: "read" | "write" | "edit" | "delete";
149
146
  }) {
150
- const filePath = extractPrimaryArg(toolCall);
151
147
  const duration = formatDuration(toolCall.startedAt, toolCall.completedAt);
152
148
 
153
- if (mode === "read") {
154
- return (
155
- <>
156
- <MetadataRow toolCall={toolCall} duration={duration} />
157
- {filePath && (
158
- <div className="flex items-center gap-1.5">
159
- <FilePathIcon />
160
- <FilePathLink path={filePath} className="text-xs" />
161
- </div>
162
- )}
163
- </>
164
- );
165
- }
166
-
167
- const contentFromArgs =
168
- mode === "write" || mode === "edit"
169
- ? extractWriteContent(toolCall)
170
- : null;
171
-
172
- const displayContent = contentFromArgs || toolCall.result;
173
-
174
149
  return (
175
150
  <>
176
151
  <MetadataRow toolCall={toolCall} duration={duration} />
177
152
 
178
- {filePath && (
179
- <div className="flex items-center gap-1.5">
180
- <FilePathIcon />
181
- <FilePathLink path={filePath} className="text-xs" />
182
- </div>
183
- )}
184
-
185
- {displayContent && (
186
- <CollapsibleCode
187
- label={mode === "delete" ? "Result" : "Content"}
188
- content={formatResult(displayContent)}
189
- />
190
- )}
153
+ <ToolArgsView
154
+ toolName={toolCall.name}
155
+ args={toolCall.args as Record<string, unknown> | null}
156
+ mcpServerSlug={toolCall.mcpServerSlug}
157
+ />
191
158
 
192
- {contentFromArgs && toolCall.result && (
159
+ {mode !== "read" && toolCall.result && (
193
160
  <CollapsibleCode
194
161
  label="Result"
195
162
  content={formatResult(toolCall.result)}
@@ -199,24 +166,18 @@ function FileToolDetail({
199
166
  );
200
167
  }
201
168
 
202
- /**
203
- * Search/discovery rendering for grep, glob, list tools.
204
- * Shows search pattern/path and results.
205
- */
206
169
  function SearchToolDetail({ toolCall }: { toolCall: ToolCall }) {
207
- const pattern = extractPrimaryArg(toolCall);
208
170
  const duration = formatDuration(toolCall.startedAt, toolCall.completedAt);
209
171
 
210
172
  return (
211
173
  <>
212
174
  <MetadataRow toolCall={toolCall} duration={duration} />
213
175
 
214
- {pattern && (
215
- <div className="flex items-center gap-1.5">
216
- <span className="font-medium text-muted-foreground">Pattern:</span>
217
- <span className="font-mono text-foreground">{pattern}</span>
218
- </div>
219
- )}
176
+ <ToolArgsView
177
+ toolName={toolCall.name}
178
+ args={toolCall.args as Record<string, unknown> | null}
179
+ mcpServerSlug={toolCall.mcpServerSlug}
180
+ />
220
181
 
221
182
  {toolCall.result && (
222
183
  <CollapsibleCode
@@ -228,10 +189,6 @@ function SearchToolDetail({ toolCall }: { toolCall: ToolCall }) {
228
189
  );
229
190
  }
230
191
 
231
- /**
232
- * Thought rendering. Muted, italic presentation distinct from
233
- * regular tool output.
234
- */
235
192
  function ThinkToolDetail({ toolCall }: { toolCall: ToolCall }) {
236
193
  const thought =
237
194
  (toolCall.args?.["thought"] as string | undefined) || toolCall.result;
@@ -248,10 +205,6 @@ function ThinkToolDetail({ toolCall }: { toolCall: ToolCall }) {
248
205
  );
249
206
  }
250
207
 
251
- /**
252
- * Fallback rendering for unknown/MCP tools. Preserves the original
253
- * generic args + result JSON display.
254
- */
255
208
  function GenericToolDetail({ toolCall }: { toolCall: ToolCall }) {
256
209
  const duration = formatDuration(toolCall.startedAt, toolCall.completedAt);
257
210
 
@@ -259,12 +212,11 @@ function GenericToolDetail({ toolCall }: { toolCall: ToolCall }) {
259
212
  <>
260
213
  <MetadataRow toolCall={toolCall} duration={duration} />
261
214
 
262
- {toolCall.args && Object.keys(toolCall.args).length > 0 && (
263
- <CollapsibleCode
264
- label="Arguments"
265
- content={formatJson(toolCall.args)}
266
- />
267
- )}
215
+ <ToolArgsView
216
+ toolName={toolCall.name}
217
+ args={toolCall.args as Record<string, unknown> | null}
218
+ mcpServerSlug={toolCall.mcpServerSlug}
219
+ />
268
220
 
269
221
  {toolCall.result && (
270
222
  <CollapsibleCode
@@ -302,128 +254,10 @@ function MetadataRow({
302
254
  );
303
255
  }
304
256
 
305
- function CollapsibleCode({
306
- label,
307
- content,
308
- }: {
309
- label: string;
310
- content: string;
311
- }) {
312
- const lines = content.split("\n");
313
- const needsTruncation = lines.length > TRUNCATION_LINE_LIMIT;
314
- const [isExpanded, setIsExpanded] = useState(false);
315
-
316
- const displayContent =
317
- needsTruncation && !isExpanded
318
- ? lines.slice(0, TRUNCATION_LINE_LIMIT).join("\n") + "\n\u2026"
319
- : content;
320
-
321
- return (
322
- <div className="space-y-1">
323
- <span className="font-medium text-muted-foreground">{label}</span>
324
- <pre className="max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-md border border-border bg-muted/40 p-2 font-mono text-foreground">
325
- {displayContent}
326
- </pre>
327
- {needsTruncation && (
328
- <button
329
- type="button"
330
- onClick={() => setIsExpanded((v) => !v)}
331
- className="text-primary hover:text-primary/80 text-xs font-medium transition-colors"
332
- >
333
- {isExpanded
334
- ? "Show less"
335
- : `Show all ${lines.length} lines`}
336
- </button>
337
- )}
338
- </div>
339
- );
340
- }
341
-
342
- function CollapsiblePre({
343
- content,
344
- className,
345
- }: {
346
- content: string;
347
- className?: string;
348
- }) {
349
- const lines = content.split("\n");
350
- const needsTruncation = lines.length > TRUNCATION_LINE_LIMIT;
351
- const [isExpanded, setIsExpanded] = useState(false);
352
-
353
- const displayContent =
354
- needsTruncation && !isExpanded
355
- ? lines.slice(0, TRUNCATION_LINE_LIMIT).join("\n") + "\n\u2026"
356
- : content;
357
-
358
- return (
359
- <>
360
- <pre className={cn("whitespace-pre-wrap break-words font-mono", className)}>
361
- {displayContent}
362
- </pre>
363
- {needsTruncation && (
364
- <button
365
- type="button"
366
- onClick={() => setIsExpanded((v) => !v)}
367
- className="mt-1 text-primary hover:text-primary/80 text-xs font-medium transition-colors"
368
- >
369
- {isExpanded ? "Show less" : `Show all ${lines.length} lines`}
370
- </button>
371
- )}
372
- </>
373
- );
374
- }
375
-
376
- function FilePathIcon() {
377
- return (
378
- <svg
379
- width="10"
380
- height="10"
381
- viewBox="0 0 12 12"
382
- fill="none"
383
- stroke="currentColor"
384
- strokeWidth="1.2"
385
- strokeLinecap="round"
386
- strokeLinejoin="round"
387
- className="shrink-0 text-muted-foreground"
388
- aria-hidden="true"
389
- >
390
- <path d="M7 1H3C2.45 1 2 1.45 2 2V10C2 10.55 2.45 11 3 11H9C9.55 11 10 10.55 10 10V4L7 1Z" />
391
- <path d="M7 1V4H10" />
392
- </svg>
393
- );
394
- }
395
-
396
257
  // ---------------------------------------------------------------------------
397
258
  // Utilities
398
259
  // ---------------------------------------------------------------------------
399
260
 
400
- function extractWriteContent(toolCall: ToolCall): string | null {
401
- if (!toolCall.args) return null;
402
- const fields = ["contents", "content", "file_content", "new_text", "new_string", "replacement"];
403
- for (const field of fields) {
404
- const val = toolCall.args[field];
405
- if (typeof val === "string" && val.length > 0) return val;
406
- }
407
- return null;
408
- }
409
-
410
- function formatJson(obj: object): string {
411
- try {
412
- return JSON.stringify(obj, null, 2);
413
- } catch {
414
- return String(obj);
415
- }
416
- }
417
-
418
- function formatResult(result: string): string {
419
- try {
420
- const parsed = JSON.parse(result);
421
- return JSON.stringify(parsed, null, 2);
422
- } catch {
423
- return result;
424
- }
425
- }
426
-
427
261
  /**
428
262
  * Returns a human-readable duration string from two ISO 8601
429
263
  * timestamps. Returns `null` when either timestamp is empty or
@@ -86,8 +86,9 @@ function defaultFormatSummary(
86
86
  status: AggregateStatus,
87
87
  ): string {
88
88
  if (toolCalls.length === 1) {
89
- const cat = resolveToolCategory(toolCalls[0].name);
90
- const primary = extractPrimaryArg(toolCalls[0]);
89
+ const tc = toolCalls[0];
90
+ const cat = resolveToolCategory(tc.name, tc.mcpServerSlug);
91
+ const primary = extractPrimaryArg(tc);
91
92
  if (primary) {
92
93
  const truncated =
93
94
  primary.length > 60 ? primary.slice(0, 57) + "\u2026" : primary;