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

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/embed.mjs CHANGED
@@ -67,6 +67,11 @@ var MODES = /* @__PURE__ */ new Set(["none", "control", "mirror"]);
67
67
  var STRATEGIES = /* @__PURE__ */ new Set(["start", "resume", "attach", "branch", "reduce"]);
68
68
  var STARTUP = /* @__PURE__ */ new Set(["connecting", "starting", "discovering", "ready"]);
69
69
  var FIDELITY = /* @__PURE__ */ new Set(["byte_lossless", "value_lossless", "semantic"]);
70
+ var TOOL_CATEGORIES = /* @__PURE__ */ new Set(["read", "search", "edit", "command", "test", "web", "agent", "plan", "other"]);
71
+ var TOOL_DETAILS = /* @__PURE__ */ new Set(["file", "matches", "diff", "terminal", "web", "agent", "plan", "fields"]);
72
+ var MAX_TOOL_FIELDS = 8;
73
+ var MAX_TOOL_FIELD_CHARS = 800;
74
+ var MAX_TOOL_PREVIEW_CHARS = 4e3;
70
75
  function record(value) {
71
76
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
72
77
  }
@@ -79,6 +84,132 @@ function number(value, fallback = 0) {
79
84
  function nullableNumber(value) {
80
85
  return typeof value === "number" && Number.isFinite(value) ? value : null;
81
86
  }
87
+ function boundedString(value, max) {
88
+ if (typeof value !== "string") return "";
89
+ return value.length <= max ? value : `${value.slice(0, max)}\u2026`;
90
+ }
91
+ function argumentObject(argumentsText) {
92
+ if (!argumentsText) return null;
93
+ try {
94
+ return record(JSON.parse(argumentsText));
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+ function firstString(source, keys) {
100
+ for (const key of keys) {
101
+ if (typeof source?.[key] === "string" && source[key].trim()) return source[key].trim();
102
+ }
103
+ return "";
104
+ }
105
+ function explicitNumber(sources, keys) {
106
+ for (const source of sources) {
107
+ for (const key of keys) {
108
+ const value = source?.[key];
109
+ const parsed = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
110
+ if (typeof parsed === "number" && Number.isFinite(parsed)) return parsed;
111
+ }
112
+ }
113
+ return null;
114
+ }
115
+ function toolDetail(category) {
116
+ return { read: "file", search: "matches", edit: "diff", command: "terminal", test: "terminal", web: "web", agent: "agent", plan: "plan" }[category] ?? "fields";
117
+ }
118
+ function usefulToolFields(args) {
119
+ if (!args) return [];
120
+ 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"]);
121
+ return Object.entries(args).flatMap(([key, value]) => {
122
+ if (hidden.has(key) || value === null || value === void 0) return [];
123
+ const rendered = typeof value === "string" ? value : JSON.stringify(value);
124
+ if (!rendered) return [];
125
+ return [{ label: key.replaceAll(/[_-]+/g, " ").replace(/^./, (letter) => letter.toLocaleUpperCase()), value: boundedString(rendered, MAX_TOOL_FIELD_CHARS) }];
126
+ }).slice(0, MAX_TOOL_FIELDS);
127
+ }
128
+ function planItems(args) {
129
+ const source = Array.isArray(args?.plan) ? args.plan : Array.isArray(args?.todos) ? args.todos : [];
130
+ return source.flatMap((item) => {
131
+ if (typeof item === "string" && item.trim()) return [{ label: boundedString(item.trim(), 300), status: "" }];
132
+ const value = record(item);
133
+ const label = firstString(value, ["step", "title", "content", "text"]);
134
+ if (!label) return [];
135
+ return [{ label: boundedString(label, 300), status: firstString(value, ["status"]) }];
136
+ }).slice(0, 12);
137
+ }
138
+ function editPreview(args, resultText) {
139
+ const direct = firstString(args, ["patch", "diff"]);
140
+ if (direct) return direct;
141
+ const oldText = firstString(args, ["old_string"]);
142
+ const newText = firstString(args, ["new_string"]);
143
+ if (oldText || newText) {
144
+ return [
145
+ ...oldText.split("\n").map((line) => `- ${line}`),
146
+ ...newText.split("\n").map((line) => `+ ${line}`)
147
+ ].join("\n");
148
+ }
149
+ return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? "") ? resultText : "";
150
+ }
151
+ function createToolPresentation(entry) {
152
+ const args = argumentObject(entry.arguments);
153
+ const category = toolCategory({ label: entry.label, arguments: entry.arguments });
154
+ const command = firstString(args, ["command", "cmd"]);
155
+ const path = firstString(args, ["file_path", "target_file", "path"]);
156
+ const query = firstString(args, ["query", "pattern"]);
157
+ const url = firstString(args, ["url"]);
158
+ const subject = firstString(args, ["description", "task", "prompt"]);
159
+ const target = path || command || query || url || subject || toolTarget(entry.arguments);
160
+ const previewSource = category === "edit" ? editPreview(args, entry.resultText) : entry.resultText ?? "";
161
+ const result = record(entry.resultContent);
162
+ const metadata = record(entry.metadata);
163
+ return {
164
+ category,
165
+ detail: toolDetail(category),
166
+ target: boundedString(target, 300),
167
+ command: boundedString(command, MAX_TOOL_FIELD_CHARS),
168
+ path: boundedString(path, MAX_TOOL_FIELD_CHARS),
169
+ query: boundedString(query, MAX_TOOL_FIELD_CHARS),
170
+ url: boundedString(url, MAX_TOOL_FIELD_CHARS),
171
+ subject: boundedString(subject, MAX_TOOL_FIELD_CHARS),
172
+ preview: boundedString(previewSource, MAX_TOOL_PREVIEW_CHARS),
173
+ fields: usefulToolFields(args),
174
+ items: planItems(args),
175
+ exitCode: explicitNumber([result, metadata], ["exit_code", "exitCode"]),
176
+ durationMs: explicitNumber([result, metadata], ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs"]),
177
+ additions: explicitNumber([result, metadata], ["additions", "lines_added"]),
178
+ deletions: explicitNumber([result, metadata], ["deletions", "lines_removed"]),
179
+ matches: explicitNumber([result, metadata], ["matches", "match_count", "result_count"])
180
+ };
181
+ }
182
+ function readToolPresentation(value, entry) {
183
+ const generated = createToolPresentation(entry);
184
+ const item = record(value);
185
+ if (!item) return generated;
186
+ const fields = Array.isArray(item.fields) ? item.fields.flatMap((raw) => {
187
+ const field = record(raw);
188
+ return field && typeof field.label === "string" && typeof field.value === "string" ? [{ label: boundedString(field.label, 80), value: boundedString(field.value, MAX_TOOL_FIELD_CHARS) }] : [];
189
+ }).slice(0, MAX_TOOL_FIELDS) : generated.fields;
190
+ const items = Array.isArray(item.items) ? item.items.flatMap((raw) => {
191
+ const planItem = record(raw);
192
+ return planItem && typeof planItem.label === "string" ? [{ label: boundedString(planItem.label, 300), status: boundedString(planItem.status, 40) }] : [];
193
+ }).slice(0, 12) : generated.items;
194
+ return {
195
+ category: TOOL_CATEGORIES.has(item.category) ? item.category : generated.category,
196
+ detail: TOOL_DETAILS.has(item.detail) ? item.detail : generated.detail,
197
+ target: boundedString(item.target, 300) || generated.target,
198
+ command: boundedString(item.command, MAX_TOOL_FIELD_CHARS) || generated.command,
199
+ path: boundedString(item.path, MAX_TOOL_FIELD_CHARS) || generated.path,
200
+ query: boundedString(item.query, MAX_TOOL_FIELD_CHARS) || generated.query,
201
+ url: boundedString(item.url, MAX_TOOL_FIELD_CHARS) || generated.url,
202
+ subject: boundedString(item.subject, MAX_TOOL_FIELD_CHARS) || generated.subject,
203
+ preview: boundedString(item.preview, MAX_TOOL_PREVIEW_CHARS) || generated.preview,
204
+ fields,
205
+ items,
206
+ exitCode: nullableNumber(item.exitCode) ?? generated.exitCode,
207
+ durationMs: nullableNumber(item.durationMs) ?? generated.durationMs,
208
+ additions: nullableNumber(item.additions) ?? generated.additions,
209
+ deletions: nullableNumber(item.deletions) ?? generated.deletions,
210
+ matches: nullableNumber(item.matches) ?? generated.matches
211
+ };
212
+ }
82
213
  function readTranscript(value) {
83
214
  if (!Array.isArray(value)) return [];
84
215
  const result = [];
@@ -96,6 +227,7 @@ function readTranscript(value) {
96
227
  if (typeof item[key] === "string") entry[key] = item[key];
97
228
  }
98
229
  if (["pending", "completed", "error"].includes(item.status)) entry.status = item.status;
230
+ if (item.role === "tool") entry.presentation = readToolPresentation(item.presentation, entry);
99
231
  if (typeof item.streaming === "boolean") entry.streaming = item.streaming;
100
232
  if (Array.isArray(item.context)) {
101
233
  entry.context = item.context.flatMap((raw) => {
@@ -303,6 +435,7 @@ function groupConversation(entries) {
303
435
  return blocks;
304
436
  }
305
437
  function toolCategory(entry) {
438
+ if (TOOL_CATEGORIES.has(entry.presentation?.category)) return entry.presentation.category;
306
439
  const name = entry.label?.toLocaleLowerCase() ?? "";
307
440
  if (/read|view|open_file|list_dir/.test(name)) return "read";
308
441
  if (/search|find|grep|glob/.test(name)) return "search";
@@ -310,7 +443,8 @@ function toolCategory(entry) {
310
443
  if (/test|typecheck|lint|build/.test(name)) return "test";
311
444
  if (/terminal|bash|shell|command|exec/.test(name)) return /\b(test|typecheck|lint|build)\b/i.test(entry.arguments ?? "") ? "test" : "command";
312
445
  if (/browser|web|fetch|url/.test(name)) return "web";
313
- if (/subagent|spawn|task/.test(name)) return "agent";
446
+ if (/update.?plan|todo|checklist/.test(name)) return "plan";
447
+ if (/subagent|spawn.?agent|delegate|^task$/.test(name)) return "agent";
314
448
  return "other";
315
449
  }
316
450
  function toolTarget(argumentsText) {
@@ -332,9 +466,12 @@ function compactToolTarget(target, workspace) {
332
466
  function activitySummary(entries) {
333
467
  const counts = /* @__PURE__ */ new Map();
334
468
  for (const entry of entries) counts.set(toolCategory(entry), (counts.get(toolCategory(entry)) ?? 0) + 1);
469
+ const failed = entries.filter((entry) => entry.status === "error").length;
470
+ const pending = entries.filter((entry) => entry.status === "pending").length;
471
+ if (pending) return `${entries.length} actions in progress`;
472
+ if (failed) return `${entries.length} actions \xB7 ${failed} failed`;
335
473
  if ([...counts.keys()].every((key) => key === "read" || key === "search")) return `Explored ${entries.length} ${entries.length === 1 ? "item" : "items"}`;
336
- const labels = { edit: "changed", test: "tests/builds", command: "commands", read: "reads", search: "searches", web: "web", agent: "agents", other: "other" };
337
- return `Activity \xB7 ${Object.entries(labels).flatMap(([key, label]) => counts.has(key) ? [`${counts.get(key)} ${label}`] : []).join(" \xB7 ")}`;
474
+ return `${entries.length} actions`;
338
475
  }
339
476
  function canContinueHere(state) {
340
477
  if (state.mode !== "mirror" || state.canSend) return false;
@@ -473,7 +610,7 @@ function Markdown({ value }) {
473
610
  }
474
611
 
475
612
  // src/conversation.jsx
476
- import { jsx as jsx3, jsxs as jsxs2 } from "preact/jsx-runtime";
613
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "preact/jsx-runtime";
477
614
  function LoadingStatus({ state, compact = false }) {
478
615
  const copy = {
479
616
  connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
@@ -521,6 +658,185 @@ function ContextDisclosure({ context }) {
521
658
  ] }, item.id ?? index)) })
522
659
  ] });
523
660
  }
661
+ var TOOL_ICONS = {
662
+ read: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
663
+ /* @__PURE__ */ jsx3("path", { d: "M5 2.75h7.25L15 5.5v7.75A1.75 1.75 0 0 1 13.25 15h-8.5A1.75 1.75 0 0 1 3 13.25v-8.5A2 2 0 0 1 5 2.75Z" }),
664
+ /* @__PURE__ */ jsx3("path", { d: "M12 2.9v3h2.85M6 9h6M6 12h4" })
665
+ ] }),
666
+ search: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
667
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "8", r: "4.5" }),
668
+ /* @__PURE__ */ jsx3("path", { d: "m11.5 11.5 3 3" })
669
+ ] }),
670
+ edit: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
671
+ /* @__PURE__ */ jsx3("path", { d: "m11.75 3.25 3 3-8.5 8.5-3.75.75.75-3.75 8.5-8.5Z" }),
672
+ /* @__PURE__ */ jsx3("path", { d: "m10 5 3 3" })
673
+ ] }),
674
+ command: () => /* @__PURE__ */ jsx3(Fragment, { children: /* @__PURE__ */ jsx3("path", { d: "m3 5 3 3-3 3M8 12h6" }) }),
675
+ test: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
676
+ /* @__PURE__ */ jsx3("path", { d: "M6 2.5v3L3 12a2 2 0 0 0 1.8 3h8.4a2 2 0 0 0 1.8-3l-3-6.5v-3M5 9h8" }),
677
+ /* @__PURE__ */ jsx3("path", { d: "M5 2.5h8" })
678
+ ] }),
679
+ web: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
680
+ /* @__PURE__ */ jsx3("circle", { cx: "9", cy: "9", r: "6.5" }),
681
+ /* @__PURE__ */ jsx3("path", { d: "M2.75 9h12.5M9 2.5c2 1.8 3 4 3 6.5s-1 4.7-3 6.5c-2-1.8-3-4-3-6.5s1-4.7 3-6.5Z" })
682
+ ] }),
683
+ agent: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
684
+ /* @__PURE__ */ jsx3("circle", { cx: "9", cy: "6", r: "2.5" }),
685
+ /* @__PURE__ */ jsx3("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
686
+ ] }),
687
+ 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" }) }),
688
+ other: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
689
+ /* @__PURE__ */ jsx3("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
690
+ /* @__PURE__ */ jsx3("circle", { cx: "9", cy: "9", r: "3.5" })
691
+ ] })
692
+ };
693
+ function ToolIcon({ category }) {
694
+ const Glyph = TOOL_ICONS[category] ?? TOOL_ICONS.other;
695
+ 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, {}) });
696
+ }
697
+ function toolAction(entry, category) {
698
+ const status = entry.status ?? "completed";
699
+ const actions = {
700
+ read: ["Reading", "Read", "Read failed"],
701
+ search: ["Searching", "Searched", "Search failed"],
702
+ edit: ["Editing", "Edited", "Edit failed"],
703
+ command: ["Running command", "Ran command", "Command failed"],
704
+ test: ["Running tests", "Ran tests", "Tests failed"],
705
+ web: ["Browsing", "Browsed", "Browser action failed"],
706
+ agent: ["Starting agent", "Started agent", "Agent failed"],
707
+ plan: ["Updating plan", "Updated plan", "Plan update failed"]
708
+ };
709
+ const position = status === "pending" ? 0 : status === "error" ? 2 : 1;
710
+ if (actions[category]) return actions[category][position];
711
+ const label = entry.label?.split(/__|\//).at(-1)?.replaceAll(/[_-]+/g, " ") || "Tool";
712
+ return status === "error" ? `${label} failed` : label;
713
+ }
714
+ function argumentLabel(key) {
715
+ const labels = { cmd: "Command", command: "Command", cwd: "Working directory", file_path: "File", target_file: "File", path: "Path", query: "Query", pattern: "Pattern", url: "URL" };
716
+ return labels[key] ?? key.replaceAll(/[_-]+/g, " ").replace(/^./, (letter) => letter.toLocaleUpperCase());
717
+ }
718
+ function argumentRows(argumentsText) {
719
+ if (!argumentsText) return [];
720
+ try {
721
+ const value = JSON.parse(argumentsText);
722
+ if (value && typeof value === "object" && !Array.isArray(value)) {
723
+ return Object.entries(value).map(([key, item]) => ({
724
+ key,
725
+ label: argumentLabel(key),
726
+ value: typeof item === "string" ? item : JSON.stringify(item, null, 2)
727
+ }));
728
+ }
729
+ } catch {
730
+ }
731
+ return [{ key: "arguments", label: "Details", value: argumentsText }];
732
+ }
733
+ function stripAnsi(value) {
734
+ 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, "") ?? "";
735
+ }
736
+ function formatDuration(value) {
737
+ if (value === null) return "";
738
+ if (value < 1e3) return `${Math.round(value)}ms`;
739
+ return `${(value / 1e3).toFixed(value < 1e4 ? 1 : 0)}s`;
740
+ }
741
+ function ToolMetrics({ presentation }) {
742
+ const metrics = [
743
+ presentation.matches === null ? "" : `${presentation.matches} ${presentation.matches === 1 ? "match" : "matches"}`,
744
+ presentation.additions === null ? "" : `+${presentation.additions}`,
745
+ presentation.deletions === null ? "" : `\u2212${presentation.deletions}`,
746
+ presentation.exitCode === null ? "" : `exit ${presentation.exitCode}`,
747
+ formatDuration(presentation.durationMs)
748
+ ].filter(Boolean);
749
+ return metrics.length ? /* @__PURE__ */ jsx3("div", { class: "scui-tool-metrics", children: metrics.map((metric) => /* @__PURE__ */ jsx3("span", { children: metric }, metric)) }) : null;
750
+ }
751
+ function LinePreview({ value, kind }) {
752
+ if (!value) return null;
753
+ return /* @__PURE__ */ jsx3("ol", { class: "scui-code-preview", "data-kind": kind, children: stripAnsi(value).split("\n").map((line, index) => {
754
+ const tone = kind === "diff" ? line.startsWith("+") && !line.startsWith("+++") ? "add" : line.startsWith("-") && !line.startsWith("---") ? "remove" : line.startsWith("@@") ? "hunk" : "plain" : "plain";
755
+ return /* @__PURE__ */ jsxs2("li", { "data-tone": tone, children: [
756
+ /* @__PURE__ */ jsx3("span", { children: index + 1 }),
757
+ /* @__PURE__ */ jsx3("code", { children: line || " " })
758
+ ] }, index);
759
+ }) });
760
+ }
761
+ function TerminalPreview({ presentation, pending, failed }) {
762
+ return /* @__PURE__ */ jsxs2("section", { class: "scui-terminal", children: [
763
+ /* @__PURE__ */ jsxs2("header", { children: [
764
+ /* @__PURE__ */ jsxs2("span", { "aria-hidden": "true", children: [
765
+ /* @__PURE__ */ jsx3("i", {}),
766
+ /* @__PURE__ */ jsx3("i", {}),
767
+ /* @__PURE__ */ jsx3("i", {})
768
+ ] }),
769
+ /* @__PURE__ */ jsx3("code", { children: presentation.command ? `$ ${presentation.command}` : "Terminal" })
770
+ ] }),
771
+ presentation.preview ? /* @__PURE__ */ jsx3("pre", { "data-error": failed, children: stripAnsi(presentation.preview) }) : pending ? /* @__PURE__ */ jsxs2("div", { class: "scui-terminal-wait", children: [
772
+ /* @__PURE__ */ jsx3("i", {}),
773
+ " Waiting for output"
774
+ ] }) : /* @__PURE__ */ jsx3("div", { class: "scui-terminal-empty", children: "No output" })
775
+ ] });
776
+ }
777
+ function SearchPreview({ presentation }) {
778
+ const lines = stripAnsi(presentation.preview).split("\n").filter(Boolean);
779
+ return /* @__PURE__ */ jsxs2("section", { class: "scui-search-preview", children: [
780
+ presentation.query ? /* @__PURE__ */ jsxs2("header", { children: [
781
+ /* @__PURE__ */ jsx3("span", { children: "Search" }),
782
+ /* @__PURE__ */ jsx3("code", { children: presentation.query })
783
+ ] }) : null,
784
+ lines.length ? /* @__PURE__ */ jsx3("ol", { children: lines.map((line, index) => {
785
+ const match = /^(.*?):(\d+)(?::(\d+))?:(.*)$/.exec(line);
786
+ return /* @__PURE__ */ jsx3("li", { children: match ? /* @__PURE__ */ jsxs2(Fragment, { children: [
787
+ /* @__PURE__ */ jsx3("code", { children: match[1] }),
788
+ /* @__PURE__ */ jsxs2("small", { children: [
789
+ match[2],
790
+ match[3] ? `:${match[3]}` : ""
791
+ ] }),
792
+ /* @__PURE__ */ jsx3("span", { children: match[4] })
793
+ ] }) : /* @__PURE__ */ jsx3("span", { children: line }) }, index);
794
+ }) }) : /* @__PURE__ */ jsx3("p", { children: "No textual results" })
795
+ ] });
796
+ }
797
+ function ToolPreview({ presentation, entry }) {
798
+ const pending = entry.status === "pending";
799
+ const failed = entry.status === "error";
800
+ if (presentation.detail === "terminal") return /* @__PURE__ */ jsx3(TerminalPreview, { presentation, pending, failed });
801
+ 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" });
802
+ 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" });
803
+ if (presentation.detail === "matches") return /* @__PURE__ */ jsx3(SearchPreview, { presentation });
804
+ if (presentation.detail === "web") return /* @__PURE__ */ jsxs2("section", { class: "scui-web-preview", children: [
805
+ presentation.url ? /* @__PURE__ */ jsx3("code", { children: presentation.url }) : null,
806
+ presentation.preview ? /* @__PURE__ */ jsx3("p", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx3("small", { children: pending ? "Waiting for the page" : "No page summary returned" })
807
+ ] });
808
+ if (presentation.detail === "agent") return /* @__PURE__ */ jsxs2("section", { class: "scui-agent-preview", children: [
809
+ presentation.subject ? /* @__PURE__ */ jsx3("p", { children: presentation.subject }) : null,
810
+ presentation.preview ? /* @__PURE__ */ jsx3("pre", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx3("small", { children: pending ? "Agent is working" : "No textual handoff returned" })
811
+ ] });
812
+ 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: [
813
+ /* @__PURE__ */ jsx3("i", { "aria-hidden": "true" }),
814
+ /* @__PURE__ */ jsx3("span", { children: item.label })
815
+ ] }, `${item.label}:${index}`)) });
816
+ return presentation.preview ? /* @__PURE__ */ jsx3("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
817
+ }
818
+ function TechnicalDetails({ entry }) {
819
+ if (!entry.arguments && !entry.resultText) return null;
820
+ return /* @__PURE__ */ jsxs2("details", { class: "scui-tool-technical", children: [
821
+ /* @__PURE__ */ jsx3("summary", { children: "Technical details" }),
822
+ /* @__PURE__ */ jsxs2("div", { children: [
823
+ entry.arguments ? /* @__PURE__ */ jsxs2("section", { children: [
824
+ /* @__PURE__ */ jsx3("strong", { children: "Native arguments" }),
825
+ /* @__PURE__ */ jsx3("dl", { children: argumentRows(entry.arguments).map((row) => /* @__PURE__ */ jsxs2("div", { children: [
826
+ /* @__PURE__ */ jsx3("dt", { children: row.label }),
827
+ /* @__PURE__ */ jsx3("dd", { children: /* @__PURE__ */ jsx3("pre", { children: row.value }) })
828
+ ] }, row.key)) })
829
+ ] }) : null,
830
+ entry.resultText ? /* @__PURE__ */ jsxs2("section", { children: [
831
+ /* @__PURE__ */ jsx3("strong", { children: "Native result" }),
832
+ /* @__PURE__ */ jsxs2("pre", { "data-error": entry.status === "error", children: [
833
+ entry.resultText,
834
+ entry.truncated ? "\n[truncated]" : ""
835
+ ] })
836
+ ] }) : null
837
+ ] })
838
+ ] });
839
+ }
524
840
  function TranscriptEntry({ entry, state, adapter }) {
525
841
  if (entry.role === "request") return /* @__PURE__ */ jsx3(RequestCard, { entry, adapter, canRespond: state.canRespond });
526
842
  if (entry.role === "reasoning") {
@@ -536,36 +852,35 @@ function TranscriptEntry({ entry, state, adapter }) {
536
852
  entry.truncated ? /* @__PURE__ */ jsx3("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null
537
853
  ] });
538
854
  }
539
- function ToolRow({ entry, workspace }) {
540
- const [open, setOpen] = useState2(entry.status === "pending");
541
- const detailId = useId();
542
- const target = compactToolTarget(toolTarget(entry.arguments), workspace);
543
- const hasDetail = Boolean(entry.arguments || entry.resultText);
544
- const category = toolCategory(entry);
545
- return /* @__PURE__ */ jsxs2("div", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, children: [
546
- /* @__PURE__ */ jsxs2("button", { type: "button", class: "scui-tool-head", disabled: !hasDetail, "aria-expanded": hasDetail ? open : void 0, "aria-controls": hasDetail ? detailId : void 0, onClick: () => hasDetail && setOpen((value) => !value), children: [
547
- /* @__PURE__ */ jsx3("span", { class: "scui-tool-glyph", "aria-hidden": "true", children: { read: "\u25A4", search: "\u2315", edit: "\u270E", command: ">_", test: "\u25C7", web: "\u25CE", agent: "\u2659", other: "\u25C6" }[category] }),
548
- /* @__PURE__ */ jsx3("strong", { children: entry.label?.split(/__|\//).at(-1)?.replaceAll("_", " ") || "Tool" }),
549
- target ? /* @__PURE__ */ jsx3("span", { class: "scui-tool-target", title: toolTarget(entry.arguments), children: target }) : null,
550
- /* @__PURE__ */ jsx3("span", { class: "scui-spacer" }),
551
- /* @__PURE__ */ jsx3("span", { "aria-label": entry.status ?? "completed", children: entry.status === "pending" ? "\u25CC" : entry.status === "error" ? "\xD7" : "\u2713" })
552
- ] }),
553
- hasDetail && open ? /* @__PURE__ */ jsxs2("div", { id: detailId, class: "scui-tool-detail", children: [
554
- entry.arguments ? /* @__PURE__ */ jsxs2("pre", { children: [
555
- /* @__PURE__ */ jsx3("b", { children: "Input" }),
556
- "\n",
557
- entry.arguments
558
- ] }) : null,
559
- entry.resultText ? /* @__PURE__ */ jsxs2("pre", { "data-error": entry.status === "error", children: [
560
- /* @__PURE__ */ jsx3("b", { children: "Output" }),
561
- "\n",
562
- entry.resultText,
563
- entry.truncated ? "\n[truncated]" : ""
564
- ] }) : null
565
- ] }) : null
855
+ function ToolRow({ entry, workspace, open = false }) {
856
+ const presentation = entry.presentation ?? createToolPresentation(entry);
857
+ const target = compactToolTarget(presentation.target, workspace);
858
+ const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
859
+ const category = presentation.category ?? toolCategory(entry);
860
+ const summary = /* @__PURE__ */ jsxs2(Fragment, { children: [
861
+ /* @__PURE__ */ jsx3(ToolIcon, { category }),
862
+ /* @__PURE__ */ jsx3("strong", { children: toolAction(entry, category) }),
863
+ target ? /* @__PURE__ */ jsx3("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
864
+ /* @__PURE__ */ jsx3("span", { class: "scui-spacer" }),
865
+ /* @__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" }),
866
+ hasDetail ? /* @__PURE__ */ jsx3("span", { class: "scui-tool-chevron", "aria-hidden": "true", children: "\u203A" }) : null
867
+ ] });
868
+ 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 }) });
869
+ return /* @__PURE__ */ jsxs2("details", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, open: open || entry.status === "pending", children: [
870
+ /* @__PURE__ */ jsx3("summary", { class: "scui-tool-head", children: summary }),
871
+ /* @__PURE__ */ jsxs2("div", { class: "scui-tool-detail", children: [
872
+ /* @__PURE__ */ jsx3(ToolMetrics, { presentation }),
873
+ /* @__PURE__ */ jsx3(ToolPreview, { presentation, entry }),
874
+ presentation.fields.length ? /* @__PURE__ */ jsx3("dl", { class: "scui-tool-fields", children: presentation.fields.map((field) => /* @__PURE__ */ jsxs2("div", { children: [
875
+ /* @__PURE__ */ jsx3("dt", { children: field.label }),
876
+ /* @__PURE__ */ jsx3("dd", { children: field.value })
877
+ ] }, field.label)) }) : null,
878
+ /* @__PURE__ */ jsx3(TechnicalDetails, { entry })
879
+ ] })
566
880
  ] });
567
881
  }
568
882
  function ActivityGroup({ entries, state }) {
883
+ if (entries.length === 1) return /* @__PURE__ */ jsx3("section", { class: "scui-activity", "data-single": "true", children: /* @__PURE__ */ jsx3(ToolRow, { entry: entries[0], workspace: state.workspace }) });
569
884
  const active = entries.some((entry) => entry.status === "pending");
570
885
  const [open, setOpen] = useState2(active);
571
886
  const id = useId();
@@ -578,7 +893,7 @@ function ActivityGroup({ entries, state }) {
578
893
  /* @__PURE__ */ jsx3("strong", { children: activitySummary(entries) }),
579
894
  /* @__PURE__ */ jsx3("span", { class: "scui-spacer" }),
580
895
  /* @__PURE__ */ jsxs2("small", { children: [
581
- entries.filter((entry) => entry.status === "completed").length,
896
+ entries.filter((entry) => entry.status !== "pending").length,
582
897
  "/",
583
898
  entries.length
584
899
  ] })
@@ -942,9 +1257,13 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
942
1257
  }
943
1258
  function NewChat({ state, adapter, onBack, onClose, onStarted, labels }) {
944
1259
  const startable = state.harnesses.filter((item) => item.startable);
1260
+ const startableKey = startable.map((item) => item.id).join("\0");
945
1261
  const [harness, setHarness] = useState4(startable[0]?.id ?? "");
946
1262
  const [draft, setDraft] = useState4("");
947
1263
  const [starting, setStarting] = useState4(false);
1264
+ useEffect4(() => {
1265
+ if (!startable.some((item) => item.id === harness)) setHarness(startable[0]?.id ?? "");
1266
+ }, [harness, startableKey]);
948
1267
  useEffect4(() => {
949
1268
  if (starting && (state.busy || state.transcript.length)) onStarted();
950
1269
  }, [onStarted, starting, state.busy, state.transcript.length]);
package/index.d.ts CHANGED
@@ -74,6 +74,28 @@ export interface TranscriptEntryModel {
74
74
  request?: TranscriptRequest;
75
75
  code?: string;
76
76
  context?: TranscriptContext[];
77
+ presentation?: ToolPresentationModel;
78
+ }
79
+
80
+ export type ToolCategory = 'read' | 'search' | 'edit' | 'command' | 'test' | 'web' | 'agent' | 'plan' | 'other';
81
+
82
+ export interface ToolPresentationModel {
83
+ category: ToolCategory;
84
+ detail: 'file' | 'matches' | 'diff' | 'terminal' | 'web' | 'agent' | 'plan' | 'fields';
85
+ target: string;
86
+ command: string;
87
+ path: string;
88
+ query: string;
89
+ url: string;
90
+ subject: string;
91
+ preview: string;
92
+ fields: Array<{ label: string; value: string }>;
93
+ items: Array<{ label: string; status: string }>;
94
+ exitCode: number | null;
95
+ durationMs: number | null;
96
+ additions: number | null;
97
+ deletions: number | null;
98
+ matches: number | null;
77
99
  }
78
100
 
79
101
  export interface TaskPlanItem {
@@ -240,6 +262,12 @@ export interface ActivityGroupProps {
240
262
  adapter: UiAdapter;
241
263
  }
242
264
 
265
+ export interface ToolRowProps {
266
+ entry: TranscriptEntryModel;
267
+ workspace?: string;
268
+ open?: boolean;
269
+ }
270
+
243
271
  export interface TaskPlanProps {
244
272
  plan: TaskPlanModel;
245
273
  value?: TaskPlanModel;
@@ -285,7 +313,8 @@ export function groupConversation(entries: readonly TranscriptEntryModel[]): Arr
285
313
  | { kind: 'entry'; id: string; entry: TranscriptEntryModel }
286
314
  | { kind: 'activity'; id: string; entries: TranscriptEntryModel[] }
287
315
  >;
288
- export function toolCategory(entry: Pick<TranscriptEntryModel, 'label' | 'arguments'>): 'read' | 'search' | 'edit' | 'command' | 'test' | 'web' | 'agent' | 'other';
316
+ export function toolCategory(entry: Pick<TranscriptEntryModel, 'label' | 'arguments' | 'presentation'>): ToolCategory;
317
+ export function createToolPresentation(entry: Pick<TranscriptEntryModel, 'label' | 'arguments' | 'resultText'> & { resultContent?: unknown; metadata?: Record<string, string> }): ToolPresentationModel;
289
318
  export function toolTarget(argumentsText?: string): string;
290
319
  export function compactToolTarget(target: string, workspace: string): string;
291
320
  export function activitySummary(entries: readonly TranscriptEntryModel[]): string;
@@ -300,6 +329,7 @@ export function LoadingStatus(props: { state: SupercodeUiState; compact?: boolea
300
329
  export function RequestCard(props: { entry: TranscriptEntryModel; adapter: UiAdapter; canRespond: boolean }): VNode | null;
301
330
  export function TranscriptEntry(props: { entry: TranscriptEntryModel; state: SupercodeUiState; adapter: UiAdapter }): VNode;
302
331
  export function ActivityGroup(props: { entries: TranscriptEntryModel[]; state: SupercodeUiState; adapter: UiAdapter }): VNode;
332
+ export function ToolRow(props: ToolRowProps): VNode;
303
333
  export function TaskPlan(props: { plan: TaskPlanModel }): VNode | null;
304
334
  export function SessionDetails(props: { semantics: SessionSemanticsModel }): VNode | null;
305
335
  export function Conversation(props: { state: SupercodeUiState; adapter: UiAdapter; components?: MessengerComponents; slots?: MessengerSlots; memoryKey?: string; pending?: string | null }): VNode;