@volter-ai-dev/supercode-ui 0.1.8 → 0.1.10

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.
package/conversation.mjs CHANGED
@@ -59,6 +59,178 @@ var EMPTY_UI_STATE = Object.freeze({
59
59
  owned: null,
60
60
  attachError: null
61
61
  });
62
+ var TOOL_CATEGORIES = /* @__PURE__ */ new Set(["read", "search", "edit", "command", "test", "web", "agent", "plan", "other"]);
63
+ var MAX_TOOL_FIELDS = 8;
64
+ var MAX_TOOL_FIELD_CHARS = 800;
65
+ var MAX_TOOL_PREVIEW_CHARS = 4e3;
66
+ function record(value) {
67
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
68
+ }
69
+ function boundedString(value, max) {
70
+ if (typeof value !== "string") return "";
71
+ return value.length <= max ? value : `${value.slice(0, max)}\u2026`;
72
+ }
73
+ function argumentValue(argumentsText) {
74
+ if (!argumentsText) return null;
75
+ try {
76
+ return JSON.parse(argumentsText);
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+ function firstString(source, keys) {
82
+ for (const key of keys) {
83
+ if (typeof source?.[key] === "string" && source[key].trim()) return source[key].trim();
84
+ }
85
+ return "";
86
+ }
87
+ function decodedLiteral(value) {
88
+ if (!value) return "";
89
+ if (value.startsWith('"')) {
90
+ try {
91
+ return JSON.parse(value);
92
+ } catch {
93
+ return "";
94
+ }
95
+ }
96
+ return value.slice(1, -1).replaceAll("\\n", "\n").replaceAll("\\t", " ").replaceAll("\\r", "\r").replaceAll("\\`", "`").replaceAll("\\'", "'").replaceAll("\\\\", "\\");
97
+ }
98
+ function sourceString(source, keys) {
99
+ if (!source) return "";
100
+ const names = keys.join("|");
101
+ const match = new RegExp(`\\b(?:${names})\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
102
+ return decodedLiteral(match?.[1]);
103
+ }
104
+ function toolEnvelope(entry) {
105
+ const value = argumentValue(entry.arguments);
106
+ const source = typeof value === "string" ? value : "";
107
+ const tools = source ? [...new Set([...source.matchAll(/\btools\.([A-Za-z0-9_]+)/g)].map((match) => match[1]))].slice(0, 8) : [];
108
+ const name = tools.length === 1 ? tools[0] : entry.label ?? "tool";
109
+ return { args: record(value), source, tools, name };
110
+ }
111
+ function patchPath(source) {
112
+ return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]+)/.exec(source)?.[1]?.trim() ?? "";
113
+ }
114
+ function explicitNumber(sources, keys) {
115
+ for (const source of sources) {
116
+ for (const key of keys) {
117
+ const value = source?.[key];
118
+ const parsed = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
119
+ if (typeof parsed === "number" && Number.isFinite(parsed)) return parsed;
120
+ }
121
+ }
122
+ return null;
123
+ }
124
+ function toolDetail(category) {
125
+ return { read: "file", search: "matches", edit: "diff", command: "terminal", test: "terminal", web: "web", agent: "agent", plan: "plan" }[category] ?? "fields";
126
+ }
127
+ function usefulToolFields(args) {
128
+ if (!args) return [];
129
+ const hidden = /* @__PURE__ */ new Set(["command", "cmd", "file_path", "target_file", "path", "query", "pattern", "url", "patch", "diff", "old_string", "new_string", "content", "prompt", "description", "task", "plan", "todos"]);
130
+ return Object.entries(args).flatMap(([key, value]) => {
131
+ if (hidden.has(key) || value === null || value === void 0) return [];
132
+ const rendered = typeof value === "string" ? value : JSON.stringify(value);
133
+ if (!rendered) return [];
134
+ return [{ label: key.replaceAll(/[_-]+/g, " ").replace(/^./, (letter) => letter.toLocaleUpperCase()), value: boundedString(rendered, MAX_TOOL_FIELD_CHARS) }];
135
+ }).slice(0, MAX_TOOL_FIELDS);
136
+ }
137
+ function planItems(args) {
138
+ const source = Array.isArray(args?.plan) ? args.plan : Array.isArray(args?.todos) ? args.todos : [];
139
+ return source.flatMap((item) => {
140
+ if (typeof item === "string" && item.trim()) return [{ label: boundedString(item.trim(), 300), status: "" }];
141
+ const value = record(item);
142
+ const label = firstString(value, ["step", "title", "content", "text"]);
143
+ if (!label) return [];
144
+ return [{ label: boundedString(label, 300), status: firstString(value, ["status"]) }];
145
+ }).slice(0, 12);
146
+ }
147
+ function editPreview(args, resultText, source) {
148
+ const direct = firstString(args, ["patch", "diff"]);
149
+ if (direct) return direct;
150
+ const oldText = firstString(args, ["old_string"]);
151
+ const newText = firstString(args, ["new_string"]);
152
+ if (oldText || newText) {
153
+ return [
154
+ ...oldText.split("\n").map((line) => `- ${line}`),
155
+ ...newText.split("\n").map((line) => `+ ${line}`)
156
+ ].join("\n");
157
+ }
158
+ const patch = /\*\*\* Begin Patch[\s\S]*?\*\*\* End Patch/.exec(source ?? "")?.[0];
159
+ if (patch) return patch;
160
+ return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? "") ? resultText : "";
161
+ }
162
+ function classifyTool(name, command) {
163
+ const normalized = name.toLocaleLowerCase();
164
+ if (/write_stdin|^wait$/.test(normalized)) return "command";
165
+ if (/update.?plan|todo|checklist|taskcreate|taskupdate/.test(normalized)) return "plan";
166
+ if (/search.?replace|edit|write|patch|replace|create_file|apply_patch/.test(normalized)) return "edit";
167
+ if (/read|view|open_file|list_dir/.test(normalized)) return "read";
168
+ if (/search|find|grep|glob|toolsearch/.test(normalized)) return "search";
169
+ if (/browser|web|fetch|url/.test(normalized)) return "web";
170
+ if (/agent|subagent|sendmessage|delegate|^task$/.test(normalized)) return "agent";
171
+ if (/test|typecheck|lint|build/.test(normalized)) return "test";
172
+ if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return /\b(test|typecheck|lint|build)\b/i.test(command) ? "test" : "command";
173
+ return "other";
174
+ }
175
+ function toolAction(status, category, name, tools) {
176
+ const position = status === "pending" ? 0 : status === "error" ? 2 : 1;
177
+ if (tools.length > 1) return [`Running ${tools.length} actions`, `Ran ${tools.length} actions`, `${tools.length} actions failed`][position];
178
+ const normalized = name.toLocaleLowerCase();
179
+ if (/sendmessage/.test(normalized)) return ["Messaging agent", "Messaged agent", "Agent message failed"][position];
180
+ if (/kill_command_or_subagent/.test(normalized)) return ["Stopping", "Stopped", "Stop failed"][position];
181
+ if (/get_command_or_subagent_output|write_stdin|^wait$/.test(normalized)) return ["Waiting for", "Checked", "Check failed"][position];
182
+ const actions = {
183
+ read: ["Reading", "Read", "Read failed"],
184
+ search: ["Searching", "Searched", "Search failed"],
185
+ edit: ["Editing", "Edited", "Edit failed"],
186
+ command: ["Running command", "Ran command", "Command failed"],
187
+ test: ["Running tests", "Ran tests", "Tests failed"],
188
+ web: ["Browsing", "Browsed", "Browser action failed"],
189
+ agent: ["Starting agent", "Started agent", "Agent failed"],
190
+ plan: ["Updating plan", "Updated plan", "Plan update failed"]
191
+ };
192
+ return actions[category]?.[position] ?? (status === "error" ? `${name} failed` : name.replaceAll(/[_-]+/g, " "));
193
+ }
194
+ function createToolPresentation(entry) {
195
+ const envelope = toolEnvelope(entry);
196
+ const args = envelope.args;
197
+ const command = firstString(args, ["command", "cmd"]) || sourceString(envelope.source, ["command", "cmd"]);
198
+ const category = classifyTool(envelope.name, command || envelope.source || entry.arguments || "");
199
+ const path = firstString(args, ["file_path", "target_file", "target_directory", "path"]) || sourceString(envelope.source, ["file_path", "target_file", "target_directory", "path"]) || patchPath(envelope.source);
200
+ const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.source, ["query", "pattern", "q"]);
201
+ const url = firstString(args, ["url"]) || sourceString(envelope.source, ["url", "ref_id"]);
202
+ const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.source, ["subject", "description", "summary", "task", "prompt"]);
203
+ const background = /get_command_or_subagent_output|kill_command_or_subagent/i.test(envelope.name) ? "background task" : /write_stdin|^wait$/i.test(envelope.name) ? "background command" : "";
204
+ const items = planItems(args);
205
+ const taskId = firstString(args, ["taskId", "task_id"]);
206
+ const planTarget = category === "plan" ? items.length ? `${items.length} ${items.length === 1 ? "item" : "items"}` : taskId ? `task ${taskId}` : "" : "";
207
+ const target = path || command || query || url || subject || background || planTarget || (envelope.tools.length > 1 ? `${envelope.tools.length} coordinated actions` : toolTarget(entry.arguments));
208
+ const previewSource = category === "edit" ? editPreview(args, entry.resultText, envelope.source) : entry.resultText ?? "";
209
+ const result = record(entry.resultContent);
210
+ const metadata = record(entry.metadata);
211
+ const resultMetadata = record(result?.metadata);
212
+ return {
213
+ name: boundedString(envelope.name, 120),
214
+ action: boundedString(toolAction(entry.status ?? "completed", category, envelope.name, envelope.tools), 120),
215
+ category,
216
+ detail: toolDetail(category),
217
+ target: boundedString(target, 300),
218
+ command: boundedString(command, MAX_TOOL_FIELD_CHARS),
219
+ path: boundedString(path, MAX_TOOL_FIELD_CHARS),
220
+ query: boundedString(query, MAX_TOOL_FIELD_CHARS),
221
+ url: boundedString(url, MAX_TOOL_FIELD_CHARS),
222
+ subject: boundedString(subject, MAX_TOOL_FIELD_CHARS),
223
+ preview: boundedString(previewSource, MAX_TOOL_PREVIEW_CHARS),
224
+ fields: usefulToolFields(args),
225
+ items,
226
+ tools: envelope.tools,
227
+ exitCode: explicitNumber([result, resultMetadata, metadata], ["exit_code", "exitCode", "pi_bash_exit_code"]),
228
+ durationMs: explicitNumber([result, resultMetadata, metadata], ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs", "totalDurationMs"]),
229
+ additions: explicitNumber([result, resultMetadata, metadata], ["additions", "lines_added"]),
230
+ deletions: explicitNumber([result, resultMetadata, metadata], ["deletions", "lines_removed"]),
231
+ matches: explicitNumber([result, resultMetadata, metadata], ["matches", "match_count", "result_count"])
232
+ };
233
+ }
62
234
  function harnessDisplayName(id) {
63
235
  return HARNESS_NAMES[id] ?? id;
64
236
  }
@@ -76,15 +248,10 @@ function groupConversation(entries) {
76
248
  return blocks;
77
249
  }
78
250
  function toolCategory(entry) {
79
- const name = entry.label?.toLocaleLowerCase() ?? "";
80
- if (/read|view|open_file|list_dir/.test(name)) return "read";
81
- if (/search|find|grep|glob/.test(name)) return "search";
82
- if (/edit|write|patch|replace|create_file/.test(name)) return "edit";
83
- if (/test|typecheck|lint|build/.test(name)) return "test";
84
- if (/terminal|bash|shell|command|exec/.test(name)) return /\b(test|typecheck|lint|build)\b/i.test(entry.arguments ?? "") ? "test" : "command";
85
- if (/browser|web|fetch|url/.test(name)) return "web";
86
- if (/subagent|spawn|task/.test(name)) return "agent";
87
- return "other";
251
+ if (TOOL_CATEGORIES.has(entry.presentation?.category)) return entry.presentation.category;
252
+ const envelope = toolEnvelope(entry);
253
+ const command = firstString(envelope.args, ["command", "cmd"]) || sourceString(envelope.source, ["command", "cmd"]);
254
+ return classifyTool(envelope.name, command || envelope.source || entry.arguments || "");
88
255
  }
89
256
  function toolTarget(argumentsText) {
90
257
  if (!argumentsText) return "";
@@ -110,7 +277,8 @@ function activitySummary(entries) {
110
277
  if (pending) return `${entries.length} actions in progress`;
111
278
  if (failed) return `${entries.length} actions \xB7 ${failed} failed`;
112
279
  if ([...counts.keys()].every((key) => key === "read" || key === "search")) return `Explored ${entries.length} ${entries.length === 1 ? "item" : "items"}`;
113
- return `${entries.length} actions`;
280
+ const nouns = { read: ["read", "reads"], search: ["search", "searches"], edit: ["edit", "edits"], command: ["command", "commands"], test: ["test run", "test runs"], web: ["web action", "web actions"], agent: ["agent action", "agent actions"], plan: ["plan update", "plan updates"], other: ["action", "actions"] };
281
+ return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([category, count]) => `${count} ${nouns[category][count === 1 ? 0 : 1]}`).join(" \xB7 ");
114
282
  }
115
283
 
116
284
  // src/markdown.jsx
@@ -212,6 +380,7 @@ var TOOL_ICONS = {
212
380
  /* @__PURE__ */ jsx2("circle", { cx: "9", cy: "6", r: "2.5" }),
213
381
  /* @__PURE__ */ jsx2("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
214
382
  ] }),
383
+ plan: () => /* @__PURE__ */ jsx2(Fragment, { children: /* @__PURE__ */ jsx2("path", { d: "m3 5 1 1 2-2M3 9l1 1 2-2M3 13l1 1 2-2M8 5h7M8 9h7M8 13h7" }) }),
215
384
  other: () => /* @__PURE__ */ jsxs(Fragment, { children: [
216
385
  /* @__PURE__ */ jsx2("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
217
386
  /* @__PURE__ */ jsx2("circle", { cx: "9", cy: "9", r: "3.5" })
@@ -221,7 +390,8 @@ function ToolIcon({ category }) {
221
390
  const Glyph = TOOL_ICONS[category] ?? TOOL_ICONS.other;
222
391
  return /* @__PURE__ */ jsx2("svg", { class: "scui-tool-icon", viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", "stroke-width": "1.35", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(Glyph, {}) });
223
392
  }
224
- function toolAction(entry, category) {
393
+ function toolAction2(entry, category, presentation) {
394
+ if (presentation?.action) return presentation.action;
225
395
  const status = entry.status ?? "completed";
226
396
  const actions = {
227
397
  read: ["Reading", "Read", "Read failed"],
@@ -230,7 +400,8 @@ function toolAction(entry, category) {
230
400
  command: ["Running command", "Ran command", "Command failed"],
231
401
  test: ["Running tests", "Ran tests", "Tests failed"],
232
402
  web: ["Browsing", "Browsed", "Browser action failed"],
233
- agent: ["Starting agent", "Started agent", "Agent failed"]
403
+ agent: ["Starting agent", "Started agent", "Agent failed"],
404
+ plan: ["Updating plan", "Updated plan", "Plan update failed"]
234
405
  };
235
406
  const position = status === "pending" ? 0 : status === "error" ? 2 : 1;
236
407
  if (actions[category]) return actions[category][position];
@@ -256,6 +427,138 @@ function argumentRows(argumentsText) {
256
427
  }
257
428
  return [{ key: "arguments", label: "Details", value: argumentsText }];
258
429
  }
430
+ function stripAnsi(value) {
431
+ return value?.replaceAll(/[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d\/#&.:=?%@~_]+)*)?\u0007)|(?:(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g, "") ?? "";
432
+ }
433
+ function formatDuration(value) {
434
+ if (value === null) return "";
435
+ if (value < 1e3) return `${Math.round(value)}ms`;
436
+ return `${(value / 1e3).toFixed(value < 1e4 ? 1 : 0)}s`;
437
+ }
438
+ function ToolMetrics({ presentation }) {
439
+ const metrics = [
440
+ presentation.matches === null ? "" : `${presentation.matches} ${presentation.matches === 1 ? "match" : "matches"}`,
441
+ presentation.additions === null ? "" : `+${presentation.additions}`,
442
+ presentation.deletions === null ? "" : `\u2212${presentation.deletions}`,
443
+ presentation.exitCode === null ? "" : `exit ${presentation.exitCode}`,
444
+ formatDuration(presentation.durationMs)
445
+ ].filter(Boolean);
446
+ return metrics.length ? /* @__PURE__ */ jsx2("div", { class: "scui-tool-metrics", children: metrics.map((metric) => /* @__PURE__ */ jsx2("span", { children: metric }, metric)) }) : null;
447
+ }
448
+ function PendingElapsed({ now }) {
449
+ const clock = now ?? Date.now;
450
+ const started = useRef(clock());
451
+ const [elapsed, setElapsed] = useState(0);
452
+ useEffect(() => {
453
+ const timer = setInterval(() => setElapsed(Math.max(0, clock() - started.current)), 1e3);
454
+ return () => clearInterval(timer);
455
+ }, [clock]);
456
+ return /* @__PURE__ */ jsx2("small", { class: "scui-tool-elapsed", children: elapsed < 1e3 ? "now" : formatDuration(elapsed) });
457
+ }
458
+ function LinePreview({ value, kind }) {
459
+ if (!value) return null;
460
+ return /* @__PURE__ */ jsx2("ol", { class: "scui-code-preview", "data-kind": kind, children: stripAnsi(value).split("\n").map((line, index) => {
461
+ const tone = kind === "diff" ? line.startsWith("+") && !line.startsWith("+++") ? "add" : line.startsWith("-") && !line.startsWith("---") ? "remove" : line.startsWith("@@") ? "hunk" : "plain" : "plain";
462
+ return /* @__PURE__ */ jsxs("li", { "data-tone": tone, children: [
463
+ /* @__PURE__ */ jsx2("span", { children: index + 1 }),
464
+ /* @__PURE__ */ jsx2("code", { children: line || " " })
465
+ ] }, index);
466
+ }) });
467
+ }
468
+ function TerminalPreview({ presentation, pending, failed }) {
469
+ return /* @__PURE__ */ jsxs("section", { class: "scui-terminal", children: [
470
+ /* @__PURE__ */ jsxs("header", { children: [
471
+ /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", children: [
472
+ /* @__PURE__ */ jsx2("i", {}),
473
+ /* @__PURE__ */ jsx2("i", {}),
474
+ /* @__PURE__ */ jsx2("i", {})
475
+ ] }),
476
+ /* @__PURE__ */ jsx2("code", { children: presentation.command ? `$ ${presentation.command}` : "Terminal" })
477
+ ] }),
478
+ presentation.preview ? /* @__PURE__ */ jsx2("pre", { "data-error": failed, children: stripAnsi(presentation.preview) }) : pending ? /* @__PURE__ */ jsxs("div", { class: "scui-terminal-wait", children: [
479
+ /* @__PURE__ */ jsx2("i", {}),
480
+ " Waiting for output"
481
+ ] }) : /* @__PURE__ */ jsx2("div", { class: "scui-terminal-empty", children: "No output" })
482
+ ] });
483
+ }
484
+ function SearchPreview({ presentation }) {
485
+ const lines = stripAnsi(presentation.preview).split("\n").filter(Boolean);
486
+ return /* @__PURE__ */ jsxs("section", { class: "scui-search-preview", children: [
487
+ presentation.query ? /* @__PURE__ */ jsxs("header", { children: [
488
+ /* @__PURE__ */ jsx2("span", { children: "Search" }),
489
+ /* @__PURE__ */ jsx2("code", { children: presentation.query })
490
+ ] }) : null,
491
+ lines.length ? /* @__PURE__ */ jsx2("ol", { children: lines.map((line, index) => {
492
+ const match = /^(.*?):(\d+)(?::(\d+))?:(.*)$/.exec(line);
493
+ return /* @__PURE__ */ jsx2("li", { children: match ? /* @__PURE__ */ jsxs(Fragment, { children: [
494
+ /* @__PURE__ */ jsx2("code", { children: match[1] }),
495
+ /* @__PURE__ */ jsxs("small", { children: [
496
+ match[2],
497
+ match[3] ? `:${match[3]}` : ""
498
+ ] }),
499
+ /* @__PURE__ */ jsx2("span", { children: match[4] })
500
+ ] }) : /* @__PURE__ */ jsx2("span", { children: line }) }, index);
501
+ }) }) : /* @__PURE__ */ jsx2("p", { children: "No textual results" })
502
+ ] });
503
+ }
504
+ function ToolPreview({ presentation, entry }) {
505
+ const pending = entry.status === "pending";
506
+ const failed = entry.status === "error";
507
+ if (presentation.detail === "terminal") return /* @__PURE__ */ jsx2(TerminalPreview, { presentation, pending, failed });
508
+ if (presentation.detail === "diff") return presentation.preview ? /* @__PURE__ */ jsx2(LinePreview, { value: presentation.preview, kind: "diff" }) : /* @__PURE__ */ jsx2("div", { class: "scui-tool-empty", children: "Edit completed without a textual diff" });
509
+ if (presentation.detail === "file") return presentation.preview ? /* @__PURE__ */ jsx2(LinePreview, { value: presentation.preview, kind: "file" }) : /* @__PURE__ */ jsx2("div", { class: "scui-tool-empty", children: "File contents were not included in this event" });
510
+ if (presentation.detail === "matches") return /* @__PURE__ */ jsx2(SearchPreview, { presentation });
511
+ if (presentation.detail === "web") return /* @__PURE__ */ jsxs("section", { class: "scui-web-preview", children: [
512
+ presentation.url ? /* @__PURE__ */ jsx2("code", { children: presentation.url }) : null,
513
+ presentation.preview ? /* @__PURE__ */ jsx2("p", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx2("small", { children: pending ? "Waiting for the page" : "No page summary returned" })
514
+ ] });
515
+ if (presentation.detail === "agent") return /* @__PURE__ */ jsx2("section", { class: "scui-agent-preview", children: presentation.preview ? /* @__PURE__ */ jsx2("pre", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx2("small", { children: pending ? "Agent is working" : "No textual handoff returned" }) });
516
+ if (presentation.detail === "plan") return /* @__PURE__ */ jsx2("ol", { class: "scui-plan-preview", children: presentation.items?.map((item, index) => /* @__PURE__ */ jsxs("li", { "data-status": item.status, children: [
517
+ /* @__PURE__ */ jsx2("i", { "aria-hidden": "true" }),
518
+ /* @__PURE__ */ jsx2("span", { children: item.label })
519
+ ] }, `${item.label}:${index}`)) });
520
+ return presentation.preview ? /* @__PURE__ */ jsx2("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
521
+ }
522
+ function ToolActions({ presentation, adapter }) {
523
+ const [copied, setCopied] = useState(false);
524
+ const reset = useRef(null);
525
+ useEffect(() => () => clearTimeout(reset.current), []);
526
+ if (!adapter?.copyText) return null;
527
+ const action = presentation.command ? ["Copy command", presentation.command] : presentation.path ? ["Copy path", presentation.path] : presentation.url ? ["Copy URL", presentation.url] : presentation.query ? ["Copy query", presentation.query] : null;
528
+ const copy = async () => {
529
+ await adapter.copyText(action[1]);
530
+ setCopied(true);
531
+ clearTimeout(reset.current);
532
+ reset.current = setTimeout(() => setCopied(false), 1500);
533
+ };
534
+ return action ? /* @__PURE__ */ jsx2("div", { class: "scui-tool-actions", children: /* @__PURE__ */ jsx2("button", { type: "button", onClick: copy, children: copied ? "Copied" : action[0] }) }) : null;
535
+ }
536
+ function ToolStack({ tools }) {
537
+ if (tools.length < 2) return null;
538
+ return /* @__PURE__ */ jsx2("div", { class: "scui-tool-stack", "aria-label": "Coordinated tools", children: tools.map((tool) => /* @__PURE__ */ jsx2("span", { children: tool.replaceAll("__", " \xB7 ").replaceAll("_", " ") }, tool)) });
539
+ }
540
+ function TechnicalDetails({ entry }) {
541
+ if (!entry.arguments && !entry.resultText) return null;
542
+ return /* @__PURE__ */ jsxs("details", { class: "scui-tool-technical", children: [
543
+ /* @__PURE__ */ jsx2("summary", { children: "Technical details" }),
544
+ /* @__PURE__ */ jsxs("div", { children: [
545
+ entry.arguments ? /* @__PURE__ */ jsxs("section", { children: [
546
+ /* @__PURE__ */ jsx2("strong", { children: "Native arguments" }),
547
+ /* @__PURE__ */ jsx2("dl", { children: argumentRows(entry.arguments).map((row) => /* @__PURE__ */ jsxs("div", { children: [
548
+ /* @__PURE__ */ jsx2("dt", { children: row.label }),
549
+ /* @__PURE__ */ jsx2("dd", { children: /* @__PURE__ */ jsx2("pre", { children: row.value }) })
550
+ ] }, row.key)) })
551
+ ] }) : null,
552
+ entry.resultText ? /* @__PURE__ */ jsxs("section", { children: [
553
+ /* @__PURE__ */ jsx2("strong", { children: "Native result" }),
554
+ /* @__PURE__ */ jsxs("pre", { "data-error": entry.status === "error", children: [
555
+ entry.resultText,
556
+ entry.truncated ? "\n[truncated]" : ""
557
+ ] })
558
+ ] }) : null
559
+ ] })
560
+ ] });
561
+ }
259
562
  function TranscriptEntry({ entry, state, adapter }) {
260
563
  if (entry.role === "request") return /* @__PURE__ */ jsx2(RequestCard, { entry, adapter, canRespond: state.canRespond });
261
564
  if (entry.role === "reasoning") {
@@ -271,38 +574,42 @@ function TranscriptEntry({ entry, state, adapter }) {
271
574
  entry.truncated ? /* @__PURE__ */ jsx2("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null
272
575
  ] });
273
576
  }
274
- function ToolRow({ entry, workspace }) {
275
- const target = compactToolTarget(toolTarget(entry.arguments), workspace);
276
- const hasDetail = Boolean(entry.arguments || entry.resultText);
277
- const category = toolCategory(entry);
577
+ function ToolRow({ entry, workspace, open = false, adapter }) {
578
+ const presentation = entry.presentation ?? createToolPresentation(entry);
579
+ const [expanded, setExpanded] = useState(open || entry.status === "pending");
580
+ useEffect(() => {
581
+ if (entry.status === "pending") setExpanded(true);
582
+ }, [entry.status]);
583
+ const target = compactToolTarget(presentation.target, workspace);
584
+ const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
585
+ const category = presentation.category ?? toolCategory(entry);
278
586
  const summary = /* @__PURE__ */ jsxs(Fragment, { children: [
279
587
  /* @__PURE__ */ jsx2(ToolIcon, { category }),
280
- /* @__PURE__ */ jsx2("strong", { children: toolAction(entry, category) }),
281
- target ? /* @__PURE__ */ jsx2("code", { class: "scui-tool-target", title: toolTarget(entry.arguments), children: target }) : null,
588
+ /* @__PURE__ */ jsx2("strong", { children: toolAction2(entry, category, presentation) }),
589
+ target ? /* @__PURE__ */ jsx2("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
282
590
  /* @__PURE__ */ jsx2("span", { class: "scui-spacer" }),
591
+ entry.status === "pending" ? /* @__PURE__ */ jsx2(PendingElapsed, { now: adapter?.now }) : null,
283
592
  /* @__PURE__ */ jsx2("span", { class: "scui-tool-status", role: "status", "data-status": entry.status ?? "completed", "aria-label": entry.status ?? "completed", children: entry.status === "pending" ? /* @__PURE__ */ jsx2("i", {}) : entry.status === "error" ? "\xD7" : "\u2713" }),
284
593
  hasDetail ? /* @__PURE__ */ jsx2("span", { class: "scui-tool-chevron", "aria-hidden": "true", children: "\u203A" }) : null
285
594
  ] });
286
595
  if (!hasDetail) return /* @__PURE__ */ jsx2("div", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, children: /* @__PURE__ */ jsx2("div", { class: "scui-tool-head", children: summary }) });
287
- return /* @__PURE__ */ jsxs("details", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, children: [
596
+ return /* @__PURE__ */ jsxs("details", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, open: expanded, onToggle: (event) => setExpanded(event.currentTarget.open), children: [
288
597
  /* @__PURE__ */ jsx2("summary", { class: "scui-tool-head", children: summary }),
289
598
  /* @__PURE__ */ jsxs("div", { class: "scui-tool-detail", children: [
290
- entry.arguments ? /* @__PURE__ */ jsx2("dl", { children: argumentRows(entry.arguments).map((row) => /* @__PURE__ */ jsxs("div", { children: [
291
- /* @__PURE__ */ jsx2("dt", { children: row.label }),
292
- /* @__PURE__ */ jsx2("dd", { children: /* @__PURE__ */ jsx2("pre", { children: row.value }) })
293
- ] }, row.key)) }) : null,
294
- entry.resultText ? /* @__PURE__ */ jsxs("section", { children: [
295
- /* @__PURE__ */ jsx2("strong", { children: entry.status === "error" ? "Error" : "Result" }),
296
- /* @__PURE__ */ jsxs("pre", { "data-error": entry.status === "error", children: [
297
- entry.resultText,
298
- entry.truncated ? "\n[truncated]" : ""
299
- ] })
300
- ] }) : null
599
+ /* @__PURE__ */ jsx2(ToolMetrics, { presentation }),
600
+ /* @__PURE__ */ jsx2(ToolStack, { tools: presentation.tools ?? [] }),
601
+ /* @__PURE__ */ jsx2(ToolPreview, { presentation, entry }),
602
+ presentation.fields.length ? /* @__PURE__ */ jsx2("dl", { class: "scui-tool-fields", children: presentation.fields.map((field) => /* @__PURE__ */ jsxs("div", { children: [
603
+ /* @__PURE__ */ jsx2("dt", { children: field.label }),
604
+ /* @__PURE__ */ jsx2("dd", { children: field.value })
605
+ ] }, field.label)) }) : null,
606
+ /* @__PURE__ */ jsx2(ToolActions, { presentation, adapter }),
607
+ /* @__PURE__ */ jsx2(TechnicalDetails, { entry })
301
608
  ] })
302
609
  ] });
303
610
  }
304
- function ActivityGroup({ entries, state }) {
305
- if (entries.length === 1) return /* @__PURE__ */ jsx2("section", { class: "scui-activity", "data-single": "true", children: /* @__PURE__ */ jsx2(ToolRow, { entry: entries[0], workspace: state.workspace }) });
611
+ function ActivityGroup({ entries, state, adapter }) {
612
+ if (entries.length === 1) return /* @__PURE__ */ jsx2("section", { class: "scui-activity", "data-single": "true", children: /* @__PURE__ */ jsx2(ToolRow, { entry: entries[0], workspace: state.workspace, adapter }) });
306
613
  const active = entries.some((entry) => entry.status === "pending");
307
614
  const [open, setOpen] = useState(active);
308
615
  const id = useId();
@@ -320,7 +627,7 @@ function ActivityGroup({ entries, state }) {
320
627
  entries.length
321
628
  ] })
322
629
  ] }),
323
- open ? /* @__PURE__ */ jsx2("div", { id, children: entries.map((entry) => /* @__PURE__ */ jsx2(ToolRow, { entry, workspace: state.workspace }, entry.id)) }) : null
630
+ open ? /* @__PURE__ */ jsx2("div", { id, children: entries.map((entry) => /* @__PURE__ */ jsx2(ToolRow, { entry, workspace: state.workspace, adapter }, entry.id)) }) : null
324
631
  ] });
325
632
  }
326
633
  function TaskPlan({ plan }) {
@@ -462,5 +769,6 @@ export {
462
769
  RequestCard,
463
770
  SessionDetails,
464
771
  TaskPlan,
772
+ ToolRow,
465
773
  TranscriptEntry
466
774
  };
package/core.d.ts CHANGED
@@ -19,6 +19,8 @@ export type {
19
19
  TranscriptContext,
20
20
  TranscriptEntryModel,
21
21
  TranscriptRequest,
22
+ ToolCategory,
23
+ ToolPresentationModel,
22
24
  UiTone,
23
25
  } from './index.js';
24
26
  export {
@@ -27,6 +29,7 @@ export {
27
29
  activitySummary,
28
30
  canContinueHere,
29
31
  compactToolTarget,
32
+ createToolPresentation,
30
33
  filterSessions,
31
34
  groupConversation,
32
35
  harnessDisplayName,