@volter-ai-dev/supercode-ui 0.1.9 → 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/components.mjs +141 -40
- package/conversation.mjs +137 -40
- package/core.mjs +111 -29
- package/embed.mjs +141 -40
- package/index.d.ts +5 -1
- package/messenger.mjs +141 -40
- package/package.json +1 -1
- package/styles.css +3 -0
package/core.mjs
CHANGED
|
@@ -90,15 +90,19 @@ function boundedString(value, max) {
|
|
|
90
90
|
return value.length <= max ? value : `${value.slice(0, max)}…`;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function
|
|
93
|
+
function argumentValue(argumentsText) {
|
|
94
94
|
if (!argumentsText) return null;
|
|
95
95
|
try {
|
|
96
|
-
return
|
|
96
|
+
return JSON.parse(argumentsText);
|
|
97
97
|
} catch {
|
|
98
98
|
return null;
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
function argumentObject(argumentsText) {
|
|
103
|
+
return record(argumentValue(argumentsText));
|
|
104
|
+
}
|
|
105
|
+
|
|
102
106
|
function firstString(source, keys) {
|
|
103
107
|
for (const key of keys) {
|
|
104
108
|
if (typeof source?.[key] === 'string' && source[key].trim()) return source[key].trim();
|
|
@@ -106,6 +110,41 @@ function firstString(source, keys) {
|
|
|
106
110
|
return '';
|
|
107
111
|
}
|
|
108
112
|
|
|
113
|
+
function decodedLiteral(value) {
|
|
114
|
+
if (!value) return '';
|
|
115
|
+
if (value.startsWith('"')) {
|
|
116
|
+
try { return JSON.parse(value); } catch { return ''; }
|
|
117
|
+
}
|
|
118
|
+
return value.slice(1, -1)
|
|
119
|
+
.replaceAll('\\n', '\n')
|
|
120
|
+
.replaceAll('\\t', '\t')
|
|
121
|
+
.replaceAll('\\r', '\r')
|
|
122
|
+
.replaceAll('\\`', '`')
|
|
123
|
+
.replaceAll("\\'", "'")
|
|
124
|
+
.replaceAll('\\\\', '\\');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function sourceString(source, keys) {
|
|
128
|
+
if (!source) return '';
|
|
129
|
+
const names = keys.join('|');
|
|
130
|
+
const match = new RegExp(`\\b(?:${names})\\s*:\\s*(\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
|
|
131
|
+
return decodedLiteral(match?.[1]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function toolEnvelope(entry) {
|
|
135
|
+
const value = argumentValue(entry.arguments);
|
|
136
|
+
const source = typeof value === 'string' ? value : '';
|
|
137
|
+
const tools = source
|
|
138
|
+
? [...new Set([...source.matchAll(/\btools\.([A-Za-z0-9_]+)/g)].map((match) => match[1]))].slice(0, 8)
|
|
139
|
+
: [];
|
|
140
|
+
const name = tools.length === 1 ? tools[0] : entry.label ?? 'tool';
|
|
141
|
+
return { args: record(value), source, tools, name };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function patchPath(source) {
|
|
145
|
+
return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]+)/.exec(source)?.[1]?.trim() ?? '';
|
|
146
|
+
}
|
|
147
|
+
|
|
109
148
|
function explicitNumber(sources, keys) {
|
|
110
149
|
for (const source of sources) {
|
|
111
150
|
for (const key of keys) {
|
|
@@ -143,7 +182,7 @@ function planItems(args) {
|
|
|
143
182
|
}).slice(0, 12);
|
|
144
183
|
}
|
|
145
184
|
|
|
146
|
-
function editPreview(args, resultText) {
|
|
185
|
+
function editPreview(args, resultText, source) {
|
|
147
186
|
const direct = firstString(args, ['patch', 'diff']);
|
|
148
187
|
if (direct) return direct;
|
|
149
188
|
const oldText = firstString(args, ['old_string']);
|
|
@@ -154,22 +193,66 @@ function editPreview(args, resultText) {
|
|
|
154
193
|
...newText.split('\n').map((line) => `+ ${line}`),
|
|
155
194
|
].join('\n');
|
|
156
195
|
}
|
|
196
|
+
const patch = /\*\*\* Begin Patch[\s\S]*?\*\*\* End Patch/.exec(source ?? '')?.[0];
|
|
197
|
+
if (patch) return patch;
|
|
157
198
|
return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? '') ? resultText : '';
|
|
158
199
|
}
|
|
159
200
|
|
|
201
|
+
function classifyTool(name, command) {
|
|
202
|
+
const normalized = name.toLocaleLowerCase();
|
|
203
|
+
if (/write_stdin|^wait$/.test(normalized)) return 'command';
|
|
204
|
+
if (/update.?plan|todo|checklist|taskcreate|taskupdate/.test(normalized)) return 'plan';
|
|
205
|
+
if (/search.?replace|edit|write|patch|replace|create_file|apply_patch/.test(normalized)) return 'edit';
|
|
206
|
+
if (/read|view|open_file|list_dir/.test(normalized)) return 'read';
|
|
207
|
+
if (/search|find|grep|glob|toolsearch/.test(normalized)) return 'search';
|
|
208
|
+
if (/browser|web|fetch|url/.test(normalized)) return 'web';
|
|
209
|
+
if (/agent|subagent|sendmessage|delegate|^task$/.test(normalized)) return 'agent';
|
|
210
|
+
if (/test|typecheck|lint|build/.test(normalized)) return 'test';
|
|
211
|
+
if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return /\b(test|typecheck|lint|build)\b/i.test(command) ? 'test' : 'command';
|
|
212
|
+
return 'other';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function toolAction(status, category, name, tools) {
|
|
216
|
+
const position = status === 'pending' ? 0 : status === 'error' ? 2 : 1;
|
|
217
|
+
if (tools.length > 1) return [`Running ${tools.length} actions`, `Ran ${tools.length} actions`, `${tools.length} actions failed`][position];
|
|
218
|
+
const normalized = name.toLocaleLowerCase();
|
|
219
|
+
if (/sendmessage/.test(normalized)) return ['Messaging agent', 'Messaged agent', 'Agent message failed'][position];
|
|
220
|
+
if (/kill_command_or_subagent/.test(normalized)) return ['Stopping', 'Stopped', 'Stop failed'][position];
|
|
221
|
+
if (/get_command_or_subagent_output|write_stdin|^wait$/.test(normalized)) return ['Waiting for', 'Checked', 'Check failed'][position];
|
|
222
|
+
const actions = {
|
|
223
|
+
read: ['Reading', 'Read', 'Read failed'],
|
|
224
|
+
search: ['Searching', 'Searched', 'Search failed'],
|
|
225
|
+
edit: ['Editing', 'Edited', 'Edit failed'],
|
|
226
|
+
command: ['Running command', 'Ran command', 'Command failed'],
|
|
227
|
+
test: ['Running tests', 'Ran tests', 'Tests failed'],
|
|
228
|
+
web: ['Browsing', 'Browsed', 'Browser action failed'],
|
|
229
|
+
agent: ['Starting agent', 'Started agent', 'Agent failed'],
|
|
230
|
+
plan: ['Updating plan', 'Updated plan', 'Plan update failed'],
|
|
231
|
+
};
|
|
232
|
+
return actions[category]?.[position] ?? (status === 'error' ? `${name} failed` : name.replaceAll(/[_-]+/g, ' '));
|
|
233
|
+
}
|
|
234
|
+
|
|
160
235
|
export function createToolPresentation(entry) {
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
const command = firstString(args, ['command', 'cmd']);
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
const
|
|
236
|
+
const envelope = toolEnvelope(entry);
|
|
237
|
+
const args = envelope.args;
|
|
238
|
+
const command = firstString(args, ['command', 'cmd']) || sourceString(envelope.source, ['command', 'cmd']);
|
|
239
|
+
const category = classifyTool(envelope.name, command || envelope.source || entry.arguments || '');
|
|
240
|
+
const path = firstString(args, ['file_path', 'target_file', 'target_directory', 'path']) || sourceString(envelope.source, ['file_path', 'target_file', 'target_directory', 'path']) || patchPath(envelope.source);
|
|
241
|
+
const query = firstString(args, ['query', 'pattern']) || sourceString(envelope.source, ['query', 'pattern', 'q']);
|
|
242
|
+
const url = firstString(args, ['url']) || sourceString(envelope.source, ['url', 'ref_id']);
|
|
243
|
+
const subject = firstString(args, ['subject', 'description', 'summary', 'task', 'prompt']) || sourceString(envelope.source, ['subject', 'description', 'summary', 'task', 'prompt']);
|
|
244
|
+
const background = /get_command_or_subagent_output|kill_command_or_subagent/i.test(envelope.name) ? 'background task' : /write_stdin|^wait$/i.test(envelope.name) ? 'background command' : '';
|
|
245
|
+
const items = planItems(args);
|
|
246
|
+
const taskId = firstString(args, ['taskId', 'task_id']);
|
|
247
|
+
const planTarget = category === 'plan' ? items.length ? `${items.length} ${items.length === 1 ? 'item' : 'items'}` : taskId ? `task ${taskId}` : '' : '';
|
|
248
|
+
const target = path || command || query || url || subject || background || planTarget || (envelope.tools.length > 1 ? `${envelope.tools.length} coordinated actions` : toolTarget(entry.arguments));
|
|
249
|
+
const previewSource = category === 'edit' ? editPreview(args, entry.resultText, envelope.source) : (entry.resultText ?? '');
|
|
170
250
|
const result = record(entry.resultContent);
|
|
171
251
|
const metadata = record(entry.metadata);
|
|
252
|
+
const resultMetadata = record(result?.metadata);
|
|
172
253
|
return {
|
|
254
|
+
name: boundedString(envelope.name, 120),
|
|
255
|
+
action: boundedString(toolAction(entry.status ?? 'completed', category, envelope.name, envelope.tools), 120),
|
|
173
256
|
category,
|
|
174
257
|
detail: toolDetail(category),
|
|
175
258
|
target: boundedString(target, 300),
|
|
@@ -180,12 +263,13 @@ export function createToolPresentation(entry) {
|
|
|
180
263
|
subject: boundedString(subject, MAX_TOOL_FIELD_CHARS),
|
|
181
264
|
preview: boundedString(previewSource, MAX_TOOL_PREVIEW_CHARS),
|
|
182
265
|
fields: usefulToolFields(args),
|
|
183
|
-
items
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
266
|
+
items,
|
|
267
|
+
tools: envelope.tools,
|
|
268
|
+
exitCode: explicitNumber([result, resultMetadata, metadata], ['exit_code', 'exitCode', 'pi_bash_exit_code']),
|
|
269
|
+
durationMs: explicitNumber([result, resultMetadata, metadata], ['duration_ms', 'durationMs', 'elapsed_ms', 'elapsedMs', 'totalDurationMs']),
|
|
270
|
+
additions: explicitNumber([result, resultMetadata, metadata], ['additions', 'lines_added']),
|
|
271
|
+
deletions: explicitNumber([result, resultMetadata, metadata], ['deletions', 'lines_removed']),
|
|
272
|
+
matches: explicitNumber([result, resultMetadata, metadata], ['matches', 'match_count', 'result_count']),
|
|
189
273
|
};
|
|
190
274
|
}
|
|
191
275
|
|
|
@@ -205,7 +289,10 @@ function readToolPresentation(value, entry) {
|
|
|
205
289
|
? [{ label: boundedString(planItem.label, 300), status: boundedString(planItem.status, 40) }]
|
|
206
290
|
: [];
|
|
207
291
|
}).slice(0, 12) : generated.items;
|
|
292
|
+
const tools = Array.isArray(item.tools) ? item.tools.filter((tool) => typeof tool === 'string').map((tool) => boundedString(tool, 120)).slice(0, 8) : generated.tools;
|
|
208
293
|
return {
|
|
294
|
+
name: boundedString(item.name, 120) || generated.name,
|
|
295
|
+
action: boundedString(item.action, 120) || generated.action,
|
|
209
296
|
category: TOOL_CATEGORIES.has(item.category) ? item.category : generated.category,
|
|
210
297
|
detail: TOOL_DETAILS.has(item.detail) ? item.detail : generated.detail,
|
|
211
298
|
target: boundedString(item.target, 300) || generated.target,
|
|
@@ -217,6 +304,7 @@ function readToolPresentation(value, entry) {
|
|
|
217
304
|
preview: boundedString(item.preview, MAX_TOOL_PREVIEW_CHARS) || generated.preview,
|
|
218
305
|
fields,
|
|
219
306
|
items,
|
|
307
|
+
tools,
|
|
220
308
|
exitCode: nullableNumber(item.exitCode) ?? generated.exitCode,
|
|
221
309
|
durationMs: nullableNumber(item.durationMs) ?? generated.durationMs,
|
|
222
310
|
additions: nullableNumber(item.additions) ?? generated.additions,
|
|
@@ -505,16 +593,9 @@ export function groupConversation(entries) {
|
|
|
505
593
|
|
|
506
594
|
export function toolCategory(entry) {
|
|
507
595
|
if (TOOL_CATEGORIES.has(entry.presentation?.category)) return entry.presentation.category;
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
if (/edit|write|patch|replace|create_file/.test(name)) return 'edit';
|
|
512
|
-
if (/test|typecheck|lint|build/.test(name)) return 'test';
|
|
513
|
-
if (/terminal|bash|shell|command|exec/.test(name)) return /\b(test|typecheck|lint|build)\b/i.test(entry.arguments ?? '') ? 'test' : 'command';
|
|
514
|
-
if (/browser|web|fetch|url/.test(name)) return 'web';
|
|
515
|
-
if (/update.?plan|todo|checklist/.test(name)) return 'plan';
|
|
516
|
-
if (/subagent|spawn.?agent|delegate|^task$/.test(name)) return 'agent';
|
|
517
|
-
return 'other';
|
|
596
|
+
const envelope = toolEnvelope(entry);
|
|
597
|
+
const command = firstString(envelope.args, ['command', 'cmd']) || sourceString(envelope.source, ['command', 'cmd']);
|
|
598
|
+
return classifyTool(envelope.name, command || envelope.source || entry.arguments || '');
|
|
518
599
|
}
|
|
519
600
|
|
|
520
601
|
export function toolTarget(argumentsText) {
|
|
@@ -543,7 +624,8 @@ export function activitySummary(entries) {
|
|
|
543
624
|
if (pending) return `${entries.length} actions in progress`;
|
|
544
625
|
if (failed) return `${entries.length} actions · ${failed} failed`;
|
|
545
626
|
if ([...counts.keys()].every((key) => key === 'read' || key === 'search')) return `Explored ${entries.length} ${entries.length === 1 ? 'item' : 'items'}`;
|
|
546
|
-
|
|
627
|
+
const nouns = { read: ['read', 'reads'], search: ['search', 'searches'], edit: ['edit', 'edits'], command: ['command', 'commands'], test: ['test run', 'test runs'], web: ['web action', 'web actions'], agent: ['agent action', 'agent actions'], plan: ['plan update', 'plan updates'], other: ['action', 'actions'] };
|
|
628
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([category, count]) => `${count} ${nouns[category][count === 1 ? 0 : 1]}`).join(' · ');
|
|
547
629
|
}
|
|
548
630
|
|
|
549
631
|
export function canContinueHere(state) {
|
package/embed.mjs
CHANGED
|
@@ -88,10 +88,10 @@ function boundedString(value, max) {
|
|
|
88
88
|
if (typeof value !== "string") return "";
|
|
89
89
|
return value.length <= max ? value : `${value.slice(0, max)}\u2026`;
|
|
90
90
|
}
|
|
91
|
-
function
|
|
91
|
+
function argumentValue(argumentsText) {
|
|
92
92
|
if (!argumentsText) return null;
|
|
93
93
|
try {
|
|
94
|
-
return
|
|
94
|
+
return JSON.parse(argumentsText);
|
|
95
95
|
} catch {
|
|
96
96
|
return null;
|
|
97
97
|
}
|
|
@@ -102,6 +102,33 @@ function firstString(source, keys) {
|
|
|
102
102
|
}
|
|
103
103
|
return "";
|
|
104
104
|
}
|
|
105
|
+
function decodedLiteral(value) {
|
|
106
|
+
if (!value) return "";
|
|
107
|
+
if (value.startsWith('"')) {
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(value);
|
|
110
|
+
} catch {
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return value.slice(1, -1).replaceAll("\\n", "\n").replaceAll("\\t", " ").replaceAll("\\r", "\r").replaceAll("\\`", "`").replaceAll("\\'", "'").replaceAll("\\\\", "\\");
|
|
115
|
+
}
|
|
116
|
+
function sourceString(source, keys) {
|
|
117
|
+
if (!source) return "";
|
|
118
|
+
const names = keys.join("|");
|
|
119
|
+
const match = new RegExp(`\\b(?:${names})\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
|
|
120
|
+
return decodedLiteral(match?.[1]);
|
|
121
|
+
}
|
|
122
|
+
function toolEnvelope(entry) {
|
|
123
|
+
const value = argumentValue(entry.arguments);
|
|
124
|
+
const source = typeof value === "string" ? value : "";
|
|
125
|
+
const tools = source ? [...new Set([...source.matchAll(/\btools\.([A-Za-z0-9_]+)/g)].map((match) => match[1]))].slice(0, 8) : [];
|
|
126
|
+
const name = tools.length === 1 ? tools[0] : entry.label ?? "tool";
|
|
127
|
+
return { args: record(value), source, tools, name };
|
|
128
|
+
}
|
|
129
|
+
function patchPath(source) {
|
|
130
|
+
return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]+)/.exec(source)?.[1]?.trim() ?? "";
|
|
131
|
+
}
|
|
105
132
|
function explicitNumber(sources, keys) {
|
|
106
133
|
for (const source of sources) {
|
|
107
134
|
for (const key of keys) {
|
|
@@ -135,7 +162,7 @@ function planItems(args) {
|
|
|
135
162
|
return [{ label: boundedString(label, 300), status: firstString(value, ["status"]) }];
|
|
136
163
|
}).slice(0, 12);
|
|
137
164
|
}
|
|
138
|
-
function editPreview(args, resultText) {
|
|
165
|
+
function editPreview(args, resultText, source) {
|
|
139
166
|
const direct = firstString(args, ["patch", "diff"]);
|
|
140
167
|
if (direct) return direct;
|
|
141
168
|
const oldText = firstString(args, ["old_string"]);
|
|
@@ -146,21 +173,63 @@ function editPreview(args, resultText) {
|
|
|
146
173
|
...newText.split("\n").map((line) => `+ ${line}`)
|
|
147
174
|
].join("\n");
|
|
148
175
|
}
|
|
176
|
+
const patch = /\*\*\* Begin Patch[\s\S]*?\*\*\* End Patch/.exec(source ?? "")?.[0];
|
|
177
|
+
if (patch) return patch;
|
|
149
178
|
return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? "") ? resultText : "";
|
|
150
179
|
}
|
|
180
|
+
function classifyTool(name, command) {
|
|
181
|
+
const normalized = name.toLocaleLowerCase();
|
|
182
|
+
if (/write_stdin|^wait$/.test(normalized)) return "command";
|
|
183
|
+
if (/update.?plan|todo|checklist|taskcreate|taskupdate/.test(normalized)) return "plan";
|
|
184
|
+
if (/search.?replace|edit|write|patch|replace|create_file|apply_patch/.test(normalized)) return "edit";
|
|
185
|
+
if (/read|view|open_file|list_dir/.test(normalized)) return "read";
|
|
186
|
+
if (/search|find|grep|glob|toolsearch/.test(normalized)) return "search";
|
|
187
|
+
if (/browser|web|fetch|url/.test(normalized)) return "web";
|
|
188
|
+
if (/agent|subagent|sendmessage|delegate|^task$/.test(normalized)) return "agent";
|
|
189
|
+
if (/test|typecheck|lint|build/.test(normalized)) return "test";
|
|
190
|
+
if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return /\b(test|typecheck|lint|build)\b/i.test(command) ? "test" : "command";
|
|
191
|
+
return "other";
|
|
192
|
+
}
|
|
193
|
+
function toolAction(status, category, name, tools) {
|
|
194
|
+
const position = status === "pending" ? 0 : status === "error" ? 2 : 1;
|
|
195
|
+
if (tools.length > 1) return [`Running ${tools.length} actions`, `Ran ${tools.length} actions`, `${tools.length} actions failed`][position];
|
|
196
|
+
const normalized = name.toLocaleLowerCase();
|
|
197
|
+
if (/sendmessage/.test(normalized)) return ["Messaging agent", "Messaged agent", "Agent message failed"][position];
|
|
198
|
+
if (/kill_command_or_subagent/.test(normalized)) return ["Stopping", "Stopped", "Stop failed"][position];
|
|
199
|
+
if (/get_command_or_subagent_output|write_stdin|^wait$/.test(normalized)) return ["Waiting for", "Checked", "Check failed"][position];
|
|
200
|
+
const actions = {
|
|
201
|
+
read: ["Reading", "Read", "Read failed"],
|
|
202
|
+
search: ["Searching", "Searched", "Search failed"],
|
|
203
|
+
edit: ["Editing", "Edited", "Edit failed"],
|
|
204
|
+
command: ["Running command", "Ran command", "Command failed"],
|
|
205
|
+
test: ["Running tests", "Ran tests", "Tests failed"],
|
|
206
|
+
web: ["Browsing", "Browsed", "Browser action failed"],
|
|
207
|
+
agent: ["Starting agent", "Started agent", "Agent failed"],
|
|
208
|
+
plan: ["Updating plan", "Updated plan", "Plan update failed"]
|
|
209
|
+
};
|
|
210
|
+
return actions[category]?.[position] ?? (status === "error" ? `${name} failed` : name.replaceAll(/[_-]+/g, " "));
|
|
211
|
+
}
|
|
151
212
|
function createToolPresentation(entry) {
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
const command = firstString(args, ["command", "cmd"]);
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
const
|
|
213
|
+
const envelope = toolEnvelope(entry);
|
|
214
|
+
const args = envelope.args;
|
|
215
|
+
const command = firstString(args, ["command", "cmd"]) || sourceString(envelope.source, ["command", "cmd"]);
|
|
216
|
+
const category = classifyTool(envelope.name, command || envelope.source || entry.arguments || "");
|
|
217
|
+
const path = firstString(args, ["file_path", "target_file", "target_directory", "path"]) || sourceString(envelope.source, ["file_path", "target_file", "target_directory", "path"]) || patchPath(envelope.source);
|
|
218
|
+
const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.source, ["query", "pattern", "q"]);
|
|
219
|
+
const url = firstString(args, ["url"]) || sourceString(envelope.source, ["url", "ref_id"]);
|
|
220
|
+
const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.source, ["subject", "description", "summary", "task", "prompt"]);
|
|
221
|
+
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" : "";
|
|
222
|
+
const items = planItems(args);
|
|
223
|
+
const taskId = firstString(args, ["taskId", "task_id"]);
|
|
224
|
+
const planTarget = category === "plan" ? items.length ? `${items.length} ${items.length === 1 ? "item" : "items"}` : taskId ? `task ${taskId}` : "" : "";
|
|
225
|
+
const target = path || command || query || url || subject || background || planTarget || (envelope.tools.length > 1 ? `${envelope.tools.length} coordinated actions` : toolTarget(entry.arguments));
|
|
226
|
+
const previewSource = category === "edit" ? editPreview(args, entry.resultText, envelope.source) : entry.resultText ?? "";
|
|
161
227
|
const result = record(entry.resultContent);
|
|
162
228
|
const metadata = record(entry.metadata);
|
|
229
|
+
const resultMetadata = record(result?.metadata);
|
|
163
230
|
return {
|
|
231
|
+
name: boundedString(envelope.name, 120),
|
|
232
|
+
action: boundedString(toolAction(entry.status ?? "completed", category, envelope.name, envelope.tools), 120),
|
|
164
233
|
category,
|
|
165
234
|
detail: toolDetail(category),
|
|
166
235
|
target: boundedString(target, 300),
|
|
@@ -171,12 +240,13 @@ function createToolPresentation(entry) {
|
|
|
171
240
|
subject: boundedString(subject, MAX_TOOL_FIELD_CHARS),
|
|
172
241
|
preview: boundedString(previewSource, MAX_TOOL_PREVIEW_CHARS),
|
|
173
242
|
fields: usefulToolFields(args),
|
|
174
|
-
items
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
243
|
+
items,
|
|
244
|
+
tools: envelope.tools,
|
|
245
|
+
exitCode: explicitNumber([result, resultMetadata, metadata], ["exit_code", "exitCode", "pi_bash_exit_code"]),
|
|
246
|
+
durationMs: explicitNumber([result, resultMetadata, metadata], ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs", "totalDurationMs"]),
|
|
247
|
+
additions: explicitNumber([result, resultMetadata, metadata], ["additions", "lines_added"]),
|
|
248
|
+
deletions: explicitNumber([result, resultMetadata, metadata], ["deletions", "lines_removed"]),
|
|
249
|
+
matches: explicitNumber([result, resultMetadata, metadata], ["matches", "match_count", "result_count"])
|
|
180
250
|
};
|
|
181
251
|
}
|
|
182
252
|
function readToolPresentation(value, entry) {
|
|
@@ -191,7 +261,10 @@ function readToolPresentation(value, entry) {
|
|
|
191
261
|
const planItem = record(raw);
|
|
192
262
|
return planItem && typeof planItem.label === "string" ? [{ label: boundedString(planItem.label, 300), status: boundedString(planItem.status, 40) }] : [];
|
|
193
263
|
}).slice(0, 12) : generated.items;
|
|
264
|
+
const tools = Array.isArray(item.tools) ? item.tools.filter((tool) => typeof tool === "string").map((tool) => boundedString(tool, 120)).slice(0, 8) : generated.tools;
|
|
194
265
|
return {
|
|
266
|
+
name: boundedString(item.name, 120) || generated.name,
|
|
267
|
+
action: boundedString(item.action, 120) || generated.action,
|
|
195
268
|
category: TOOL_CATEGORIES.has(item.category) ? item.category : generated.category,
|
|
196
269
|
detail: TOOL_DETAILS.has(item.detail) ? item.detail : generated.detail,
|
|
197
270
|
target: boundedString(item.target, 300) || generated.target,
|
|
@@ -203,6 +276,7 @@ function readToolPresentation(value, entry) {
|
|
|
203
276
|
preview: boundedString(item.preview, MAX_TOOL_PREVIEW_CHARS) || generated.preview,
|
|
204
277
|
fields,
|
|
205
278
|
items,
|
|
279
|
+
tools,
|
|
206
280
|
exitCode: nullableNumber(item.exitCode) ?? generated.exitCode,
|
|
207
281
|
durationMs: nullableNumber(item.durationMs) ?? generated.durationMs,
|
|
208
282
|
additions: nullableNumber(item.additions) ?? generated.additions,
|
|
@@ -436,16 +510,9 @@ function groupConversation(entries) {
|
|
|
436
510
|
}
|
|
437
511
|
function toolCategory(entry) {
|
|
438
512
|
if (TOOL_CATEGORIES.has(entry.presentation?.category)) return entry.presentation.category;
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if (/edit|write|patch|replace|create_file/.test(name)) return "edit";
|
|
443
|
-
if (/test|typecheck|lint|build/.test(name)) return "test";
|
|
444
|
-
if (/terminal|bash|shell|command|exec/.test(name)) return /\b(test|typecheck|lint|build)\b/i.test(entry.arguments ?? "") ? "test" : "command";
|
|
445
|
-
if (/browser|web|fetch|url/.test(name)) return "web";
|
|
446
|
-
if (/update.?plan|todo|checklist/.test(name)) return "plan";
|
|
447
|
-
if (/subagent|spawn.?agent|delegate|^task$/.test(name)) return "agent";
|
|
448
|
-
return "other";
|
|
513
|
+
const envelope = toolEnvelope(entry);
|
|
514
|
+
const command = firstString(envelope.args, ["command", "cmd"]) || sourceString(envelope.source, ["command", "cmd"]);
|
|
515
|
+
return classifyTool(envelope.name, command || envelope.source || entry.arguments || "");
|
|
449
516
|
}
|
|
450
517
|
function toolTarget(argumentsText) {
|
|
451
518
|
if (!argumentsText) return "";
|
|
@@ -471,7 +538,8 @@ function activitySummary(entries) {
|
|
|
471
538
|
if (pending) return `${entries.length} actions in progress`;
|
|
472
539
|
if (failed) return `${entries.length} actions \xB7 ${failed} failed`;
|
|
473
540
|
if ([...counts.keys()].every((key) => key === "read" || key === "search")) return `Explored ${entries.length} ${entries.length === 1 ? "item" : "items"}`;
|
|
474
|
-
|
|
541
|
+
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"] };
|
|
542
|
+
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 ");
|
|
475
543
|
}
|
|
476
544
|
function canContinueHere(state) {
|
|
477
545
|
if (state.mode !== "mirror" || state.canSend) return false;
|
|
@@ -694,7 +762,8 @@ function ToolIcon({ category }) {
|
|
|
694
762
|
const Glyph = TOOL_ICONS[category] ?? TOOL_ICONS.other;
|
|
695
763
|
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
764
|
}
|
|
697
|
-
function
|
|
765
|
+
function toolAction2(entry, category, presentation) {
|
|
766
|
+
if (presentation?.action) return presentation.action;
|
|
698
767
|
const status = entry.status ?? "completed";
|
|
699
768
|
const actions = {
|
|
700
769
|
read: ["Reading", "Read", "Read failed"],
|
|
@@ -748,6 +817,16 @@ function ToolMetrics({ presentation }) {
|
|
|
748
817
|
].filter(Boolean);
|
|
749
818
|
return metrics.length ? /* @__PURE__ */ jsx3("div", { class: "scui-tool-metrics", children: metrics.map((metric) => /* @__PURE__ */ jsx3("span", { children: metric }, metric)) }) : null;
|
|
750
819
|
}
|
|
820
|
+
function PendingElapsed({ now }) {
|
|
821
|
+
const clock = now ?? Date.now;
|
|
822
|
+
const started = useRef2(clock());
|
|
823
|
+
const [elapsed, setElapsed] = useState2(0);
|
|
824
|
+
useEffect2(() => {
|
|
825
|
+
const timer = setInterval(() => setElapsed(Math.max(0, clock() - started.current)), 1e3);
|
|
826
|
+
return () => clearInterval(timer);
|
|
827
|
+
}, [clock]);
|
|
828
|
+
return /* @__PURE__ */ jsx3("small", { class: "scui-tool-elapsed", children: elapsed < 1e3 ? "now" : formatDuration(elapsed) });
|
|
829
|
+
}
|
|
751
830
|
function LinePreview({ value, kind }) {
|
|
752
831
|
if (!value) return null;
|
|
753
832
|
return /* @__PURE__ */ jsx3("ol", { class: "scui-code-preview", "data-kind": kind, children: stripAnsi(value).split("\n").map((line, index) => {
|
|
@@ -805,16 +884,31 @@ function ToolPreview({ presentation, entry }) {
|
|
|
805
884
|
presentation.url ? /* @__PURE__ */ jsx3("code", { children: presentation.url }) : null,
|
|
806
885
|
presentation.preview ? /* @__PURE__ */ jsx3("p", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx3("small", { children: pending ? "Waiting for the page" : "No page summary returned" })
|
|
807
886
|
] });
|
|
808
|
-
if (presentation.detail === "agent") return /* @__PURE__ */
|
|
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
|
-
] });
|
|
887
|
+
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" }) });
|
|
812
888
|
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
889
|
/* @__PURE__ */ jsx3("i", { "aria-hidden": "true" }),
|
|
814
890
|
/* @__PURE__ */ jsx3("span", { children: item.label })
|
|
815
891
|
] }, `${item.label}:${index}`)) });
|
|
816
892
|
return presentation.preview ? /* @__PURE__ */ jsx3("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
|
|
817
893
|
}
|
|
894
|
+
function ToolActions({ presentation, adapter }) {
|
|
895
|
+
const [copied, setCopied] = useState2(false);
|
|
896
|
+
const reset = useRef2(null);
|
|
897
|
+
useEffect2(() => () => clearTimeout(reset.current), []);
|
|
898
|
+
if (!adapter?.copyText) return null;
|
|
899
|
+
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;
|
|
900
|
+
const copy = async () => {
|
|
901
|
+
await adapter.copyText(action[1]);
|
|
902
|
+
setCopied(true);
|
|
903
|
+
clearTimeout(reset.current);
|
|
904
|
+
reset.current = setTimeout(() => setCopied(false), 1500);
|
|
905
|
+
};
|
|
906
|
+
return action ? /* @__PURE__ */ jsx3("div", { class: "scui-tool-actions", children: /* @__PURE__ */ jsx3("button", { type: "button", onClick: copy, children: copied ? "Copied" : action[0] }) }) : null;
|
|
907
|
+
}
|
|
908
|
+
function ToolStack({ tools }) {
|
|
909
|
+
if (tools.length < 2) return null;
|
|
910
|
+
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)) });
|
|
911
|
+
}
|
|
818
912
|
function TechnicalDetails({ entry }) {
|
|
819
913
|
if (!entry.arguments && !entry.resultText) return null;
|
|
820
914
|
return /* @__PURE__ */ jsxs2("details", { class: "scui-tool-technical", children: [
|
|
@@ -852,35 +946,42 @@ function TranscriptEntry({ entry, state, adapter }) {
|
|
|
852
946
|
entry.truncated ? /* @__PURE__ */ jsx3("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null
|
|
853
947
|
] });
|
|
854
948
|
}
|
|
855
|
-
function ToolRow({ entry, workspace, open = false }) {
|
|
949
|
+
function ToolRow({ entry, workspace, open = false, adapter }) {
|
|
856
950
|
const presentation = entry.presentation ?? createToolPresentation(entry);
|
|
951
|
+
const [expanded, setExpanded] = useState2(open || entry.status === "pending");
|
|
952
|
+
useEffect2(() => {
|
|
953
|
+
if (entry.status === "pending") setExpanded(true);
|
|
954
|
+
}, [entry.status]);
|
|
857
955
|
const target = compactToolTarget(presentation.target, workspace);
|
|
858
956
|
const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
|
|
859
957
|
const category = presentation.category ?? toolCategory(entry);
|
|
860
958
|
const summary = /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
861
959
|
/* @__PURE__ */ jsx3(ToolIcon, { category }),
|
|
862
|
-
/* @__PURE__ */ jsx3("strong", { children:
|
|
960
|
+
/* @__PURE__ */ jsx3("strong", { children: toolAction2(entry, category, presentation) }),
|
|
863
961
|
target ? /* @__PURE__ */ jsx3("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
|
|
864
962
|
/* @__PURE__ */ jsx3("span", { class: "scui-spacer" }),
|
|
963
|
+
entry.status === "pending" ? /* @__PURE__ */ jsx3(PendingElapsed, { now: adapter?.now }) : null,
|
|
865
964
|
/* @__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
965
|
hasDetail ? /* @__PURE__ */ jsx3("span", { class: "scui-tool-chevron", "aria-hidden": "true", children: "\u203A" }) : null
|
|
867
966
|
] });
|
|
868
967
|
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:
|
|
968
|
+
return /* @__PURE__ */ jsxs2("details", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, open: expanded, onToggle: (event) => setExpanded(event.currentTarget.open), children: [
|
|
870
969
|
/* @__PURE__ */ jsx3("summary", { class: "scui-tool-head", children: summary }),
|
|
871
970
|
/* @__PURE__ */ jsxs2("div", { class: "scui-tool-detail", children: [
|
|
872
971
|
/* @__PURE__ */ jsx3(ToolMetrics, { presentation }),
|
|
972
|
+
/* @__PURE__ */ jsx3(ToolStack, { tools: presentation.tools ?? [] }),
|
|
873
973
|
/* @__PURE__ */ jsx3(ToolPreview, { presentation, entry }),
|
|
874
974
|
presentation.fields.length ? /* @__PURE__ */ jsx3("dl", { class: "scui-tool-fields", children: presentation.fields.map((field) => /* @__PURE__ */ jsxs2("div", { children: [
|
|
875
975
|
/* @__PURE__ */ jsx3("dt", { children: field.label }),
|
|
876
976
|
/* @__PURE__ */ jsx3("dd", { children: field.value })
|
|
877
977
|
] }, field.label)) }) : null,
|
|
978
|
+
/* @__PURE__ */ jsx3(ToolActions, { presentation, adapter }),
|
|
878
979
|
/* @__PURE__ */ jsx3(TechnicalDetails, { entry })
|
|
879
980
|
] })
|
|
880
981
|
] });
|
|
881
982
|
}
|
|
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 }) });
|
|
983
|
+
function ActivityGroup({ entries, state, adapter }) {
|
|
984
|
+
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 }) });
|
|
884
985
|
const active = entries.some((entry) => entry.status === "pending");
|
|
885
986
|
const [open, setOpen] = useState2(active);
|
|
886
987
|
const id = useId();
|
|
@@ -898,7 +999,7 @@ function ActivityGroup({ entries, state }) {
|
|
|
898
999
|
entries.length
|
|
899
1000
|
] })
|
|
900
1001
|
] }),
|
|
901
|
-
open ? /* @__PURE__ */ jsx3("div", { id, children: entries.map((entry) => /* @__PURE__ */ jsx3(ToolRow, { entry, workspace: state.workspace }, entry.id)) }) : null
|
|
1002
|
+
open ? /* @__PURE__ */ jsx3("div", { id, children: entries.map((entry) => /* @__PURE__ */ jsx3(ToolRow, { entry, workspace: state.workspace, adapter }, entry.id)) }) : null
|
|
902
1003
|
] });
|
|
903
1004
|
}
|
|
904
1005
|
function TaskPlan({ plan }) {
|
package/index.d.ts
CHANGED
|
@@ -80,6 +80,8 @@ export interface TranscriptEntryModel {
|
|
|
80
80
|
export type ToolCategory = 'read' | 'search' | 'edit' | 'command' | 'test' | 'web' | 'agent' | 'plan' | 'other';
|
|
81
81
|
|
|
82
82
|
export interface ToolPresentationModel {
|
|
83
|
+
name: string;
|
|
84
|
+
action: string;
|
|
83
85
|
category: ToolCategory;
|
|
84
86
|
detail: 'file' | 'matches' | 'diff' | 'terminal' | 'web' | 'agent' | 'plan' | 'fields';
|
|
85
87
|
target: string;
|
|
@@ -91,6 +93,7 @@ export interface ToolPresentationModel {
|
|
|
91
93
|
preview: string;
|
|
92
94
|
fields: Array<{ label: string; value: string }>;
|
|
93
95
|
items: Array<{ label: string; status: string }>;
|
|
96
|
+
tools: string[];
|
|
94
97
|
exitCode: number | null;
|
|
95
98
|
durationMs: number | null;
|
|
96
99
|
additions: number | null;
|
|
@@ -266,6 +269,7 @@ export interface ToolRowProps {
|
|
|
266
269
|
entry: TranscriptEntryModel;
|
|
267
270
|
workspace?: string;
|
|
268
271
|
open?: boolean;
|
|
272
|
+
adapter?: Pick<UiAdapter, 'copyText' | 'now'>;
|
|
269
273
|
}
|
|
270
274
|
|
|
271
275
|
export interface TaskPlanProps {
|
|
@@ -314,7 +318,7 @@ export function groupConversation(entries: readonly TranscriptEntryModel[]): Arr
|
|
|
314
318
|
| { kind: 'activity'; id: string; entries: TranscriptEntryModel[] }
|
|
315
319
|
>;
|
|
316
320
|
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;
|
|
321
|
+
export function createToolPresentation(entry: Pick<TranscriptEntryModel, 'label' | 'arguments' | 'resultText' | 'status'> & { resultContent?: unknown; metadata?: Record<string, string> }): ToolPresentationModel;
|
|
318
322
|
export function toolTarget(argumentsText?: string): string;
|
|
319
323
|
export function compactToolTarget(target: string, workspace: string): string;
|
|
320
324
|
export function activitySummary(entries: readonly TranscriptEntryModel[]): string;
|