@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/components.d.ts +1 -0
- package/components.mjs +352 -32
- package/controller.mjs +8 -2
- package/conversation.d.ts +2 -0
- package/conversation.mjs +318 -32
- package/core.d.ts +3 -0
- package/core.mjs +154 -3
- package/embed.mjs +351 -32
- package/index.d.ts +31 -1
- package/messenger.mjs +351 -32
- package/package.json +1 -1
- package/sessions.mjs +1 -1
- package/styles.css +17 -6
package/conversation.mjs
CHANGED
|
@@ -59,6 +59,108 @@ var EMPTY_UI_STATE = Object.freeze({
|
|
|
59
59
|
owned: null,
|
|
60
60
|
attachError: null
|
|
61
61
|
});
|
|
62
|
+
var TOOL_CATEGORIES = /* @__PURE__ */ new Set(["read", "search", "edit", "command", "test", "web", "agent", "plan", "other"]);
|
|
63
|
+
var MAX_TOOL_FIELDS = 8;
|
|
64
|
+
var MAX_TOOL_FIELD_CHARS = 800;
|
|
65
|
+
var MAX_TOOL_PREVIEW_CHARS = 4e3;
|
|
66
|
+
function record(value) {
|
|
67
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
68
|
+
}
|
|
69
|
+
function boundedString(value, max) {
|
|
70
|
+
if (typeof value !== "string") return "";
|
|
71
|
+
return value.length <= max ? value : `${value.slice(0, max)}\u2026`;
|
|
72
|
+
}
|
|
73
|
+
function argumentObject(argumentsText) {
|
|
74
|
+
if (!argumentsText) return null;
|
|
75
|
+
try {
|
|
76
|
+
return record(JSON.parse(argumentsText));
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function firstString(source, keys) {
|
|
82
|
+
for (const key of keys) {
|
|
83
|
+
if (typeof source?.[key] === "string" && source[key].trim()) return source[key].trim();
|
|
84
|
+
}
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
87
|
+
function explicitNumber(sources, keys) {
|
|
88
|
+
for (const source of sources) {
|
|
89
|
+
for (const key of keys) {
|
|
90
|
+
const value = source?.[key];
|
|
91
|
+
const parsed = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
|
|
92
|
+
if (typeof parsed === "number" && Number.isFinite(parsed)) return parsed;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
function toolDetail(category) {
|
|
98
|
+
return { read: "file", search: "matches", edit: "diff", command: "terminal", test: "terminal", web: "web", agent: "agent", plan: "plan" }[category] ?? "fields";
|
|
99
|
+
}
|
|
100
|
+
function usefulToolFields(args) {
|
|
101
|
+
if (!args) return [];
|
|
102
|
+
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"]);
|
|
103
|
+
return Object.entries(args).flatMap(([key, value]) => {
|
|
104
|
+
if (hidden.has(key) || value === null || value === void 0) return [];
|
|
105
|
+
const rendered = typeof value === "string" ? value : JSON.stringify(value);
|
|
106
|
+
if (!rendered) return [];
|
|
107
|
+
return [{ label: key.replaceAll(/[_-]+/g, " ").replace(/^./, (letter) => letter.toLocaleUpperCase()), value: boundedString(rendered, MAX_TOOL_FIELD_CHARS) }];
|
|
108
|
+
}).slice(0, MAX_TOOL_FIELDS);
|
|
109
|
+
}
|
|
110
|
+
function planItems(args) {
|
|
111
|
+
const source = Array.isArray(args?.plan) ? args.plan : Array.isArray(args?.todos) ? args.todos : [];
|
|
112
|
+
return source.flatMap((item) => {
|
|
113
|
+
if (typeof item === "string" && item.trim()) return [{ label: boundedString(item.trim(), 300), status: "" }];
|
|
114
|
+
const value = record(item);
|
|
115
|
+
const label = firstString(value, ["step", "title", "content", "text"]);
|
|
116
|
+
if (!label) return [];
|
|
117
|
+
return [{ label: boundedString(label, 300), status: firstString(value, ["status"]) }];
|
|
118
|
+
}).slice(0, 12);
|
|
119
|
+
}
|
|
120
|
+
function editPreview(args, resultText) {
|
|
121
|
+
const direct = firstString(args, ["patch", "diff"]);
|
|
122
|
+
if (direct) return direct;
|
|
123
|
+
const oldText = firstString(args, ["old_string"]);
|
|
124
|
+
const newText = firstString(args, ["new_string"]);
|
|
125
|
+
if (oldText || newText) {
|
|
126
|
+
return [
|
|
127
|
+
...oldText.split("\n").map((line) => `- ${line}`),
|
|
128
|
+
...newText.split("\n").map((line) => `+ ${line}`)
|
|
129
|
+
].join("\n");
|
|
130
|
+
}
|
|
131
|
+
return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? "") ? resultText : "";
|
|
132
|
+
}
|
|
133
|
+
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 ?? "";
|
|
143
|
+
const result = record(entry.resultContent);
|
|
144
|
+
const metadata = record(entry.metadata);
|
|
145
|
+
return {
|
|
146
|
+
category,
|
|
147
|
+
detail: toolDetail(category),
|
|
148
|
+
target: boundedString(target, 300),
|
|
149
|
+
command: boundedString(command, MAX_TOOL_FIELD_CHARS),
|
|
150
|
+
path: boundedString(path, MAX_TOOL_FIELD_CHARS),
|
|
151
|
+
query: boundedString(query, MAX_TOOL_FIELD_CHARS),
|
|
152
|
+
url: boundedString(url, MAX_TOOL_FIELD_CHARS),
|
|
153
|
+
subject: boundedString(subject, MAX_TOOL_FIELD_CHARS),
|
|
154
|
+
preview: boundedString(previewSource, MAX_TOOL_PREVIEW_CHARS),
|
|
155
|
+
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"])
|
|
162
|
+
};
|
|
163
|
+
}
|
|
62
164
|
function harnessDisplayName(id) {
|
|
63
165
|
return HARNESS_NAMES[id] ?? id;
|
|
64
166
|
}
|
|
@@ -76,6 +178,7 @@ function groupConversation(entries) {
|
|
|
76
178
|
return blocks;
|
|
77
179
|
}
|
|
78
180
|
function toolCategory(entry) {
|
|
181
|
+
if (TOOL_CATEGORIES.has(entry.presentation?.category)) return entry.presentation.category;
|
|
79
182
|
const name = entry.label?.toLocaleLowerCase() ?? "";
|
|
80
183
|
if (/read|view|open_file|list_dir/.test(name)) return "read";
|
|
81
184
|
if (/search|find|grep|glob/.test(name)) return "search";
|
|
@@ -83,7 +186,8 @@ function toolCategory(entry) {
|
|
|
83
186
|
if (/test|typecheck|lint|build/.test(name)) return "test";
|
|
84
187
|
if (/terminal|bash|shell|command|exec/.test(name)) return /\b(test|typecheck|lint|build)\b/i.test(entry.arguments ?? "") ? "test" : "command";
|
|
85
188
|
if (/browser|web|fetch|url/.test(name)) return "web";
|
|
86
|
-
if (/
|
|
189
|
+
if (/update.?plan|todo|checklist/.test(name)) return "plan";
|
|
190
|
+
if (/subagent|spawn.?agent|delegate|^task$/.test(name)) return "agent";
|
|
87
191
|
return "other";
|
|
88
192
|
}
|
|
89
193
|
function toolTarget(argumentsText) {
|
|
@@ -105,9 +209,12 @@ function compactToolTarget(target, workspace) {
|
|
|
105
209
|
function activitySummary(entries) {
|
|
106
210
|
const counts = /* @__PURE__ */ new Map();
|
|
107
211
|
for (const entry of entries) counts.set(toolCategory(entry), (counts.get(toolCategory(entry)) ?? 0) + 1);
|
|
212
|
+
const failed = entries.filter((entry) => entry.status === "error").length;
|
|
213
|
+
const pending = entries.filter((entry) => entry.status === "pending").length;
|
|
214
|
+
if (pending) return `${entries.length} actions in progress`;
|
|
215
|
+
if (failed) return `${entries.length} actions \xB7 ${failed} failed`;
|
|
108
216
|
if ([...counts.keys()].every((key) => key === "read" || key === "search")) return `Explored ${entries.length} ${entries.length === 1 ? "item" : "items"}`;
|
|
109
|
-
|
|
110
|
-
return `Activity \xB7 ${Object.entries(labels).flatMap(([key, label]) => counts.has(key) ? [`${counts.get(key)} ${label}`] : []).join(" \xB7 ")}`;
|
|
217
|
+
return `${entries.length} actions`;
|
|
111
218
|
}
|
|
112
219
|
|
|
113
220
|
// src/markdown.jsx
|
|
@@ -135,7 +242,7 @@ function boundedSet(map, key, value) {
|
|
|
135
242
|
}
|
|
136
243
|
|
|
137
244
|
// src/conversation.jsx
|
|
138
|
-
import { jsx as jsx2, jsxs } from "preact/jsx-runtime";
|
|
245
|
+
import { Fragment, jsx as jsx2, jsxs } from "preact/jsx-runtime";
|
|
139
246
|
function LoadingStatus({ state, compact = false }) {
|
|
140
247
|
const copy = {
|
|
141
248
|
connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
|
|
@@ -183,6 +290,185 @@ function ContextDisclosure({ context }) {
|
|
|
183
290
|
] }, item.id ?? index)) })
|
|
184
291
|
] });
|
|
185
292
|
}
|
|
293
|
+
var TOOL_ICONS = {
|
|
294
|
+
read: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
295
|
+
/* @__PURE__ */ jsx2("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" }),
|
|
296
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 2.9v3h2.85M6 9h6M6 12h4" })
|
|
297
|
+
] }),
|
|
298
|
+
search: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
299
|
+
/* @__PURE__ */ jsx2("circle", { cx: "8", cy: "8", r: "4.5" }),
|
|
300
|
+
/* @__PURE__ */ jsx2("path", { d: "m11.5 11.5 3 3" })
|
|
301
|
+
] }),
|
|
302
|
+
edit: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
303
|
+
/* @__PURE__ */ jsx2("path", { d: "m11.75 3.25 3 3-8.5 8.5-3.75.75.75-3.75 8.5-8.5Z" }),
|
|
304
|
+
/* @__PURE__ */ jsx2("path", { d: "m10 5 3 3" })
|
|
305
|
+
] }),
|
|
306
|
+
command: () => /* @__PURE__ */ jsx2(Fragment, { children: /* @__PURE__ */ jsx2("path", { d: "m3 5 3 3-3 3M8 12h6" }) }),
|
|
307
|
+
test: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
308
|
+
/* @__PURE__ */ jsx2("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" }),
|
|
309
|
+
/* @__PURE__ */ jsx2("path", { d: "M5 2.5h8" })
|
|
310
|
+
] }),
|
|
311
|
+
web: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
312
|
+
/* @__PURE__ */ jsx2("circle", { cx: "9", cy: "9", r: "6.5" }),
|
|
313
|
+
/* @__PURE__ */ jsx2("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" })
|
|
314
|
+
] }),
|
|
315
|
+
agent: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
316
|
+
/* @__PURE__ */ jsx2("circle", { cx: "9", cy: "6", r: "2.5" }),
|
|
317
|
+
/* @__PURE__ */ jsx2("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
|
|
318
|
+
] }),
|
|
319
|
+
plan: () => /* @__PURE__ */ jsx2(Fragment, { children: /* @__PURE__ */ jsx2("path", { d: "m3 5 1 1 2-2M3 9l1 1 2-2M3 13l1 1 2-2M8 5h7M8 9h7M8 13h7" }) }),
|
|
320
|
+
other: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
321
|
+
/* @__PURE__ */ jsx2("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
|
|
322
|
+
/* @__PURE__ */ jsx2("circle", { cx: "9", cy: "9", r: "3.5" })
|
|
323
|
+
] })
|
|
324
|
+
};
|
|
325
|
+
function ToolIcon({ category }) {
|
|
326
|
+
const Glyph = TOOL_ICONS[category] ?? TOOL_ICONS.other;
|
|
327
|
+
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
|
+
}
|
|
329
|
+
function toolAction(entry, category) {
|
|
330
|
+
const status = entry.status ?? "completed";
|
|
331
|
+
const actions = {
|
|
332
|
+
read: ["Reading", "Read", "Read failed"],
|
|
333
|
+
search: ["Searching", "Searched", "Search failed"],
|
|
334
|
+
edit: ["Editing", "Edited", "Edit failed"],
|
|
335
|
+
command: ["Running command", "Ran command", "Command failed"],
|
|
336
|
+
test: ["Running tests", "Ran tests", "Tests failed"],
|
|
337
|
+
web: ["Browsing", "Browsed", "Browser action failed"],
|
|
338
|
+
agent: ["Starting agent", "Started agent", "Agent failed"],
|
|
339
|
+
plan: ["Updating plan", "Updated plan", "Plan update failed"]
|
|
340
|
+
};
|
|
341
|
+
const position = status === "pending" ? 0 : status === "error" ? 2 : 1;
|
|
342
|
+
if (actions[category]) return actions[category][position];
|
|
343
|
+
const label = entry.label?.split(/__|\//).at(-1)?.replaceAll(/[_-]+/g, " ") || "Tool";
|
|
344
|
+
return status === "error" ? `${label} failed` : label;
|
|
345
|
+
}
|
|
346
|
+
function argumentLabel(key) {
|
|
347
|
+
const labels = { cmd: "Command", command: "Command", cwd: "Working directory", file_path: "File", target_file: "File", path: "Path", query: "Query", pattern: "Pattern", url: "URL" };
|
|
348
|
+
return labels[key] ?? key.replaceAll(/[_-]+/g, " ").replace(/^./, (letter) => letter.toLocaleUpperCase());
|
|
349
|
+
}
|
|
350
|
+
function argumentRows(argumentsText) {
|
|
351
|
+
if (!argumentsText) return [];
|
|
352
|
+
try {
|
|
353
|
+
const value = JSON.parse(argumentsText);
|
|
354
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
355
|
+
return Object.entries(value).map(([key, item]) => ({
|
|
356
|
+
key,
|
|
357
|
+
label: argumentLabel(key),
|
|
358
|
+
value: typeof item === "string" ? item : JSON.stringify(item, null, 2)
|
|
359
|
+
}));
|
|
360
|
+
}
|
|
361
|
+
} catch {
|
|
362
|
+
}
|
|
363
|
+
return [{ key: "arguments", label: "Details", value: argumentsText }];
|
|
364
|
+
}
|
|
365
|
+
function stripAnsi(value) {
|
|
366
|
+
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, "") ?? "";
|
|
367
|
+
}
|
|
368
|
+
function formatDuration(value) {
|
|
369
|
+
if (value === null) return "";
|
|
370
|
+
if (value < 1e3) return `${Math.round(value)}ms`;
|
|
371
|
+
return `${(value / 1e3).toFixed(value < 1e4 ? 1 : 0)}s`;
|
|
372
|
+
}
|
|
373
|
+
function ToolMetrics({ presentation }) {
|
|
374
|
+
const metrics = [
|
|
375
|
+
presentation.matches === null ? "" : `${presentation.matches} ${presentation.matches === 1 ? "match" : "matches"}`,
|
|
376
|
+
presentation.additions === null ? "" : `+${presentation.additions}`,
|
|
377
|
+
presentation.deletions === null ? "" : `\u2212${presentation.deletions}`,
|
|
378
|
+
presentation.exitCode === null ? "" : `exit ${presentation.exitCode}`,
|
|
379
|
+
formatDuration(presentation.durationMs)
|
|
380
|
+
].filter(Boolean);
|
|
381
|
+
return metrics.length ? /* @__PURE__ */ jsx2("div", { class: "scui-tool-metrics", children: metrics.map((metric) => /* @__PURE__ */ jsx2("span", { children: metric }, metric)) }) : null;
|
|
382
|
+
}
|
|
383
|
+
function LinePreview({ value, kind }) {
|
|
384
|
+
if (!value) return null;
|
|
385
|
+
return /* @__PURE__ */ jsx2("ol", { class: "scui-code-preview", "data-kind": kind, children: stripAnsi(value).split("\n").map((line, index) => {
|
|
386
|
+
const tone = kind === "diff" ? line.startsWith("+") && !line.startsWith("+++") ? "add" : line.startsWith("-") && !line.startsWith("---") ? "remove" : line.startsWith("@@") ? "hunk" : "plain" : "plain";
|
|
387
|
+
return /* @__PURE__ */ jsxs("li", { "data-tone": tone, children: [
|
|
388
|
+
/* @__PURE__ */ jsx2("span", { children: index + 1 }),
|
|
389
|
+
/* @__PURE__ */ jsx2("code", { children: line || " " })
|
|
390
|
+
] }, index);
|
|
391
|
+
}) });
|
|
392
|
+
}
|
|
393
|
+
function TerminalPreview({ presentation, pending, failed }) {
|
|
394
|
+
return /* @__PURE__ */ jsxs("section", { class: "scui-terminal", children: [
|
|
395
|
+
/* @__PURE__ */ jsxs("header", { children: [
|
|
396
|
+
/* @__PURE__ */ jsxs("span", { "aria-hidden": "true", children: [
|
|
397
|
+
/* @__PURE__ */ jsx2("i", {}),
|
|
398
|
+
/* @__PURE__ */ jsx2("i", {}),
|
|
399
|
+
/* @__PURE__ */ jsx2("i", {})
|
|
400
|
+
] }),
|
|
401
|
+
/* @__PURE__ */ jsx2("code", { children: presentation.command ? `$ ${presentation.command}` : "Terminal" })
|
|
402
|
+
] }),
|
|
403
|
+
presentation.preview ? /* @__PURE__ */ jsx2("pre", { "data-error": failed, children: stripAnsi(presentation.preview) }) : pending ? /* @__PURE__ */ jsxs("div", { class: "scui-terminal-wait", children: [
|
|
404
|
+
/* @__PURE__ */ jsx2("i", {}),
|
|
405
|
+
" Waiting for output"
|
|
406
|
+
] }) : /* @__PURE__ */ jsx2("div", { class: "scui-terminal-empty", children: "No output" })
|
|
407
|
+
] });
|
|
408
|
+
}
|
|
409
|
+
function SearchPreview({ presentation }) {
|
|
410
|
+
const lines = stripAnsi(presentation.preview).split("\n").filter(Boolean);
|
|
411
|
+
return /* @__PURE__ */ jsxs("section", { class: "scui-search-preview", children: [
|
|
412
|
+
presentation.query ? /* @__PURE__ */ jsxs("header", { children: [
|
|
413
|
+
/* @__PURE__ */ jsx2("span", { children: "Search" }),
|
|
414
|
+
/* @__PURE__ */ jsx2("code", { children: presentation.query })
|
|
415
|
+
] }) : null,
|
|
416
|
+
lines.length ? /* @__PURE__ */ jsx2("ol", { children: lines.map((line, index) => {
|
|
417
|
+
const match = /^(.*?):(\d+)(?::(\d+))?:(.*)$/.exec(line);
|
|
418
|
+
return /* @__PURE__ */ jsx2("li", { children: match ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
419
|
+
/* @__PURE__ */ jsx2("code", { children: match[1] }),
|
|
420
|
+
/* @__PURE__ */ jsxs("small", { children: [
|
|
421
|
+
match[2],
|
|
422
|
+
match[3] ? `:${match[3]}` : ""
|
|
423
|
+
] }),
|
|
424
|
+
/* @__PURE__ */ jsx2("span", { children: match[4] })
|
|
425
|
+
] }) : /* @__PURE__ */ jsx2("span", { children: line }) }, index);
|
|
426
|
+
}) }) : /* @__PURE__ */ jsx2("p", { children: "No textual results" })
|
|
427
|
+
] });
|
|
428
|
+
}
|
|
429
|
+
function ToolPreview({ presentation, entry }) {
|
|
430
|
+
const pending = entry.status === "pending";
|
|
431
|
+
const failed = entry.status === "error";
|
|
432
|
+
if (presentation.detail === "terminal") return /* @__PURE__ */ jsx2(TerminalPreview, { presentation, pending, failed });
|
|
433
|
+
if (presentation.detail === "diff") return presentation.preview ? /* @__PURE__ */ jsx2(LinePreview, { value: presentation.preview, kind: "diff" }) : /* @__PURE__ */ jsx2("div", { class: "scui-tool-empty", children: "Edit completed without a textual diff" });
|
|
434
|
+
if (presentation.detail === "file") return presentation.preview ? /* @__PURE__ */ jsx2(LinePreview, { value: presentation.preview, kind: "file" }) : /* @__PURE__ */ jsx2("div", { class: "scui-tool-empty", children: "File contents were not included in this event" });
|
|
435
|
+
if (presentation.detail === "matches") return /* @__PURE__ */ jsx2(SearchPreview, { presentation });
|
|
436
|
+
if (presentation.detail === "web") return /* @__PURE__ */ jsxs("section", { class: "scui-web-preview", children: [
|
|
437
|
+
presentation.url ? /* @__PURE__ */ jsx2("code", { children: presentation.url }) : null,
|
|
438
|
+
presentation.preview ? /* @__PURE__ */ jsx2("p", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx2("small", { children: pending ? "Waiting for the page" : "No page summary returned" })
|
|
439
|
+
] });
|
|
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
|
+
] });
|
|
444
|
+
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
|
+
/* @__PURE__ */ jsx2("i", { "aria-hidden": "true" }),
|
|
446
|
+
/* @__PURE__ */ jsx2("span", { children: item.label })
|
|
447
|
+
] }, `${item.label}:${index}`)) });
|
|
448
|
+
return presentation.preview ? /* @__PURE__ */ jsx2("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
|
|
449
|
+
}
|
|
450
|
+
function TechnicalDetails({ entry }) {
|
|
451
|
+
if (!entry.arguments && !entry.resultText) return null;
|
|
452
|
+
return /* @__PURE__ */ jsxs("details", { class: "scui-tool-technical", children: [
|
|
453
|
+
/* @__PURE__ */ jsx2("summary", { children: "Technical details" }),
|
|
454
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
455
|
+
entry.arguments ? /* @__PURE__ */ jsxs("section", { children: [
|
|
456
|
+
/* @__PURE__ */ jsx2("strong", { children: "Native arguments" }),
|
|
457
|
+
/* @__PURE__ */ jsx2("dl", { children: argumentRows(entry.arguments).map((row) => /* @__PURE__ */ jsxs("div", { children: [
|
|
458
|
+
/* @__PURE__ */ jsx2("dt", { children: row.label }),
|
|
459
|
+
/* @__PURE__ */ jsx2("dd", { children: /* @__PURE__ */ jsx2("pre", { children: row.value }) })
|
|
460
|
+
] }, row.key)) })
|
|
461
|
+
] }) : null,
|
|
462
|
+
entry.resultText ? /* @__PURE__ */ jsxs("section", { children: [
|
|
463
|
+
/* @__PURE__ */ jsx2("strong", { children: "Native result" }),
|
|
464
|
+
/* @__PURE__ */ jsxs("pre", { "data-error": entry.status === "error", children: [
|
|
465
|
+
entry.resultText,
|
|
466
|
+
entry.truncated ? "\n[truncated]" : ""
|
|
467
|
+
] })
|
|
468
|
+
] }) : null
|
|
469
|
+
] })
|
|
470
|
+
] });
|
|
471
|
+
}
|
|
186
472
|
function TranscriptEntry({ entry, state, adapter }) {
|
|
187
473
|
if (entry.role === "request") return /* @__PURE__ */ jsx2(RequestCard, { entry, adapter, canRespond: state.canRespond });
|
|
188
474
|
if (entry.role === "reasoning") {
|
|
@@ -198,36 +484,35 @@ function TranscriptEntry({ entry, state, adapter }) {
|
|
|
198
484
|
entry.truncated ? /* @__PURE__ */ jsx2("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null
|
|
199
485
|
] });
|
|
200
486
|
}
|
|
201
|
-
function ToolRow({ entry, workspace }) {
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
/* @__PURE__ */
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
/* @__PURE__ */ jsx2("
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
] }) : null
|
|
227
|
-
] }) : null
|
|
487
|
+
function ToolRow({ entry, workspace, open = false }) {
|
|
488
|
+
const presentation = entry.presentation ?? createToolPresentation(entry);
|
|
489
|
+
const target = compactToolTarget(presentation.target, workspace);
|
|
490
|
+
const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
|
|
491
|
+
const category = presentation.category ?? toolCategory(entry);
|
|
492
|
+
const summary = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
493
|
+
/* @__PURE__ */ jsx2(ToolIcon, { category }),
|
|
494
|
+
/* @__PURE__ */ jsx2("strong", { children: toolAction(entry, category) }),
|
|
495
|
+
target ? /* @__PURE__ */ jsx2("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
|
|
496
|
+
/* @__PURE__ */ jsx2("span", { class: "scui-spacer" }),
|
|
497
|
+
/* @__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
|
+
hasDetail ? /* @__PURE__ */ jsx2("span", { class: "scui-tool-chevron", "aria-hidden": "true", children: "\u203A" }) : null
|
|
499
|
+
] });
|
|
500
|
+
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: [
|
|
502
|
+
/* @__PURE__ */ jsx2("summary", { class: "scui-tool-head", children: summary }),
|
|
503
|
+
/* @__PURE__ */ jsxs("div", { class: "scui-tool-detail", children: [
|
|
504
|
+
/* @__PURE__ */ jsx2(ToolMetrics, { presentation }),
|
|
505
|
+
/* @__PURE__ */ jsx2(ToolPreview, { presentation, entry }),
|
|
506
|
+
presentation.fields.length ? /* @__PURE__ */ jsx2("dl", { class: "scui-tool-fields", children: presentation.fields.map((field) => /* @__PURE__ */ jsxs("div", { children: [
|
|
507
|
+
/* @__PURE__ */ jsx2("dt", { children: field.label }),
|
|
508
|
+
/* @__PURE__ */ jsx2("dd", { children: field.value })
|
|
509
|
+
] }, field.label)) }) : null,
|
|
510
|
+
/* @__PURE__ */ jsx2(TechnicalDetails, { entry })
|
|
511
|
+
] })
|
|
228
512
|
] });
|
|
229
513
|
}
|
|
230
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 }) });
|
|
231
516
|
const active = entries.some((entry) => entry.status === "pending");
|
|
232
517
|
const [open, setOpen] = useState(active);
|
|
233
518
|
const id = useId();
|
|
@@ -240,7 +525,7 @@ function ActivityGroup({ entries, state }) {
|
|
|
240
525
|
/* @__PURE__ */ jsx2("strong", { children: activitySummary(entries) }),
|
|
241
526
|
/* @__PURE__ */ jsx2("span", { class: "scui-spacer" }),
|
|
242
527
|
/* @__PURE__ */ jsxs("small", { children: [
|
|
243
|
-
entries.filter((entry) => entry.status
|
|
528
|
+
entries.filter((entry) => entry.status !== "pending").length,
|
|
244
529
|
"/",
|
|
245
530
|
entries.length
|
|
246
531
|
] })
|
|
@@ -387,5 +672,6 @@ export {
|
|
|
387
672
|
RequestCard,
|
|
388
673
|
SessionDetails,
|
|
389
674
|
TaskPlan,
|
|
675
|
+
ToolRow,
|
|
390
676
|
TranscriptEntry
|
|
391
677
|
};
|
package/core.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export type {
|
|
|
19
19
|
TranscriptContext,
|
|
20
20
|
TranscriptEntryModel,
|
|
21
21
|
TranscriptRequest,
|
|
22
|
+
ToolCategory,
|
|
23
|
+
ToolPresentationModel,
|
|
22
24
|
UiTone,
|
|
23
25
|
} from './index.js';
|
|
24
26
|
export {
|
|
@@ -27,6 +29,7 @@ export {
|
|
|
27
29
|
activitySummary,
|
|
28
30
|
canContinueHere,
|
|
29
31
|
compactToolTarget,
|
|
32
|
+
createToolPresentation,
|
|
30
33
|
filterSessions,
|
|
31
34
|
groupConversation,
|
|
32
35
|
harnessDisplayName,
|
package/core.mjs
CHANGED
|
@@ -63,6 +63,11 @@ const MODES = new Set(['none', 'control', 'mirror']);
|
|
|
63
63
|
const STRATEGIES = new Set(['start', 'resume', 'attach', 'branch', 'reduce']);
|
|
64
64
|
const STARTUP = new Set(['connecting', 'starting', 'discovering', 'ready']);
|
|
65
65
|
const FIDELITY = new Set(['byte_lossless', 'value_lossless', 'semantic']);
|
|
66
|
+
const TOOL_CATEGORIES = new Set(['read', 'search', 'edit', 'command', 'test', 'web', 'agent', 'plan', 'other']);
|
|
67
|
+
const TOOL_DETAILS = new Set(['file', 'matches', 'diff', 'terminal', 'web', 'agent', 'plan', 'fields']);
|
|
68
|
+
const MAX_TOOL_FIELDS = 8;
|
|
69
|
+
const MAX_TOOL_FIELD_CHARS = 800;
|
|
70
|
+
const MAX_TOOL_PREVIEW_CHARS = 4_000;
|
|
66
71
|
|
|
67
72
|
function record(value) {
|
|
68
73
|
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
@@ -80,6 +85,146 @@ function nullableNumber(value) {
|
|
|
80
85
|
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
81
86
|
}
|
|
82
87
|
|
|
88
|
+
function boundedString(value, max) {
|
|
89
|
+
if (typeof value !== 'string') return '';
|
|
90
|
+
return value.length <= max ? value : `${value.slice(0, max)}…`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function argumentObject(argumentsText) {
|
|
94
|
+
if (!argumentsText) return null;
|
|
95
|
+
try {
|
|
96
|
+
return record(JSON.parse(argumentsText));
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function firstString(source, keys) {
|
|
103
|
+
for (const key of keys) {
|
|
104
|
+
if (typeof source?.[key] === 'string' && source[key].trim()) return source[key].trim();
|
|
105
|
+
}
|
|
106
|
+
return '';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function explicitNumber(sources, keys) {
|
|
110
|
+
for (const source of sources) {
|
|
111
|
+
for (const key of keys) {
|
|
112
|
+
const value = source?.[key];
|
|
113
|
+
const parsed = typeof value === 'string' && value.trim() !== '' ? Number(value) : value;
|
|
114
|
+
if (typeof parsed === 'number' && Number.isFinite(parsed)) return parsed;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function toolDetail(category) {
|
|
121
|
+
return ({ read: 'file', search: 'matches', edit: 'diff', command: 'terminal', test: 'terminal', web: 'web', agent: 'agent', plan: 'plan' })[category] ?? 'fields';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function usefulToolFields(args) {
|
|
125
|
+
if (!args) return [];
|
|
126
|
+
const hidden = new Set(['command', 'cmd', 'file_path', 'target_file', 'path', 'query', 'pattern', 'url', 'patch', 'diff', 'old_string', 'new_string', 'content', 'prompt', 'description', 'task', 'plan', 'todos']);
|
|
127
|
+
return Object.entries(args).flatMap(([key, value]) => {
|
|
128
|
+
if (hidden.has(key) || value === null || value === undefined) return [];
|
|
129
|
+
const rendered = typeof value === 'string' ? value : JSON.stringify(value);
|
|
130
|
+
if (!rendered) return [];
|
|
131
|
+
return [{ label: key.replaceAll(/[_-]+/g, ' ').replace(/^./, (letter) => letter.toLocaleUpperCase()), value: boundedString(rendered, MAX_TOOL_FIELD_CHARS) }];
|
|
132
|
+
}).slice(0, MAX_TOOL_FIELDS);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function planItems(args) {
|
|
136
|
+
const source = Array.isArray(args?.plan) ? args.plan : Array.isArray(args?.todos) ? args.todos : [];
|
|
137
|
+
return source.flatMap((item) => {
|
|
138
|
+
if (typeof item === 'string' && item.trim()) return [{ label: boundedString(item.trim(), 300), status: '' }];
|
|
139
|
+
const value = record(item);
|
|
140
|
+
const label = firstString(value, ['step', 'title', 'content', 'text']);
|
|
141
|
+
if (!label) return [];
|
|
142
|
+
return [{ label: boundedString(label, 300), status: firstString(value, ['status']) }];
|
|
143
|
+
}).slice(0, 12);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function editPreview(args, resultText) {
|
|
147
|
+
const direct = firstString(args, ['patch', 'diff']);
|
|
148
|
+
if (direct) return direct;
|
|
149
|
+
const oldText = firstString(args, ['old_string']);
|
|
150
|
+
const newText = firstString(args, ['new_string']);
|
|
151
|
+
if (oldText || newText) {
|
|
152
|
+
return [
|
|
153
|
+
...oldText.split('\n').map((line) => `- ${line}`),
|
|
154
|
+
...newText.split('\n').map((line) => `+ ${line}`),
|
|
155
|
+
].join('\n');
|
|
156
|
+
}
|
|
157
|
+
return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? '') ? resultText : '';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
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 ?? '');
|
|
170
|
+
const result = record(entry.resultContent);
|
|
171
|
+
const metadata = record(entry.metadata);
|
|
172
|
+
return {
|
|
173
|
+
category,
|
|
174
|
+
detail: toolDetail(category),
|
|
175
|
+
target: boundedString(target, 300),
|
|
176
|
+
command: boundedString(command, MAX_TOOL_FIELD_CHARS),
|
|
177
|
+
path: boundedString(path, MAX_TOOL_FIELD_CHARS),
|
|
178
|
+
query: boundedString(query, MAX_TOOL_FIELD_CHARS),
|
|
179
|
+
url: boundedString(url, MAX_TOOL_FIELD_CHARS),
|
|
180
|
+
subject: boundedString(subject, MAX_TOOL_FIELD_CHARS),
|
|
181
|
+
preview: boundedString(previewSource, MAX_TOOL_PREVIEW_CHARS),
|
|
182
|
+
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']),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function readToolPresentation(value, entry) {
|
|
193
|
+
const generated = createToolPresentation(entry);
|
|
194
|
+
const item = record(value);
|
|
195
|
+
if (!item) return generated;
|
|
196
|
+
const fields = Array.isArray(item.fields) ? item.fields.flatMap((raw) => {
|
|
197
|
+
const field = record(raw);
|
|
198
|
+
return field && typeof field.label === 'string' && typeof field.value === 'string'
|
|
199
|
+
? [{ label: boundedString(field.label, 80), value: boundedString(field.value, MAX_TOOL_FIELD_CHARS) }]
|
|
200
|
+
: [];
|
|
201
|
+
}).slice(0, MAX_TOOL_FIELDS) : generated.fields;
|
|
202
|
+
const items = Array.isArray(item.items) ? item.items.flatMap((raw) => {
|
|
203
|
+
const planItem = record(raw);
|
|
204
|
+
return planItem && typeof planItem.label === 'string'
|
|
205
|
+
? [{ label: boundedString(planItem.label, 300), status: boundedString(planItem.status, 40) }]
|
|
206
|
+
: [];
|
|
207
|
+
}).slice(0, 12) : generated.items;
|
|
208
|
+
return {
|
|
209
|
+
category: TOOL_CATEGORIES.has(item.category) ? item.category : generated.category,
|
|
210
|
+
detail: TOOL_DETAILS.has(item.detail) ? item.detail : generated.detail,
|
|
211
|
+
target: boundedString(item.target, 300) || generated.target,
|
|
212
|
+
command: boundedString(item.command, MAX_TOOL_FIELD_CHARS) || generated.command,
|
|
213
|
+
path: boundedString(item.path, MAX_TOOL_FIELD_CHARS) || generated.path,
|
|
214
|
+
query: boundedString(item.query, MAX_TOOL_FIELD_CHARS) || generated.query,
|
|
215
|
+
url: boundedString(item.url, MAX_TOOL_FIELD_CHARS) || generated.url,
|
|
216
|
+
subject: boundedString(item.subject, MAX_TOOL_FIELD_CHARS) || generated.subject,
|
|
217
|
+
preview: boundedString(item.preview, MAX_TOOL_PREVIEW_CHARS) || generated.preview,
|
|
218
|
+
fields,
|
|
219
|
+
items,
|
|
220
|
+
exitCode: nullableNumber(item.exitCode) ?? generated.exitCode,
|
|
221
|
+
durationMs: nullableNumber(item.durationMs) ?? generated.durationMs,
|
|
222
|
+
additions: nullableNumber(item.additions) ?? generated.additions,
|
|
223
|
+
deletions: nullableNumber(item.deletions) ?? generated.deletions,
|
|
224
|
+
matches: nullableNumber(item.matches) ?? generated.matches,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
83
228
|
function readTranscript(value) {
|
|
84
229
|
if (!Array.isArray(value)) return [];
|
|
85
230
|
const result = [];
|
|
@@ -97,6 +242,7 @@ function readTranscript(value) {
|
|
|
97
242
|
if (typeof item[key] === 'string') entry[key] = item[key];
|
|
98
243
|
}
|
|
99
244
|
if (['pending', 'completed', 'error'].includes(item.status)) entry.status = item.status;
|
|
245
|
+
if (item.role === 'tool') entry.presentation = readToolPresentation(item.presentation, entry);
|
|
100
246
|
if (typeof item.streaming === 'boolean') entry.streaming = item.streaming;
|
|
101
247
|
if (Array.isArray(item.context)) {
|
|
102
248
|
entry.context = item.context.flatMap((raw) => {
|
|
@@ -358,6 +504,7 @@ export function groupConversation(entries) {
|
|
|
358
504
|
}
|
|
359
505
|
|
|
360
506
|
export function toolCategory(entry) {
|
|
507
|
+
if (TOOL_CATEGORIES.has(entry.presentation?.category)) return entry.presentation.category;
|
|
361
508
|
const name = entry.label?.toLocaleLowerCase() ?? '';
|
|
362
509
|
if (/read|view|open_file|list_dir/.test(name)) return 'read';
|
|
363
510
|
if (/search|find|grep|glob/.test(name)) return 'search';
|
|
@@ -365,7 +512,8 @@ export function toolCategory(entry) {
|
|
|
365
512
|
if (/test|typecheck|lint|build/.test(name)) return 'test';
|
|
366
513
|
if (/terminal|bash|shell|command|exec/.test(name)) return /\b(test|typecheck|lint|build)\b/i.test(entry.arguments ?? '') ? 'test' : 'command';
|
|
367
514
|
if (/browser|web|fetch|url/.test(name)) return 'web';
|
|
368
|
-
if (/
|
|
515
|
+
if (/update.?plan|todo|checklist/.test(name)) return 'plan';
|
|
516
|
+
if (/subagent|spawn.?agent|delegate|^task$/.test(name)) return 'agent';
|
|
369
517
|
return 'other';
|
|
370
518
|
}
|
|
371
519
|
|
|
@@ -390,9 +538,12 @@ export function compactToolTarget(target, workspace) {
|
|
|
390
538
|
export function activitySummary(entries) {
|
|
391
539
|
const counts = new Map();
|
|
392
540
|
for (const entry of entries) counts.set(toolCategory(entry), (counts.get(toolCategory(entry)) ?? 0) + 1);
|
|
541
|
+
const failed = entries.filter((entry) => entry.status === 'error').length;
|
|
542
|
+
const pending = entries.filter((entry) => entry.status === 'pending').length;
|
|
543
|
+
if (pending) return `${entries.length} actions in progress`;
|
|
544
|
+
if (failed) return `${entries.length} actions · ${failed} failed`;
|
|
393
545
|
if ([...counts.keys()].every((key) => key === 'read' || key === 'search')) return `Explored ${entries.length} ${entries.length === 1 ? 'item' : 'items'}`;
|
|
394
|
-
|
|
395
|
-
return `Activity · ${Object.entries(labels).flatMap(([key, label]) => counts.has(key) ? [`${counts.get(key)} ${label}`] : []).join(' · ')}`;
|
|
546
|
+
return `${entries.length} actions`;
|
|
396
547
|
}
|
|
397
548
|
|
|
398
549
|
export function canContinueHere(state) {
|