@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/messenger.mjs CHANGED
@@ -64,6 +64,11 @@ var MODES = /* @__PURE__ */ new Set(["none", "control", "mirror"]);
64
64
  var STRATEGIES = /* @__PURE__ */ new Set(["start", "resume", "attach", "branch", "reduce"]);
65
65
  var STARTUP = /* @__PURE__ */ new Set(["connecting", "starting", "discovering", "ready"]);
66
66
  var FIDELITY = /* @__PURE__ */ new Set(["byte_lossless", "value_lossless", "semantic"]);
67
+ var TOOL_CATEGORIES = /* @__PURE__ */ new Set(["read", "search", "edit", "command", "test", "web", "agent", "plan", "other"]);
68
+ var TOOL_DETAILS = /* @__PURE__ */ new Set(["file", "matches", "diff", "terminal", "web", "agent", "plan", "fields"]);
69
+ var MAX_TOOL_FIELDS = 8;
70
+ var MAX_TOOL_FIELD_CHARS = 800;
71
+ var MAX_TOOL_PREVIEW_CHARS = 4e3;
67
72
  function record(value) {
68
73
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
69
74
  }
@@ -76,6 +81,206 @@ function number(value, fallback = 0) {
76
81
  function nullableNumber(value) {
77
82
  return typeof value === "number" && Number.isFinite(value) ? value : null;
78
83
  }
84
+ function boundedString(value, max) {
85
+ if (typeof value !== "string") return "";
86
+ return value.length <= max ? value : `${value.slice(0, max)}\u2026`;
87
+ }
88
+ function argumentValue(argumentsText) {
89
+ if (!argumentsText) return null;
90
+ try {
91
+ return JSON.parse(argumentsText);
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
96
+ function firstString(source, keys) {
97
+ for (const key of keys) {
98
+ if (typeof source?.[key] === "string" && source[key].trim()) return source[key].trim();
99
+ }
100
+ return "";
101
+ }
102
+ function decodedLiteral(value) {
103
+ if (!value) return "";
104
+ if (value.startsWith('"')) {
105
+ try {
106
+ return JSON.parse(value);
107
+ } catch {
108
+ return "";
109
+ }
110
+ }
111
+ return value.slice(1, -1).replaceAll("\\n", "\n").replaceAll("\\t", " ").replaceAll("\\r", "\r").replaceAll("\\`", "`").replaceAll("\\'", "'").replaceAll("\\\\", "\\");
112
+ }
113
+ function sourceString(source, keys) {
114
+ if (!source) return "";
115
+ const names = keys.join("|");
116
+ const match = new RegExp(`\\b(?:${names})\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
117
+ return decodedLiteral(match?.[1]);
118
+ }
119
+ function toolEnvelope(entry) {
120
+ const value = argumentValue(entry.arguments);
121
+ const source = typeof value === "string" ? value : "";
122
+ const tools = source ? [...new Set([...source.matchAll(/\btools\.([A-Za-z0-9_]+)/g)].map((match) => match[1]))].slice(0, 8) : [];
123
+ const name = tools.length === 1 ? tools[0] : entry.label ?? "tool";
124
+ return { args: record(value), source, tools, name };
125
+ }
126
+ function patchPath(source) {
127
+ return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]+)/.exec(source)?.[1]?.trim() ?? "";
128
+ }
129
+ function explicitNumber(sources, keys) {
130
+ for (const source of sources) {
131
+ for (const key of keys) {
132
+ const value = source?.[key];
133
+ const parsed = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
134
+ if (typeof parsed === "number" && Number.isFinite(parsed)) return parsed;
135
+ }
136
+ }
137
+ return null;
138
+ }
139
+ function toolDetail(category) {
140
+ return { read: "file", search: "matches", edit: "diff", command: "terminal", test: "terminal", web: "web", agent: "agent", plan: "plan" }[category] ?? "fields";
141
+ }
142
+ function usefulToolFields(args) {
143
+ if (!args) return [];
144
+ 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"]);
145
+ return Object.entries(args).flatMap(([key, value]) => {
146
+ if (hidden.has(key) || value === null || value === void 0) return [];
147
+ const rendered = typeof value === "string" ? value : JSON.stringify(value);
148
+ if (!rendered) return [];
149
+ return [{ label: key.replaceAll(/[_-]+/g, " ").replace(/^./, (letter) => letter.toLocaleUpperCase()), value: boundedString(rendered, MAX_TOOL_FIELD_CHARS) }];
150
+ }).slice(0, MAX_TOOL_FIELDS);
151
+ }
152
+ function planItems(args) {
153
+ const source = Array.isArray(args?.plan) ? args.plan : Array.isArray(args?.todos) ? args.todos : [];
154
+ return source.flatMap((item) => {
155
+ if (typeof item === "string" && item.trim()) return [{ label: boundedString(item.trim(), 300), status: "" }];
156
+ const value = record(item);
157
+ const label = firstString(value, ["step", "title", "content", "text"]);
158
+ if (!label) return [];
159
+ return [{ label: boundedString(label, 300), status: firstString(value, ["status"]) }];
160
+ }).slice(0, 12);
161
+ }
162
+ function editPreview(args, resultText, source) {
163
+ const direct = firstString(args, ["patch", "diff"]);
164
+ if (direct) return direct;
165
+ const oldText = firstString(args, ["old_string"]);
166
+ const newText = firstString(args, ["new_string"]);
167
+ if (oldText || newText) {
168
+ return [
169
+ ...oldText.split("\n").map((line) => `- ${line}`),
170
+ ...newText.split("\n").map((line) => `+ ${line}`)
171
+ ].join("\n");
172
+ }
173
+ const patch = /\*\*\* Begin Patch[\s\S]*?\*\*\* End Patch/.exec(source ?? "")?.[0];
174
+ if (patch) return patch;
175
+ return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? "") ? resultText : "";
176
+ }
177
+ function classifyTool(name, command) {
178
+ const normalized = name.toLocaleLowerCase();
179
+ if (/write_stdin|^wait$/.test(normalized)) return "command";
180
+ if (/update.?plan|todo|checklist|taskcreate|taskupdate/.test(normalized)) return "plan";
181
+ if (/search.?replace|edit|write|patch|replace|create_file|apply_patch/.test(normalized)) return "edit";
182
+ if (/read|view|open_file|list_dir/.test(normalized)) return "read";
183
+ if (/search|find|grep|glob|toolsearch/.test(normalized)) return "search";
184
+ if (/browser|web|fetch|url/.test(normalized)) return "web";
185
+ if (/agent|subagent|sendmessage|delegate|^task$/.test(normalized)) return "agent";
186
+ if (/test|typecheck|lint|build/.test(normalized)) return "test";
187
+ if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return /\b(test|typecheck|lint|build)\b/i.test(command) ? "test" : "command";
188
+ return "other";
189
+ }
190
+ function toolAction(status, category, name, tools) {
191
+ const position = status === "pending" ? 0 : status === "error" ? 2 : 1;
192
+ if (tools.length > 1) return [`Running ${tools.length} actions`, `Ran ${tools.length} actions`, `${tools.length} actions failed`][position];
193
+ const normalized = name.toLocaleLowerCase();
194
+ if (/sendmessage/.test(normalized)) return ["Messaging agent", "Messaged agent", "Agent message failed"][position];
195
+ if (/kill_command_or_subagent/.test(normalized)) return ["Stopping", "Stopped", "Stop failed"][position];
196
+ if (/get_command_or_subagent_output|write_stdin|^wait$/.test(normalized)) return ["Waiting for", "Checked", "Check failed"][position];
197
+ const actions = {
198
+ read: ["Reading", "Read", "Read failed"],
199
+ search: ["Searching", "Searched", "Search failed"],
200
+ edit: ["Editing", "Edited", "Edit failed"],
201
+ command: ["Running command", "Ran command", "Command failed"],
202
+ test: ["Running tests", "Ran tests", "Tests failed"],
203
+ web: ["Browsing", "Browsed", "Browser action failed"],
204
+ agent: ["Starting agent", "Started agent", "Agent failed"],
205
+ plan: ["Updating plan", "Updated plan", "Plan update failed"]
206
+ };
207
+ return actions[category]?.[position] ?? (status === "error" ? `${name} failed` : name.replaceAll(/[_-]+/g, " "));
208
+ }
209
+ function createToolPresentation(entry) {
210
+ const envelope = toolEnvelope(entry);
211
+ const args = envelope.args;
212
+ const command = firstString(args, ["command", "cmd"]) || sourceString(envelope.source, ["command", "cmd"]);
213
+ const category = classifyTool(envelope.name, command || envelope.source || entry.arguments || "");
214
+ const path = firstString(args, ["file_path", "target_file", "target_directory", "path"]) || sourceString(envelope.source, ["file_path", "target_file", "target_directory", "path"]) || patchPath(envelope.source);
215
+ const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.source, ["query", "pattern", "q"]);
216
+ const url = firstString(args, ["url"]) || sourceString(envelope.source, ["url", "ref_id"]);
217
+ const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.source, ["subject", "description", "summary", "task", "prompt"]);
218
+ 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" : "";
219
+ const items = planItems(args);
220
+ const taskId = firstString(args, ["taskId", "task_id"]);
221
+ const planTarget = category === "plan" ? items.length ? `${items.length} ${items.length === 1 ? "item" : "items"}` : taskId ? `task ${taskId}` : "" : "";
222
+ const target = path || command || query || url || subject || background || planTarget || (envelope.tools.length > 1 ? `${envelope.tools.length} coordinated actions` : toolTarget(entry.arguments));
223
+ const previewSource = category === "edit" ? editPreview(args, entry.resultText, envelope.source) : entry.resultText ?? "";
224
+ const result = record(entry.resultContent);
225
+ const metadata = record(entry.metadata);
226
+ const resultMetadata = record(result?.metadata);
227
+ return {
228
+ name: boundedString(envelope.name, 120),
229
+ action: boundedString(toolAction(entry.status ?? "completed", category, envelope.name, envelope.tools), 120),
230
+ category,
231
+ detail: toolDetail(category),
232
+ target: boundedString(target, 300),
233
+ command: boundedString(command, MAX_TOOL_FIELD_CHARS),
234
+ path: boundedString(path, MAX_TOOL_FIELD_CHARS),
235
+ query: boundedString(query, MAX_TOOL_FIELD_CHARS),
236
+ url: boundedString(url, MAX_TOOL_FIELD_CHARS),
237
+ subject: boundedString(subject, MAX_TOOL_FIELD_CHARS),
238
+ preview: boundedString(previewSource, MAX_TOOL_PREVIEW_CHARS),
239
+ fields: usefulToolFields(args),
240
+ items,
241
+ tools: envelope.tools,
242
+ exitCode: explicitNumber([result, resultMetadata, metadata], ["exit_code", "exitCode", "pi_bash_exit_code"]),
243
+ durationMs: explicitNumber([result, resultMetadata, metadata], ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs", "totalDurationMs"]),
244
+ additions: explicitNumber([result, resultMetadata, metadata], ["additions", "lines_added"]),
245
+ deletions: explicitNumber([result, resultMetadata, metadata], ["deletions", "lines_removed"]),
246
+ matches: explicitNumber([result, resultMetadata, metadata], ["matches", "match_count", "result_count"])
247
+ };
248
+ }
249
+ function readToolPresentation(value, entry) {
250
+ const generated = createToolPresentation(entry);
251
+ const item = record(value);
252
+ if (!item) return generated;
253
+ const fields = Array.isArray(item.fields) ? item.fields.flatMap((raw) => {
254
+ const field = record(raw);
255
+ return field && typeof field.label === "string" && typeof field.value === "string" ? [{ label: boundedString(field.label, 80), value: boundedString(field.value, MAX_TOOL_FIELD_CHARS) }] : [];
256
+ }).slice(0, MAX_TOOL_FIELDS) : generated.fields;
257
+ const items = Array.isArray(item.items) ? item.items.flatMap((raw) => {
258
+ const planItem = record(raw);
259
+ return planItem && typeof planItem.label === "string" ? [{ label: boundedString(planItem.label, 300), status: boundedString(planItem.status, 40) }] : [];
260
+ }).slice(0, 12) : generated.items;
261
+ const tools = Array.isArray(item.tools) ? item.tools.filter((tool) => typeof tool === "string").map((tool) => boundedString(tool, 120)).slice(0, 8) : generated.tools;
262
+ return {
263
+ name: boundedString(item.name, 120) || generated.name,
264
+ action: boundedString(item.action, 120) || generated.action,
265
+ category: TOOL_CATEGORIES.has(item.category) ? item.category : generated.category,
266
+ detail: TOOL_DETAILS.has(item.detail) ? item.detail : generated.detail,
267
+ target: boundedString(item.target, 300) || generated.target,
268
+ command: boundedString(item.command, MAX_TOOL_FIELD_CHARS) || generated.command,
269
+ path: boundedString(item.path, MAX_TOOL_FIELD_CHARS) || generated.path,
270
+ query: boundedString(item.query, MAX_TOOL_FIELD_CHARS) || generated.query,
271
+ url: boundedString(item.url, MAX_TOOL_FIELD_CHARS) || generated.url,
272
+ subject: boundedString(item.subject, MAX_TOOL_FIELD_CHARS) || generated.subject,
273
+ preview: boundedString(item.preview, MAX_TOOL_PREVIEW_CHARS) || generated.preview,
274
+ fields,
275
+ items,
276
+ tools,
277
+ exitCode: nullableNumber(item.exitCode) ?? generated.exitCode,
278
+ durationMs: nullableNumber(item.durationMs) ?? generated.durationMs,
279
+ additions: nullableNumber(item.additions) ?? generated.additions,
280
+ deletions: nullableNumber(item.deletions) ?? generated.deletions,
281
+ matches: nullableNumber(item.matches) ?? generated.matches
282
+ };
283
+ }
79
284
  function readTranscript(value) {
80
285
  if (!Array.isArray(value)) return [];
81
286
  const result = [];
@@ -93,6 +298,7 @@ function readTranscript(value) {
93
298
  if (typeof item[key] === "string") entry[key] = item[key];
94
299
  }
95
300
  if (["pending", "completed", "error"].includes(item.status)) entry.status = item.status;
301
+ if (item.role === "tool") entry.presentation = readToolPresentation(item.presentation, entry);
96
302
  if (typeof item.streaming === "boolean") entry.streaming = item.streaming;
97
303
  if (Array.isArray(item.context)) {
98
304
  entry.context = item.context.flatMap((raw) => {
@@ -300,15 +506,10 @@ function groupConversation(entries) {
300
506
  return blocks;
301
507
  }
302
508
  function toolCategory(entry) {
303
- const name = entry.label?.toLocaleLowerCase() ?? "";
304
- if (/read|view|open_file|list_dir/.test(name)) return "read";
305
- if (/search|find|grep|glob/.test(name)) return "search";
306
- if (/edit|write|patch|replace|create_file/.test(name)) return "edit";
307
- if (/test|typecheck|lint|build/.test(name)) return "test";
308
- if (/terminal|bash|shell|command|exec/.test(name)) return /\b(test|typecheck|lint|build)\b/i.test(entry.arguments ?? "") ? "test" : "command";
309
- if (/browser|web|fetch|url/.test(name)) return "web";
310
- if (/subagent|spawn|task/.test(name)) return "agent";
311
- return "other";
509
+ if (TOOL_CATEGORIES.has(entry.presentation?.category)) return entry.presentation.category;
510
+ const envelope = toolEnvelope(entry);
511
+ const command = firstString(envelope.args, ["command", "cmd"]) || sourceString(envelope.source, ["command", "cmd"]);
512
+ return classifyTool(envelope.name, command || envelope.source || entry.arguments || "");
312
513
  }
313
514
  function toolTarget(argumentsText) {
314
515
  if (!argumentsText) return "";
@@ -334,7 +535,8 @@ function activitySummary(entries) {
334
535
  if (pending) return `${entries.length} actions in progress`;
335
536
  if (failed) return `${entries.length} actions \xB7 ${failed} failed`;
336
537
  if ([...counts.keys()].every((key) => key === "read" || key === "search")) return `Explored ${entries.length} ${entries.length === 1 ? "item" : "items"}`;
337
- return `${entries.length} actions`;
538
+ 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"] };
539
+ 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 ");
338
540
  }
339
541
  function canContinueHere(state) {
340
542
  if (state.mode !== "mirror" || state.canSend) return false;
@@ -547,6 +749,7 @@ var TOOL_ICONS = {
547
749
  /* @__PURE__ */ jsx3("circle", { cx: "9", cy: "6", r: "2.5" }),
548
750
  /* @__PURE__ */ jsx3("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
549
751
  ] }),
752
+ plan: () => /* @__PURE__ */ jsx3(Fragment, { children: /* @__PURE__ */ jsx3("path", { d: "m3 5 1 1 2-2M3 9l1 1 2-2M3 13l1 1 2-2M8 5h7M8 9h7M8 13h7" }) }),
550
753
  other: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
551
754
  /* @__PURE__ */ jsx3("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
552
755
  /* @__PURE__ */ jsx3("circle", { cx: "9", cy: "9", r: "3.5" })
@@ -556,7 +759,8 @@ function ToolIcon({ category }) {
556
759
  const Glyph = TOOL_ICONS[category] ?? TOOL_ICONS.other;
557
760
  return /* @__PURE__ */ jsx3("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__ */ jsx3(Glyph, {}) });
558
761
  }
559
- function toolAction(entry, category) {
762
+ function toolAction2(entry, category, presentation) {
763
+ if (presentation?.action) return presentation.action;
560
764
  const status = entry.status ?? "completed";
561
765
  const actions = {
562
766
  read: ["Reading", "Read", "Read failed"],
@@ -565,7 +769,8 @@ function toolAction(entry, category) {
565
769
  command: ["Running command", "Ran command", "Command failed"],
566
770
  test: ["Running tests", "Ran tests", "Tests failed"],
567
771
  web: ["Browsing", "Browsed", "Browser action failed"],
568
- agent: ["Starting agent", "Started agent", "Agent failed"]
772
+ agent: ["Starting agent", "Started agent", "Agent failed"],
773
+ plan: ["Updating plan", "Updated plan", "Plan update failed"]
569
774
  };
570
775
  const position = status === "pending" ? 0 : status === "error" ? 2 : 1;
571
776
  if (actions[category]) return actions[category][position];
@@ -591,6 +796,138 @@ function argumentRows(argumentsText) {
591
796
  }
592
797
  return [{ key: "arguments", label: "Details", value: argumentsText }];
593
798
  }
799
+ function stripAnsi(value) {
800
+ 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, "") ?? "";
801
+ }
802
+ function formatDuration(value) {
803
+ if (value === null) return "";
804
+ if (value < 1e3) return `${Math.round(value)}ms`;
805
+ return `${(value / 1e3).toFixed(value < 1e4 ? 1 : 0)}s`;
806
+ }
807
+ function ToolMetrics({ presentation }) {
808
+ const metrics = [
809
+ presentation.matches === null ? "" : `${presentation.matches} ${presentation.matches === 1 ? "match" : "matches"}`,
810
+ presentation.additions === null ? "" : `+${presentation.additions}`,
811
+ presentation.deletions === null ? "" : `\u2212${presentation.deletions}`,
812
+ presentation.exitCode === null ? "" : `exit ${presentation.exitCode}`,
813
+ formatDuration(presentation.durationMs)
814
+ ].filter(Boolean);
815
+ return metrics.length ? /* @__PURE__ */ jsx3("div", { class: "scui-tool-metrics", children: metrics.map((metric) => /* @__PURE__ */ jsx3("span", { children: metric }, metric)) }) : null;
816
+ }
817
+ function PendingElapsed({ now }) {
818
+ const clock = now ?? Date.now;
819
+ const started = useRef2(clock());
820
+ const [elapsed, setElapsed] = useState2(0);
821
+ useEffect2(() => {
822
+ const timer = setInterval(() => setElapsed(Math.max(0, clock() - started.current)), 1e3);
823
+ return () => clearInterval(timer);
824
+ }, [clock]);
825
+ return /* @__PURE__ */ jsx3("small", { class: "scui-tool-elapsed", children: elapsed < 1e3 ? "now" : formatDuration(elapsed) });
826
+ }
827
+ function LinePreview({ value, kind }) {
828
+ if (!value) return null;
829
+ return /* @__PURE__ */ jsx3("ol", { class: "scui-code-preview", "data-kind": kind, children: stripAnsi(value).split("\n").map((line, index) => {
830
+ const tone = kind === "diff" ? line.startsWith("+") && !line.startsWith("+++") ? "add" : line.startsWith("-") && !line.startsWith("---") ? "remove" : line.startsWith("@@") ? "hunk" : "plain" : "plain";
831
+ return /* @__PURE__ */ jsxs2("li", { "data-tone": tone, children: [
832
+ /* @__PURE__ */ jsx3("span", { children: index + 1 }),
833
+ /* @__PURE__ */ jsx3("code", { children: line || " " })
834
+ ] }, index);
835
+ }) });
836
+ }
837
+ function TerminalPreview({ presentation, pending, failed }) {
838
+ return /* @__PURE__ */ jsxs2("section", { class: "scui-terminal", children: [
839
+ /* @__PURE__ */ jsxs2("header", { children: [
840
+ /* @__PURE__ */ jsxs2("span", { "aria-hidden": "true", children: [
841
+ /* @__PURE__ */ jsx3("i", {}),
842
+ /* @__PURE__ */ jsx3("i", {}),
843
+ /* @__PURE__ */ jsx3("i", {})
844
+ ] }),
845
+ /* @__PURE__ */ jsx3("code", { children: presentation.command ? `$ ${presentation.command}` : "Terminal" })
846
+ ] }),
847
+ presentation.preview ? /* @__PURE__ */ jsx3("pre", { "data-error": failed, children: stripAnsi(presentation.preview) }) : pending ? /* @__PURE__ */ jsxs2("div", { class: "scui-terminal-wait", children: [
848
+ /* @__PURE__ */ jsx3("i", {}),
849
+ " Waiting for output"
850
+ ] }) : /* @__PURE__ */ jsx3("div", { class: "scui-terminal-empty", children: "No output" })
851
+ ] });
852
+ }
853
+ function SearchPreview({ presentation }) {
854
+ const lines = stripAnsi(presentation.preview).split("\n").filter(Boolean);
855
+ return /* @__PURE__ */ jsxs2("section", { class: "scui-search-preview", children: [
856
+ presentation.query ? /* @__PURE__ */ jsxs2("header", { children: [
857
+ /* @__PURE__ */ jsx3("span", { children: "Search" }),
858
+ /* @__PURE__ */ jsx3("code", { children: presentation.query })
859
+ ] }) : null,
860
+ lines.length ? /* @__PURE__ */ jsx3("ol", { children: lines.map((line, index) => {
861
+ const match = /^(.*?):(\d+)(?::(\d+))?:(.*)$/.exec(line);
862
+ return /* @__PURE__ */ jsx3("li", { children: match ? /* @__PURE__ */ jsxs2(Fragment, { children: [
863
+ /* @__PURE__ */ jsx3("code", { children: match[1] }),
864
+ /* @__PURE__ */ jsxs2("small", { children: [
865
+ match[2],
866
+ match[3] ? `:${match[3]}` : ""
867
+ ] }),
868
+ /* @__PURE__ */ jsx3("span", { children: match[4] })
869
+ ] }) : /* @__PURE__ */ jsx3("span", { children: line }) }, index);
870
+ }) }) : /* @__PURE__ */ jsx3("p", { children: "No textual results" })
871
+ ] });
872
+ }
873
+ function ToolPreview({ presentation, entry }) {
874
+ const pending = entry.status === "pending";
875
+ const failed = entry.status === "error";
876
+ if (presentation.detail === "terminal") return /* @__PURE__ */ jsx3(TerminalPreview, { presentation, pending, failed });
877
+ if (presentation.detail === "diff") return presentation.preview ? /* @__PURE__ */ jsx3(LinePreview, { value: presentation.preview, kind: "diff" }) : /* @__PURE__ */ jsx3("div", { class: "scui-tool-empty", children: "Edit completed without a textual diff" });
878
+ if (presentation.detail === "file") return presentation.preview ? /* @__PURE__ */ jsx3(LinePreview, { value: presentation.preview, kind: "file" }) : /* @__PURE__ */ jsx3("div", { class: "scui-tool-empty", children: "File contents were not included in this event" });
879
+ if (presentation.detail === "matches") return /* @__PURE__ */ jsx3(SearchPreview, { presentation });
880
+ if (presentation.detail === "web") return /* @__PURE__ */ jsxs2("section", { class: "scui-web-preview", children: [
881
+ presentation.url ? /* @__PURE__ */ jsx3("code", { children: presentation.url }) : null,
882
+ presentation.preview ? /* @__PURE__ */ jsx3("p", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx3("small", { children: pending ? "Waiting for the page" : "No page summary returned" })
883
+ ] });
884
+ if (presentation.detail === "agent") return /* @__PURE__ */ jsx3("section", { class: "scui-agent-preview", children: presentation.preview ? /* @__PURE__ */ jsx3("pre", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx3("small", { children: pending ? "Agent is working" : "No textual handoff returned" }) });
885
+ if (presentation.detail === "plan") return /* @__PURE__ */ jsx3("ol", { class: "scui-plan-preview", children: presentation.items?.map((item, index) => /* @__PURE__ */ jsxs2("li", { "data-status": item.status, children: [
886
+ /* @__PURE__ */ jsx3("i", { "aria-hidden": "true" }),
887
+ /* @__PURE__ */ jsx3("span", { children: item.label })
888
+ ] }, `${item.label}:${index}`)) });
889
+ return presentation.preview ? /* @__PURE__ */ jsx3("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
890
+ }
891
+ function ToolActions({ presentation, adapter }) {
892
+ const [copied, setCopied] = useState2(false);
893
+ const reset = useRef2(null);
894
+ useEffect2(() => () => clearTimeout(reset.current), []);
895
+ if (!adapter?.copyText) return null;
896
+ 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;
897
+ const copy = async () => {
898
+ await adapter.copyText(action[1]);
899
+ setCopied(true);
900
+ clearTimeout(reset.current);
901
+ reset.current = setTimeout(() => setCopied(false), 1500);
902
+ };
903
+ return action ? /* @__PURE__ */ jsx3("div", { class: "scui-tool-actions", children: /* @__PURE__ */ jsx3("button", { type: "button", onClick: copy, children: copied ? "Copied" : action[0] }) }) : null;
904
+ }
905
+ function ToolStack({ tools }) {
906
+ if (tools.length < 2) return null;
907
+ return /* @__PURE__ */ jsx3("div", { class: "scui-tool-stack", "aria-label": "Coordinated tools", children: tools.map((tool) => /* @__PURE__ */ jsx3("span", { children: tool.replaceAll("__", " \xB7 ").replaceAll("_", " ") }, tool)) });
908
+ }
909
+ function TechnicalDetails({ entry }) {
910
+ if (!entry.arguments && !entry.resultText) return null;
911
+ return /* @__PURE__ */ jsxs2("details", { class: "scui-tool-technical", children: [
912
+ /* @__PURE__ */ jsx3("summary", { children: "Technical details" }),
913
+ /* @__PURE__ */ jsxs2("div", { children: [
914
+ entry.arguments ? /* @__PURE__ */ jsxs2("section", { children: [
915
+ /* @__PURE__ */ jsx3("strong", { children: "Native arguments" }),
916
+ /* @__PURE__ */ jsx3("dl", { children: argumentRows(entry.arguments).map((row) => /* @__PURE__ */ jsxs2("div", { children: [
917
+ /* @__PURE__ */ jsx3("dt", { children: row.label }),
918
+ /* @__PURE__ */ jsx3("dd", { children: /* @__PURE__ */ jsx3("pre", { children: row.value }) })
919
+ ] }, row.key)) })
920
+ ] }) : null,
921
+ entry.resultText ? /* @__PURE__ */ jsxs2("section", { children: [
922
+ /* @__PURE__ */ jsx3("strong", { children: "Native result" }),
923
+ /* @__PURE__ */ jsxs2("pre", { "data-error": entry.status === "error", children: [
924
+ entry.resultText,
925
+ entry.truncated ? "\n[truncated]" : ""
926
+ ] })
927
+ ] }) : null
928
+ ] })
929
+ ] });
930
+ }
594
931
  function TranscriptEntry({ entry, state, adapter }) {
595
932
  if (entry.role === "request") return /* @__PURE__ */ jsx3(RequestCard, { entry, adapter, canRespond: state.canRespond });
596
933
  if (entry.role === "reasoning") {
@@ -606,38 +943,42 @@ function TranscriptEntry({ entry, state, adapter }) {
606
943
  entry.truncated ? /* @__PURE__ */ jsx3("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null
607
944
  ] });
608
945
  }
609
- function ToolRow({ entry, workspace }) {
610
- const target = compactToolTarget(toolTarget(entry.arguments), workspace);
611
- const hasDetail = Boolean(entry.arguments || entry.resultText);
612
- const category = toolCategory(entry);
946
+ function ToolRow({ entry, workspace, open = false, adapter }) {
947
+ const presentation = entry.presentation ?? createToolPresentation(entry);
948
+ const [expanded, setExpanded] = useState2(open || entry.status === "pending");
949
+ useEffect2(() => {
950
+ if (entry.status === "pending") setExpanded(true);
951
+ }, [entry.status]);
952
+ const target = compactToolTarget(presentation.target, workspace);
953
+ const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
954
+ const category = presentation.category ?? toolCategory(entry);
613
955
  const summary = /* @__PURE__ */ jsxs2(Fragment, { children: [
614
956
  /* @__PURE__ */ jsx3(ToolIcon, { category }),
615
- /* @__PURE__ */ jsx3("strong", { children: toolAction(entry, category) }),
616
- target ? /* @__PURE__ */ jsx3("code", { class: "scui-tool-target", title: toolTarget(entry.arguments), children: target }) : null,
957
+ /* @__PURE__ */ jsx3("strong", { children: toolAction2(entry, category, presentation) }),
958
+ target ? /* @__PURE__ */ jsx3("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
617
959
  /* @__PURE__ */ jsx3("span", { class: "scui-spacer" }),
960
+ entry.status === "pending" ? /* @__PURE__ */ jsx3(PendingElapsed, { now: adapter?.now }) : null,
618
961
  /* @__PURE__ */ jsx3("span", { class: "scui-tool-status", role: "status", "data-status": entry.status ?? "completed", "aria-label": entry.status ?? "completed", children: entry.status === "pending" ? /* @__PURE__ */ jsx3("i", {}) : entry.status === "error" ? "\xD7" : "\u2713" }),
619
962
  hasDetail ? /* @__PURE__ */ jsx3("span", { class: "scui-tool-chevron", "aria-hidden": "true", children: "\u203A" }) : null
620
963
  ] });
621
964
  if (!hasDetail) return /* @__PURE__ */ jsx3("div", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, children: /* @__PURE__ */ jsx3("div", { class: "scui-tool-head", children: summary }) });
622
- return /* @__PURE__ */ jsxs2("details", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, children: [
965
+ return /* @__PURE__ */ jsxs2("details", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, open: expanded, onToggle: (event) => setExpanded(event.currentTarget.open), children: [
623
966
  /* @__PURE__ */ jsx3("summary", { class: "scui-tool-head", children: summary }),
624
967
  /* @__PURE__ */ jsxs2("div", { class: "scui-tool-detail", children: [
625
- entry.arguments ? /* @__PURE__ */ jsx3("dl", { children: argumentRows(entry.arguments).map((row) => /* @__PURE__ */ jsxs2("div", { children: [
626
- /* @__PURE__ */ jsx3("dt", { children: row.label }),
627
- /* @__PURE__ */ jsx3("dd", { children: /* @__PURE__ */ jsx3("pre", { children: row.value }) })
628
- ] }, row.key)) }) : null,
629
- entry.resultText ? /* @__PURE__ */ jsxs2("section", { children: [
630
- /* @__PURE__ */ jsx3("strong", { children: entry.status === "error" ? "Error" : "Result" }),
631
- /* @__PURE__ */ jsxs2("pre", { "data-error": entry.status === "error", children: [
632
- entry.resultText,
633
- entry.truncated ? "\n[truncated]" : ""
634
- ] })
635
- ] }) : null
968
+ /* @__PURE__ */ jsx3(ToolMetrics, { presentation }),
969
+ /* @__PURE__ */ jsx3(ToolStack, { tools: presentation.tools ?? [] }),
970
+ /* @__PURE__ */ jsx3(ToolPreview, { presentation, entry }),
971
+ presentation.fields.length ? /* @__PURE__ */ jsx3("dl", { class: "scui-tool-fields", children: presentation.fields.map((field) => /* @__PURE__ */ jsxs2("div", { children: [
972
+ /* @__PURE__ */ jsx3("dt", { children: field.label }),
973
+ /* @__PURE__ */ jsx3("dd", { children: field.value })
974
+ ] }, field.label)) }) : null,
975
+ /* @__PURE__ */ jsx3(ToolActions, { presentation, adapter }),
976
+ /* @__PURE__ */ jsx3(TechnicalDetails, { entry })
636
977
  ] })
637
978
  ] });
638
979
  }
639
- function ActivityGroup({ entries, state }) {
640
- if (entries.length === 1) return /* @__PURE__ */ jsx3("section", { class: "scui-activity", "data-single": "true", children: /* @__PURE__ */ jsx3(ToolRow, { entry: entries[0], workspace: state.workspace }) });
980
+ function ActivityGroup({ entries, state, adapter }) {
981
+ if (entries.length === 1) return /* @__PURE__ */ jsx3("section", { class: "scui-activity", "data-single": "true", children: /* @__PURE__ */ jsx3(ToolRow, { entry: entries[0], workspace: state.workspace, adapter }) });
641
982
  const active = entries.some((entry) => entry.status === "pending");
642
983
  const [open, setOpen] = useState2(active);
643
984
  const id = useId();
@@ -655,7 +996,7 @@ function ActivityGroup({ entries, state }) {
655
996
  entries.length
656
997
  ] })
657
998
  ] }),
658
- open ? /* @__PURE__ */ jsx3("div", { id, children: entries.map((entry) => /* @__PURE__ */ jsx3(ToolRow, { entry, workspace: state.workspace }, entry.id)) }) : null
999
+ open ? /* @__PURE__ */ jsx3("div", { id, children: entries.map((entry) => /* @__PURE__ */ jsx3(ToolRow, { entry, workspace: state.workspace, adapter }, entry.id)) }) : null
659
1000
  ] });
660
1001
  }
661
1002
  function TaskPlan({ plan }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter-ai-dev/supercode-ui",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "description": "Composable default UI kit for Supercode-powered coding-agent experiences",
6
6
  "exports": {
package/styles.css CHANGED
@@ -137,8 +137,19 @@
137
137
  .scui-tool { min-width:0 }.scui-tool summary { list-style:none }.scui-tool summary::-webkit-details-marker { display:none }.scui-tool-head { cursor:default }.scui-tool > summary.scui-tool-head { cursor:pointer }.scui-tool-icon { flex:0 0 14px; width:14px; height:14px; color:var(--scui-muted) }.scui-tool-head strong { flex:none; color:var(--scui-fg); font-size:10.5px; font-weight:600; white-space:nowrap }
138
138
  .scui-tool-target { min-width:0; overflow:hidden; padding:0; background:transparent; color:var(--scui-muted); font:10px ui-monospace,SFMono-Regular,Menlo,monospace; text-overflow:ellipsis; white-space:nowrap }
139
139
  .scui-tool-status { display:grid; flex:0 0 13px; width:13px; height:13px; place-items:center; color:var(--scui-success); font-size:10px }.scui-tool-status[data-status="pending"] { color:var(--scui-accent) }.scui-tool-status[data-status="error"] { color:var(--scui-danger); font-size:13px }.scui-tool-status i { box-sizing:border-box; width:11px; height:11px; border:1.5px solid currentColor; border-right-color:transparent; border-radius:50%; animation:scui-spin .75s linear infinite }
140
+ .scui-tool-elapsed { flex:none; color:var(--scui-muted); font:9px ui-monospace,SFMono-Regular,Menlo,monospace }
140
141
  .scui-tool-chevron { flex:none; font-size:15px; line-height:1; transition:transform 120ms }.scui-tool[open] .scui-tool-chevron { transform:rotate(90deg) }
141
- .scui-tool-detail { display:grid; gap:7px; max-height:220px; margin:2px 0 6px 20px; padding:8px; overflow:auto; border-left:1px solid var(--scui-border); background:var(--scui-bg-raised); font-size:10px }.scui-tool-detail dl,.scui-tool-detail section,.scui-tool-detail dl > div { display:grid; gap:3px; margin:0 }.scui-tool-detail dt,.scui-tool-detail section > strong { color:var(--scui-muted); font-weight:600 }.scui-tool-detail dd { min-width:0; margin:0 }.scui-tool-detail pre { margin:0; overflow-wrap:anywhere; color:var(--scui-fg); font:10px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace; white-space:pre-wrap }.scui-tool-detail pre[data-error="true"] { color:var(--scui-danger) }
142
+ .scui-tool-detail { display:grid; gap:7px; max-height:240px; margin:2px 0 7px 20px; overflow:auto; border:1px solid var(--scui-border); border-radius:7px; background:var(--scui-bg-raised); font-size:10px }
143
+ .scui-tool-metrics { display:flex; flex-wrap:wrap; gap:4px; padding:7px 7px 0 }.scui-tool-metrics span { padding:2px 5px; border:1px solid var(--scui-border); border-radius:9px; color:var(--scui-muted); font:9px ui-monospace,SFMono-Regular,Menlo,monospace }.scui-tool[data-status="error"] .scui-tool-metrics span { border-color:color-mix(in srgb,var(--scui-danger) 30%,var(--scui-border)) }
144
+ .scui-tool-stack { display:flex; flex-wrap:wrap; gap:4px; padding:7px 8px 0 }.scui-tool-stack span { padding:2px 5px; border-radius:4px; background:var(--scui-fill); color:var(--scui-muted); font:9px ui-monospace,SFMono-Regular,Menlo,monospace }
145
+ .scui-terminal { min-width:0; overflow:hidden; background:color-mix(in srgb,var(--scui-fg) 7%,var(--scui-bg)); color:var(--scui-fg) }.scui-terminal header { display:flex; align-items:center; gap:7px; min-height:29px; padding:0 8px; border-bottom:1px solid var(--scui-border) }.scui-terminal header > span { display:flex; gap:3px }.scui-terminal header i { width:5px; height:5px; border-radius:50%; background:var(--scui-border-strong) }.scui-terminal header code { min-width:0; overflow:hidden; color:var(--scui-muted); font:9.5px ui-monospace,SFMono-Regular,Menlo,monospace; text-overflow:ellipsis; white-space:nowrap }.scui-terminal pre,.scui-tool-output,.scui-agent-preview pre { max-height:150px; margin:0; padding:8px; overflow:auto; overflow-wrap:anywhere; color:var(--scui-fg); font:10px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; white-space:pre-wrap }.scui-terminal pre[data-error="true"],.scui-tool-output[data-error="true"] { color:var(--scui-danger) }.scui-terminal-wait,.scui-terminal-empty { display:flex; align-items:center; gap:6px; padding:9px; color:var(--scui-muted) }.scui-terminal-wait i { width:5px; height:5px; border-radius:50%; background:var(--scui-accent); animation:scui-breathe 1.2s ease-in-out infinite }
146
+ .scui-code-preview { margin:0; padding:5px 0; overflow:auto; counter-reset:line; color:var(--scui-fg); font:9.5px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; list-style:none }.scui-code-preview li { display:grid; grid-template-columns:31px minmax(max-content,1fr); min-height:15px }.scui-code-preview li > span { padding-right:7px; color:var(--scui-muted); text-align:right; user-select:none }.scui-code-preview code { padding:0 8px; white-space:pre }.scui-code-preview li[data-tone="add"] { background:color-mix(in srgb,var(--scui-success) 11%,transparent) }.scui-code-preview li[data-tone="remove"] { background:color-mix(in srgb,var(--scui-danger) 10%,transparent) }.scui-code-preview li[data-tone="hunk"] { color:var(--scui-accent) }
147
+ .scui-search-preview header { display:flex; align-items:center; gap:6px; padding:7px 8px; border-bottom:1px solid var(--scui-border) }.scui-search-preview header span { color:var(--scui-muted) }.scui-search-preview header code { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }.scui-search-preview ol { display:grid; margin:0; padding:3px 0; list-style:none }.scui-search-preview li { display:grid; grid-template-columns:minmax(0,auto) auto minmax(70px,1fr); align-items:baseline; gap:5px; padding:3px 8px; border-bottom:1px solid color-mix(in srgb,var(--scui-border) 60%,transparent) }.scui-search-preview li:last-child { border:0 }.scui-search-preview li code { overflow:hidden; color:var(--scui-accent); text-overflow:ellipsis; white-space:nowrap }.scui-search-preview li small { color:var(--scui-muted) }.scui-search-preview li span { min-width:0; overflow:hidden; color:var(--scui-fg); text-overflow:ellipsis; white-space:nowrap }.scui-search-preview p { margin:0; padding:8px; color:var(--scui-muted) }
148
+ .scui-web-preview,.scui-agent-preview { display:grid; gap:6px; padding:8px }.scui-web-preview > code { overflow:hidden; color:var(--scui-accent); text-overflow:ellipsis; white-space:nowrap }.scui-web-preview p,.scui-agent-preview p { margin:0; color:var(--scui-fg); line-height:1.45 }.scui-web-preview small,.scui-agent-preview small,.scui-tool-empty { padding:8px; color:var(--scui-muted) }
149
+ .scui-plan-preview { display:grid; gap:5px; margin:0; padding:8px; list-style:none }.scui-plan-preview li { display:flex; align-items:flex-start; gap:6px; color:var(--scui-fg) }.scui-plan-preview li i { flex:0 0 8px; width:8px; height:8px; margin-top:3px; border:1px solid var(--scui-border-strong); border-radius:50% }.scui-plan-preview li[data-status="completed"] { color:var(--scui-muted); text-decoration:line-through }.scui-plan-preview li[data-status="completed"] i { border-color:var(--scui-success); background:var(--scui-success) }.scui-plan-preview li[data-status="in_progress"] i { border-color:var(--scui-accent); box-shadow:inset 0 0 0 2px var(--scui-bg-raised); background:var(--scui-accent) }
150
+ .scui-tool-fields { display:grid; gap:5px; margin:0; padding:0 8px 8px }.scui-tool-fields > div { display:grid; grid-template-columns:minmax(65px,auto) 1fr; gap:8px }.scui-tool-fields dt { color:var(--scui-muted) }.scui-tool-fields dd { min-width:0; margin:0; overflow-wrap:anywhere; color:var(--scui-fg) }
151
+ .scui-tool-actions { display:flex; gap:5px; padding:0 8px }.scui-tool-actions button { padding:3px 6px; border:1px solid var(--scui-border); border-radius:5px; background:transparent; color:var(--scui-muted); cursor:pointer; font:inherit; font-size:9.5px }.scui-tool-actions button:hover { border-color:var(--scui-border-strong); color:var(--scui-fg) }
152
+ .scui-tool-technical { margin:0 8px 8px; color:var(--scui-muted) }.scui-tool-technical > summary { cursor:pointer; font-size:9.5px }.scui-tool-technical > div { display:grid; gap:8px; margin-top:5px; padding:7px; border-left:1px solid var(--scui-border) }.scui-tool-technical section,.scui-tool-technical dl,.scui-tool-technical dl > div { display:grid; gap:3px; margin:0 }.scui-tool-technical strong,.scui-tool-technical dt { color:var(--scui-muted); font-weight:600 }.scui-tool-technical dd { min-width:0; margin:0 }.scui-tool-technical pre { max-height:110px; margin:0; overflow:auto; overflow-wrap:anywhere; color:var(--scui-fg); font:9.5px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace; white-space:pre-wrap }.scui-tool-technical pre[data-error="true"] { color:var(--scui-danger) }
142
153
  .scui-request { padding:9px; border:1px solid var(--scui-warning); border-radius:9px; background:color-mix(in srgb,var(--scui-warning) 8%,var(--scui-bg)) }.scui-request-actions { display:flex; flex-wrap:wrap; gap:5px }.scui-request-actions button { padding:5px 8px; border:1px solid var(--scui-border-strong); border-radius:6px; background:var(--scui-bg); cursor:pointer }
143
154
  .scui-request-done { color:var(--scui-muted); font-size:10.5px }
144
155
  .scui-plan > summary { display:flex; justify-content:space-between; padding:6px 8px; border:1px solid var(--scui-border); border-radius:7px; background:var(--scui-bg-raised) }.scui-plan ol { display:grid; gap:4px; box-sizing:border-box; max-height:140px; margin:5px 0 0; padding:7px 8px 7px 25px; overflow:auto; border:1px solid var(--scui-border); border-radius:7px }.scui-plan li[data-status="completed"] { color:var(--scui-fg); text-decoration:line-through }