@volter-ai-dev/supercode-ui 0.1.9 → 0.1.11

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
@@ -70,10 +70,10 @@ function boundedString(value, max) {
70
70
  if (typeof value !== "string") return "";
71
71
  return value.length <= max ? value : `${value.slice(0, max)}\u2026`;
72
72
  }
73
- function argumentObject(argumentsText) {
73
+ function argumentValue(argumentsText) {
74
74
  if (!argumentsText) return null;
75
75
  try {
76
- return record(JSON.parse(argumentsText));
76
+ return JSON.parse(argumentsText);
77
77
  } catch {
78
78
  return null;
79
79
  }
@@ -84,6 +84,33 @@ function firstString(source, keys) {
84
84
  }
85
85
  return "";
86
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
+ }
87
114
  function explicitNumber(sources, keys) {
88
115
  for (const source of sources) {
89
116
  for (const key of keys) {
@@ -117,7 +144,7 @@ function planItems(args) {
117
144
  return [{ label: boundedString(label, 300), status: firstString(value, ["status"]) }];
118
145
  }).slice(0, 12);
119
146
  }
120
- function editPreview(args, resultText) {
147
+ function editPreview(args, resultText, source) {
121
148
  const direct = firstString(args, ["patch", "diff"]);
122
149
  if (direct) return direct;
123
150
  const oldText = firstString(args, ["old_string"]);
@@ -128,21 +155,63 @@ function editPreview(args, resultText) {
128
155
  ...newText.split("\n").map((line) => `+ ${line}`)
129
156
  ].join("\n");
130
157
  }
158
+ const patch = /\*\*\* Begin Patch[\s\S]*?\*\*\* End Patch/.exec(source ?? "")?.[0];
159
+ if (patch) return patch;
131
160
  return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? "") ? resultText : "";
132
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
+ }
133
194
  function createToolPresentation(entry) {
134
- const args = argumentObject(entry.arguments);
135
- const category = toolCategory({ label: entry.label, arguments: entry.arguments });
136
- const command = firstString(args, ["command", "cmd"]);
137
- const path = firstString(args, ["file_path", "target_file", "path"]);
138
- const query = firstString(args, ["query", "pattern"]);
139
- const url = firstString(args, ["url"]);
140
- const subject = firstString(args, ["description", "task", "prompt"]);
141
- const target = path || command || query || url || subject || toolTarget(entry.arguments);
142
- const previewSource = category === "edit" ? editPreview(args, entry.resultText) : entry.resultText ?? "";
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 ?? "";
143
209
  const result = record(entry.resultContent);
144
210
  const metadata = record(entry.metadata);
211
+ const resultMetadata = record(result?.metadata);
145
212
  return {
213
+ name: boundedString(envelope.name, 120),
214
+ action: boundedString(toolAction(entry.status ?? "completed", category, envelope.name, envelope.tools), 120),
146
215
  category,
147
216
  detail: toolDetail(category),
148
217
  target: boundedString(target, 300),
@@ -153,12 +222,13 @@ function createToolPresentation(entry) {
153
222
  subject: boundedString(subject, MAX_TOOL_FIELD_CHARS),
154
223
  preview: boundedString(previewSource, MAX_TOOL_PREVIEW_CHARS),
155
224
  fields: usefulToolFields(args),
156
- items: planItems(args),
157
- exitCode: explicitNumber([result, metadata], ["exit_code", "exitCode"]),
158
- durationMs: explicitNumber([result, metadata], ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs"]),
159
- additions: explicitNumber([result, metadata], ["additions", "lines_added"]),
160
- deletions: explicitNumber([result, metadata], ["deletions", "lines_removed"]),
161
- matches: explicitNumber([result, metadata], ["matches", "match_count", "result_count"])
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"])
162
232
  };
163
233
  }
164
234
  function harnessDisplayName(id) {
@@ -179,16 +249,9 @@ function groupConversation(entries) {
179
249
  }
180
250
  function toolCategory(entry) {
181
251
  if (TOOL_CATEGORIES.has(entry.presentation?.category)) return entry.presentation.category;
182
- const name = entry.label?.toLocaleLowerCase() ?? "";
183
- if (/read|view|open_file|list_dir/.test(name)) return "read";
184
- if (/search|find|grep|glob/.test(name)) return "search";
185
- if (/edit|write|patch|replace|create_file/.test(name)) return "edit";
186
- if (/test|typecheck|lint|build/.test(name)) return "test";
187
- if (/terminal|bash|shell|command|exec/.test(name)) return /\b(test|typecheck|lint|build)\b/i.test(entry.arguments ?? "") ? "test" : "command";
188
- if (/browser|web|fetch|url/.test(name)) return "web";
189
- if (/update.?plan|todo|checklist/.test(name)) return "plan";
190
- if (/subagent|spawn.?agent|delegate|^task$/.test(name)) return "agent";
191
- return "other";
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 || "");
192
255
  }
193
256
  function toolTarget(argumentsText) {
194
257
  if (!argumentsText) return "";
@@ -214,7 +277,8 @@ function activitySummary(entries) {
214
277
  if (pending) return `${entries.length} actions in progress`;
215
278
  if (failed) return `${entries.length} actions \xB7 ${failed} failed`;
216
279
  if ([...counts.keys()].every((key) => key === "read" || key === "search")) return `Explored ${entries.length} ${entries.length === 1 ? "item" : "items"}`;
217
- 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 ");
218
282
  }
219
283
 
220
284
  // src/markdown.jsx
@@ -326,7 +390,8 @@ function ToolIcon({ category }) {
326
390
  const Glyph = TOOL_ICONS[category] ?? TOOL_ICONS.other;
327
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, {}) });
328
392
  }
329
- function toolAction(entry, category) {
393
+ function toolAction2(entry, category, presentation) {
394
+ if (presentation?.action) return presentation.action;
330
395
  const status = entry.status ?? "completed";
331
396
  const actions = {
332
397
  read: ["Reading", "Read", "Read failed"],
@@ -380,6 +445,16 @@ function ToolMetrics({ presentation }) {
380
445
  ].filter(Boolean);
381
446
  return metrics.length ? /* @__PURE__ */ jsx2("div", { class: "scui-tool-metrics", children: metrics.map((metric) => /* @__PURE__ */ jsx2("span", { children: metric }, metric)) }) : null;
382
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
+ }
383
458
  function LinePreview({ value, kind }) {
384
459
  if (!value) return null;
385
460
  return /* @__PURE__ */ jsx2("ol", { class: "scui-code-preview", "data-kind": kind, children: stripAnsi(value).split("\n").map((line, index) => {
@@ -437,16 +512,31 @@ function ToolPreview({ presentation, entry }) {
437
512
  presentation.url ? /* @__PURE__ */ jsx2("code", { children: presentation.url }) : null,
438
513
  presentation.preview ? /* @__PURE__ */ jsx2("p", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx2("small", { children: pending ? "Waiting for the page" : "No page summary returned" })
439
514
  ] });
440
- if (presentation.detail === "agent") return /* @__PURE__ */ jsxs("section", { class: "scui-agent-preview", children: [
441
- presentation.subject ? /* @__PURE__ */ jsx2("p", { children: presentation.subject }) : null,
442
- presentation.preview ? /* @__PURE__ */ jsx2("pre", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx2("small", { children: pending ? "Agent is working" : "No textual handoff returned" })
443
- ] });
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" }) });
444
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: [
445
517
  /* @__PURE__ */ jsx2("i", { "aria-hidden": "true" }),
446
518
  /* @__PURE__ */ jsx2("span", { children: item.label })
447
519
  ] }, `${item.label}:${index}`)) });
448
520
  return presentation.preview ? /* @__PURE__ */ jsx2("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
449
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
+ }
450
540
  function TechnicalDetails({ entry }) {
451
541
  if (!entry.arguments && !entry.resultText) return null;
452
542
  return /* @__PURE__ */ jsxs("details", { class: "scui-tool-technical", children: [
@@ -484,35 +574,42 @@ function TranscriptEntry({ entry, state, adapter }) {
484
574
  entry.truncated ? /* @__PURE__ */ jsx2("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null
485
575
  ] });
486
576
  }
487
- function ToolRow({ entry, workspace, open = false }) {
577
+ function ToolRow({ entry, workspace, open = false, adapter }) {
488
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]);
489
583
  const target = compactToolTarget(presentation.target, workspace);
490
584
  const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
491
585
  const category = presentation.category ?? toolCategory(entry);
492
586
  const summary = /* @__PURE__ */ jsxs(Fragment, { children: [
493
587
  /* @__PURE__ */ jsx2(ToolIcon, { category }),
494
- /* @__PURE__ */ jsx2("strong", { children: toolAction(entry, category) }),
588
+ /* @__PURE__ */ jsx2("strong", { children: toolAction2(entry, category, presentation) }),
495
589
  target ? /* @__PURE__ */ jsx2("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
496
590
  /* @__PURE__ */ jsx2("span", { class: "scui-spacer" }),
591
+ entry.status === "pending" ? /* @__PURE__ */ jsx2(PendingElapsed, { now: adapter?.now }) : null,
497
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" }),
498
593
  hasDetail ? /* @__PURE__ */ jsx2("span", { class: "scui-tool-chevron", "aria-hidden": "true", children: "\u203A" }) : null
499
594
  ] });
500
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 }) });
501
- return /* @__PURE__ */ jsxs("details", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, open: open || entry.status === "pending", 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: [
502
597
  /* @__PURE__ */ jsx2("summary", { class: "scui-tool-head", children: summary }),
503
598
  /* @__PURE__ */ jsxs("div", { class: "scui-tool-detail", children: [
504
599
  /* @__PURE__ */ jsx2(ToolMetrics, { presentation }),
600
+ /* @__PURE__ */ jsx2(ToolStack, { tools: presentation.tools ?? [] }),
505
601
  /* @__PURE__ */ jsx2(ToolPreview, { presentation, entry }),
506
602
  presentation.fields.length ? /* @__PURE__ */ jsx2("dl", { class: "scui-tool-fields", children: presentation.fields.map((field) => /* @__PURE__ */ jsxs("div", { children: [
507
603
  /* @__PURE__ */ jsx2("dt", { children: field.label }),
508
604
  /* @__PURE__ */ jsx2("dd", { children: field.value })
509
605
  ] }, field.label)) }) : null,
606
+ /* @__PURE__ */ jsx2(ToolActions, { presentation, adapter }),
510
607
  /* @__PURE__ */ jsx2(TechnicalDetails, { entry })
511
608
  ] })
512
609
  ] });
513
610
  }
514
- function ActivityGroup({ entries, state }) {
515
- 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 }) });
516
613
  const active = entries.some((entry) => entry.status === "pending");
517
614
  const [open, setOpen] = useState(active);
518
615
  const id = useId();
@@ -530,7 +627,7 @@ function ActivityGroup({ entries, state }) {
530
627
  entries.length
531
628
  ] })
532
629
  ] }),
533
- 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
534
631
  ] });
535
632
  }
536
633
  function TaskPlan({ plan }) {
package/core.mjs CHANGED
@@ -90,15 +90,19 @@ function boundedString(value, max) {
90
90
  return value.length <= max ? value : `${value.slice(0, max)}…`;
91
91
  }
92
92
 
93
- function argumentObject(argumentsText) {
93
+ function argumentValue(argumentsText) {
94
94
  if (!argumentsText) return null;
95
95
  try {
96
- return record(JSON.parse(argumentsText));
96
+ return JSON.parse(argumentsText);
97
97
  } catch {
98
98
  return null;
99
99
  }
100
100
  }
101
101
 
102
+ function argumentObject(argumentsText) {
103
+ return record(argumentValue(argumentsText));
104
+ }
105
+
102
106
  function firstString(source, keys) {
103
107
  for (const key of keys) {
104
108
  if (typeof source?.[key] === 'string' && source[key].trim()) return source[key].trim();
@@ -106,6 +110,41 @@ function firstString(source, keys) {
106
110
  return '';
107
111
  }
108
112
 
113
+ function decodedLiteral(value) {
114
+ if (!value) return '';
115
+ if (value.startsWith('"')) {
116
+ try { return JSON.parse(value); } catch { return ''; }
117
+ }
118
+ return value.slice(1, -1)
119
+ .replaceAll('\\n', '\n')
120
+ .replaceAll('\\t', '\t')
121
+ .replaceAll('\\r', '\r')
122
+ .replaceAll('\\`', '`')
123
+ .replaceAll("\\'", "'")
124
+ .replaceAll('\\\\', '\\');
125
+ }
126
+
127
+ function sourceString(source, keys) {
128
+ if (!source) return '';
129
+ const names = keys.join('|');
130
+ const match = new RegExp(`\\b(?:${names})\\s*:\\s*(\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
131
+ return decodedLiteral(match?.[1]);
132
+ }
133
+
134
+ function toolEnvelope(entry) {
135
+ const value = argumentValue(entry.arguments);
136
+ const source = typeof value === 'string' ? value : '';
137
+ const tools = source
138
+ ? [...new Set([...source.matchAll(/\btools\.([A-Za-z0-9_]+)/g)].map((match) => match[1]))].slice(0, 8)
139
+ : [];
140
+ const name = tools.length === 1 ? tools[0] : entry.label ?? 'tool';
141
+ return { args: record(value), source, tools, name };
142
+ }
143
+
144
+ function patchPath(source) {
145
+ return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]+)/.exec(source)?.[1]?.trim() ?? '';
146
+ }
147
+
109
148
  function explicitNumber(sources, keys) {
110
149
  for (const source of sources) {
111
150
  for (const key of keys) {
@@ -143,7 +182,7 @@ function planItems(args) {
143
182
  }).slice(0, 12);
144
183
  }
145
184
 
146
- function editPreview(args, resultText) {
185
+ function editPreview(args, resultText, source) {
147
186
  const direct = firstString(args, ['patch', 'diff']);
148
187
  if (direct) return direct;
149
188
  const oldText = firstString(args, ['old_string']);
@@ -154,22 +193,66 @@ function editPreview(args, resultText) {
154
193
  ...newText.split('\n').map((line) => `+ ${line}`),
155
194
  ].join('\n');
156
195
  }
196
+ const patch = /\*\*\* Begin Patch[\s\S]*?\*\*\* End Patch/.exec(source ?? '')?.[0];
197
+ if (patch) return patch;
157
198
  return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? '') ? resultText : '';
158
199
  }
159
200
 
201
+ function classifyTool(name, command) {
202
+ const normalized = name.toLocaleLowerCase();
203
+ if (/write_stdin|^wait$/.test(normalized)) return 'command';
204
+ if (/update.?plan|todo|checklist|taskcreate|taskupdate/.test(normalized)) return 'plan';
205
+ if (/search.?replace|edit|write|patch|replace|create_file|apply_patch/.test(normalized)) return 'edit';
206
+ if (/read|view|open_file|list_dir/.test(normalized)) return 'read';
207
+ if (/search|find|grep|glob|toolsearch/.test(normalized)) return 'search';
208
+ if (/browser|web|fetch|url/.test(normalized)) return 'web';
209
+ if (/agent|subagent|sendmessage|delegate|^task$/.test(normalized)) return 'agent';
210
+ if (/test|typecheck|lint|build/.test(normalized)) return 'test';
211
+ if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return /\b(test|typecheck|lint|build)\b/i.test(command) ? 'test' : 'command';
212
+ return 'other';
213
+ }
214
+
215
+ function toolAction(status, category, name, tools) {
216
+ const position = status === 'pending' ? 0 : status === 'error' ? 2 : 1;
217
+ if (tools.length > 1) return [`Running ${tools.length} actions`, `Ran ${tools.length} actions`, `${tools.length} actions failed`][position];
218
+ const normalized = name.toLocaleLowerCase();
219
+ if (/sendmessage/.test(normalized)) return ['Messaging agent', 'Messaged agent', 'Agent message failed'][position];
220
+ if (/kill_command_or_subagent/.test(normalized)) return ['Stopping', 'Stopped', 'Stop failed'][position];
221
+ if (/get_command_or_subagent_output|write_stdin|^wait$/.test(normalized)) return ['Waiting for', 'Checked', 'Check failed'][position];
222
+ const actions = {
223
+ read: ['Reading', 'Read', 'Read failed'],
224
+ search: ['Searching', 'Searched', 'Search failed'],
225
+ edit: ['Editing', 'Edited', 'Edit failed'],
226
+ command: ['Running command', 'Ran command', 'Command failed'],
227
+ test: ['Running tests', 'Ran tests', 'Tests failed'],
228
+ web: ['Browsing', 'Browsed', 'Browser action failed'],
229
+ agent: ['Starting agent', 'Started agent', 'Agent failed'],
230
+ plan: ['Updating plan', 'Updated plan', 'Plan update failed'],
231
+ };
232
+ return actions[category]?.[position] ?? (status === 'error' ? `${name} failed` : name.replaceAll(/[_-]+/g, ' '));
233
+ }
234
+
160
235
  export function createToolPresentation(entry) {
161
- const args = argumentObject(entry.arguments);
162
- const category = toolCategory({ label: entry.label, arguments: entry.arguments });
163
- const command = firstString(args, ['command', 'cmd']);
164
- const path = firstString(args, ['file_path', 'target_file', 'path']);
165
- const query = firstString(args, ['query', 'pattern']);
166
- const url = firstString(args, ['url']);
167
- const subject = firstString(args, ['description', 'task', 'prompt']);
168
- const target = path || command || query || url || subject || toolTarget(entry.arguments);
169
- const previewSource = category === 'edit' ? editPreview(args, entry.resultText) : (entry.resultText ?? '');
236
+ const envelope = toolEnvelope(entry);
237
+ const args = envelope.args;
238
+ const command = firstString(args, ['command', 'cmd']) || sourceString(envelope.source, ['command', 'cmd']);
239
+ const category = classifyTool(envelope.name, command || envelope.source || entry.arguments || '');
240
+ const path = firstString(args, ['file_path', 'target_file', 'target_directory', 'path']) || sourceString(envelope.source, ['file_path', 'target_file', 'target_directory', 'path']) || patchPath(envelope.source);
241
+ const query = firstString(args, ['query', 'pattern']) || sourceString(envelope.source, ['query', 'pattern', 'q']);
242
+ const url = firstString(args, ['url']) || sourceString(envelope.source, ['url', 'ref_id']);
243
+ const subject = firstString(args, ['subject', 'description', 'summary', 'task', 'prompt']) || sourceString(envelope.source, ['subject', 'description', 'summary', 'task', 'prompt']);
244
+ 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' : '';
245
+ const items = planItems(args);
246
+ const taskId = firstString(args, ['taskId', 'task_id']);
247
+ const planTarget = category === 'plan' ? items.length ? `${items.length} ${items.length === 1 ? 'item' : 'items'}` : taskId ? `task ${taskId}` : '' : '';
248
+ const target = path || command || query || url || subject || background || planTarget || (envelope.tools.length > 1 ? `${envelope.tools.length} coordinated actions` : toolTarget(entry.arguments));
249
+ const previewSource = category === 'edit' ? editPreview(args, entry.resultText, envelope.source) : (entry.resultText ?? '');
170
250
  const result = record(entry.resultContent);
171
251
  const metadata = record(entry.metadata);
252
+ const resultMetadata = record(result?.metadata);
172
253
  return {
254
+ name: boundedString(envelope.name, 120),
255
+ action: boundedString(toolAction(entry.status ?? 'completed', category, envelope.name, envelope.tools), 120),
173
256
  category,
174
257
  detail: toolDetail(category),
175
258
  target: boundedString(target, 300),
@@ -180,12 +263,13 @@ export function createToolPresentation(entry) {
180
263
  subject: boundedString(subject, MAX_TOOL_FIELD_CHARS),
181
264
  preview: boundedString(previewSource, MAX_TOOL_PREVIEW_CHARS),
182
265
  fields: usefulToolFields(args),
183
- items: planItems(args),
184
- exitCode: explicitNumber([result, metadata], ['exit_code', 'exitCode']),
185
- durationMs: explicitNumber([result, metadata], ['duration_ms', 'durationMs', 'elapsed_ms', 'elapsedMs']),
186
- additions: explicitNumber([result, metadata], ['additions', 'lines_added']),
187
- deletions: explicitNumber([result, metadata], ['deletions', 'lines_removed']),
188
- matches: explicitNumber([result, metadata], ['matches', 'match_count', 'result_count']),
266
+ items,
267
+ tools: envelope.tools,
268
+ exitCode: explicitNumber([result, resultMetadata, metadata], ['exit_code', 'exitCode', 'pi_bash_exit_code']),
269
+ durationMs: explicitNumber([result, resultMetadata, metadata], ['duration_ms', 'durationMs', 'elapsed_ms', 'elapsedMs', 'totalDurationMs']),
270
+ additions: explicitNumber([result, resultMetadata, metadata], ['additions', 'lines_added']),
271
+ deletions: explicitNumber([result, resultMetadata, metadata], ['deletions', 'lines_removed']),
272
+ matches: explicitNumber([result, resultMetadata, metadata], ['matches', 'match_count', 'result_count']),
189
273
  };
190
274
  }
191
275
 
@@ -205,7 +289,10 @@ function readToolPresentation(value, entry) {
205
289
  ? [{ label: boundedString(planItem.label, 300), status: boundedString(planItem.status, 40) }]
206
290
  : [];
207
291
  }).slice(0, 12) : generated.items;
292
+ const tools = Array.isArray(item.tools) ? item.tools.filter((tool) => typeof tool === 'string').map((tool) => boundedString(tool, 120)).slice(0, 8) : generated.tools;
208
293
  return {
294
+ name: boundedString(item.name, 120) || generated.name,
295
+ action: boundedString(item.action, 120) || generated.action,
209
296
  category: TOOL_CATEGORIES.has(item.category) ? item.category : generated.category,
210
297
  detail: TOOL_DETAILS.has(item.detail) ? item.detail : generated.detail,
211
298
  target: boundedString(item.target, 300) || generated.target,
@@ -217,6 +304,7 @@ function readToolPresentation(value, entry) {
217
304
  preview: boundedString(item.preview, MAX_TOOL_PREVIEW_CHARS) || generated.preview,
218
305
  fields,
219
306
  items,
307
+ tools,
220
308
  exitCode: nullableNumber(item.exitCode) ?? generated.exitCode,
221
309
  durationMs: nullableNumber(item.durationMs) ?? generated.durationMs,
222
310
  additions: nullableNumber(item.additions) ?? generated.additions,
@@ -505,16 +593,9 @@ export function groupConversation(entries) {
505
593
 
506
594
  export function toolCategory(entry) {
507
595
  if (TOOL_CATEGORIES.has(entry.presentation?.category)) return entry.presentation.category;
508
- const name = entry.label?.toLocaleLowerCase() ?? '';
509
- if (/read|view|open_file|list_dir/.test(name)) return 'read';
510
- if (/search|find|grep|glob/.test(name)) return 'search';
511
- if (/edit|write|patch|replace|create_file/.test(name)) return 'edit';
512
- if (/test|typecheck|lint|build/.test(name)) return 'test';
513
- if (/terminal|bash|shell|command|exec/.test(name)) return /\b(test|typecheck|lint|build)\b/i.test(entry.arguments ?? '') ? 'test' : 'command';
514
- if (/browser|web|fetch|url/.test(name)) return 'web';
515
- if (/update.?plan|todo|checklist/.test(name)) return 'plan';
516
- if (/subagent|spawn.?agent|delegate|^task$/.test(name)) return 'agent';
517
- return 'other';
596
+ const envelope = toolEnvelope(entry);
597
+ const command = firstString(envelope.args, ['command', 'cmd']) || sourceString(envelope.source, ['command', 'cmd']);
598
+ return classifyTool(envelope.name, command || envelope.source || entry.arguments || '');
518
599
  }
519
600
 
520
601
  export function toolTarget(argumentsText) {
@@ -543,7 +624,8 @@ export function activitySummary(entries) {
543
624
  if (pending) return `${entries.length} actions in progress`;
544
625
  if (failed) return `${entries.length} actions · ${failed} failed`;
545
626
  if ([...counts.keys()].every((key) => key === 'read' || key === 'search')) return `Explored ${entries.length} ${entries.length === 1 ? 'item' : 'items'}`;
546
- return `${entries.length} actions`;
627
+ 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'] };
628
+ return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([category, count]) => `${count} ${nouns[category][count === 1 ? 0 : 1]}`).join(' · ');
547
629
  }
548
630
 
549
631
  export function canContinueHere(state) {
@@ -554,7 +636,7 @@ export function canContinueHere(state) {
554
636
 
555
637
  export function operationLabel(operation) {
556
638
  if (!operation) return '';
557
- const labels = { discover: 'Refreshing chats…', attach: 'Opening chat…', resume: 'Continuing here…', branch: 'Starting continuation…', reduce: 'Reducing context and verifying reversibility…', terminal: 'Preparing terminal handoff…', export: 'Exporting losslessly…', refresh: 'Retrying…' };
639
+ const labels = { discover: 'Refreshing chats…', observe: 'Loading recent messages…', attach: 'Opening chat…', resume: 'Continuing here…', branch: 'Starting continuation…', reduce: 'Reducing context and verifying reversibility…', terminal: 'Preparing terminal handoff…', export: 'Exporting losslessly…', refresh: 'Retrying…' };
558
640
  return labels[operation] ?? `${operation.replaceAll('_', ' ')}…`;
559
641
  }
560
642