@volter-ai-dev/supercode-ui 0.1.15 → 0.1.17
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 +157 -14
- package/conversation.mjs +157 -14
- package/core.mjs +108 -16
- package/embed.mjs +157 -14
- package/messenger.mjs +157 -14
- package/package.json +1 -1
package/components.mjs
CHANGED
|
@@ -113,18 +113,116 @@ function decodedLiteral(value) {
|
|
|
113
113
|
function sourceString(source, keys) {
|
|
114
114
|
if (!source) return "";
|
|
115
115
|
const names = keys.join("|");
|
|
116
|
-
const match = new RegExp(
|
|
116
|
+
const match = new RegExp(`(?:^|[,{\\s])["']?(?:${names})["']?\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
|
|
117
117
|
return decodedLiteral(match?.[1]);
|
|
118
118
|
}
|
|
119
|
+
function callArgumentSource(source, open) {
|
|
120
|
+
let depth = 1;
|
|
121
|
+
let quote = "";
|
|
122
|
+
let escaped = false;
|
|
123
|
+
let lineComment = false;
|
|
124
|
+
let blockComment = false;
|
|
125
|
+
for (let index = open + 1; index < source.length; index += 1) {
|
|
126
|
+
const char = source[index];
|
|
127
|
+
const next = source[index + 1];
|
|
128
|
+
if (lineComment) {
|
|
129
|
+
if (char === "\n") lineComment = false;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (blockComment) {
|
|
133
|
+
if (char === "*" && next === "/") {
|
|
134
|
+
blockComment = false;
|
|
135
|
+
index += 1;
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (quote) {
|
|
140
|
+
if (escaped) escaped = false;
|
|
141
|
+
else if (char === "\\") escaped = true;
|
|
142
|
+
else if (char === quote) quote = "";
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (char === "/" && next === "/") {
|
|
146
|
+
lineComment = true;
|
|
147
|
+
index += 1;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (char === "/" && next === "*") {
|
|
151
|
+
blockComment = true;
|
|
152
|
+
index += 1;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
156
|
+
quote = char;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (char === "(") depth += 1;
|
|
160
|
+
else if (char === ")" && (depth -= 1) === 0) return source.slice(open + 1, index);
|
|
161
|
+
}
|
|
162
|
+
return source.slice(open + 1);
|
|
163
|
+
}
|
|
164
|
+
function toolCalls(source) {
|
|
165
|
+
const calls = [];
|
|
166
|
+
let quote = "";
|
|
167
|
+
let escaped = false;
|
|
168
|
+
let lineComment = false;
|
|
169
|
+
let blockComment = false;
|
|
170
|
+
for (let index = 0; index < source.length && calls.length < 8; index += 1) {
|
|
171
|
+
const char = source[index];
|
|
172
|
+
const next = source[index + 1];
|
|
173
|
+
if (lineComment) {
|
|
174
|
+
if (char === "\n") lineComment = false;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (blockComment) {
|
|
178
|
+
if (char === "*" && next === "/") {
|
|
179
|
+
blockComment = false;
|
|
180
|
+
index += 1;
|
|
181
|
+
}
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (quote) {
|
|
185
|
+
if (escaped) escaped = false;
|
|
186
|
+
else if (char === "\\") escaped = true;
|
|
187
|
+
else if (char === quote) quote = "";
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (char === "/" && next === "/") {
|
|
191
|
+
lineComment = true;
|
|
192
|
+
index += 1;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (char === "/" && next === "*") {
|
|
196
|
+
blockComment = true;
|
|
197
|
+
index += 1;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
201
|
+
quote = char;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (!source.startsWith("tools.", index) || /[A-Za-z0-9_$]/.test(source[index - 1] ?? "")) continue;
|
|
205
|
+
const nameStart = index + 6;
|
|
206
|
+
let nameEnd = nameStart;
|
|
207
|
+
while (/[A-Za-z0-9_$]/.test(source[nameEnd] ?? "")) nameEnd += 1;
|
|
208
|
+
let open = nameEnd;
|
|
209
|
+
while (/\s/.test(source[open] ?? "")) open += 1;
|
|
210
|
+
if (nameEnd === nameStart || source[open] !== "(") continue;
|
|
211
|
+
calls.push({ name: source.slice(nameStart, nameEnd), arguments: callArgumentSource(source, open) });
|
|
212
|
+
index = open;
|
|
213
|
+
}
|
|
214
|
+
return calls;
|
|
215
|
+
}
|
|
119
216
|
function toolEnvelope(entry) {
|
|
120
217
|
const value = argumentValue(entry.arguments);
|
|
121
|
-
const source = typeof value === "string" ? value : "";
|
|
122
|
-
const
|
|
218
|
+
const source = typeof value === "string" ? value : value === null ? entry.arguments ?? "" : "";
|
|
219
|
+
const calls = source ? toolCalls(source) : [];
|
|
220
|
+
const tools = [...new Set(calls.map((call) => call.name))];
|
|
123
221
|
const name = tools.length === 1 ? tools[0] : entry.label ?? "tool";
|
|
124
|
-
return { args: record(value), source, tools, name };
|
|
222
|
+
return { args: record(value), source, callSource: calls[0]?.arguments ?? "", tools, name };
|
|
125
223
|
}
|
|
126
224
|
function patchPath(source) {
|
|
127
|
-
return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]
|
|
225
|
+
return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]*?)(?=\\[nr]|[\r\n]|$)/.exec(source)?.[1]?.trim() ?? "";
|
|
128
226
|
}
|
|
129
227
|
function explicitNumber(sources, keys) {
|
|
130
228
|
for (const source of sources) {
|
|
@@ -174,6 +272,50 @@ function editPreview(args, resultText, source) {
|
|
|
174
272
|
if (patch) return patch;
|
|
175
273
|
return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? "") ? resultText : "";
|
|
176
274
|
}
|
|
275
|
+
function toolResultEnvelope(resultText) {
|
|
276
|
+
const match = /^Script (?:completed|failed)\r?\nWall time ([0-9.]+) seconds\r?\nOutput:\r?\n([\s\S]*)$/.exec(resultText ?? "");
|
|
277
|
+
if (!match) return { preview: resultText ?? "", value: null, durationMs: null };
|
|
278
|
+
const durationMs = Number(match[1]) * 1e3;
|
|
279
|
+
try {
|
|
280
|
+
const value = record(JSON.parse(match[2]));
|
|
281
|
+
return {
|
|
282
|
+
preview: typeof value?.output === "string" ? value.output : match[2],
|
|
283
|
+
value,
|
|
284
|
+
durationMs: Number.isFinite(durationMs) ? durationMs : null
|
|
285
|
+
};
|
|
286
|
+
} catch {
|
|
287
|
+
return { preview: match[2], value: null, durationMs: Number.isFinite(durationMs) ? durationMs : null };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function verificationCommand(command) {
|
|
291
|
+
let shell = "";
|
|
292
|
+
let quote = "";
|
|
293
|
+
let escaped = false;
|
|
294
|
+
for (const char of command) {
|
|
295
|
+
if (escaped) {
|
|
296
|
+
escaped = false;
|
|
297
|
+
shell += quote ? " " : char;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (char === "\\") {
|
|
301
|
+
escaped = true;
|
|
302
|
+
shell += quote ? " " : char;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (quote) {
|
|
306
|
+
if (char === quote) quote = "";
|
|
307
|
+
shell += " ";
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
311
|
+
quote = char;
|
|
312
|
+
shell += " ";
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
shell += char;
|
|
316
|
+
}
|
|
317
|
+
return /(?:^|[;&|]\s*)(?:(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|typecheck|lint|build)|cargo\s+(?:test|clippy|build|check|fmt)|npx\s+(?:vitest|tsc|eslint|playwright)|pytest\b|go\s+test\b)/i.test(shell);
|
|
318
|
+
}
|
|
177
319
|
function classifyTool(name, command) {
|
|
178
320
|
const normalized = name.toLocaleLowerCase();
|
|
179
321
|
if (/write_stdin|^wait$/.test(normalized)) return "command";
|
|
@@ -184,7 +326,7 @@ function classifyTool(name, command) {
|
|
|
184
326
|
if (/browser|web|fetch|url/.test(normalized)) return "web";
|
|
185
327
|
if (/agent|subagent|sendmessage|delegate|^task$/.test(normalized)) return "agent";
|
|
186
328
|
if (/test|typecheck|lint|build/.test(normalized)) return "test";
|
|
187
|
-
if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return
|
|
329
|
+
if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return verificationCommand(command) ? "test" : "command";
|
|
188
330
|
return "other";
|
|
189
331
|
}
|
|
190
332
|
function toolAction(status, category, name, tools) {
|
|
@@ -209,18 +351,19 @@ function toolAction(status, category, name, tools) {
|
|
|
209
351
|
function createToolPresentation(entry) {
|
|
210
352
|
const envelope = toolEnvelope(entry);
|
|
211
353
|
const args = envelope.args;
|
|
212
|
-
const
|
|
354
|
+
const outcome = toolResultEnvelope(entry.resultText);
|
|
355
|
+
const command = firstString(args, ["command", "cmd"]) || sourceString(envelope.callSource, ["command", "cmd"]);
|
|
213
356
|
const category = classifyTool(envelope.name, command || envelope.source || entry.arguments || "");
|
|
214
|
-
const path = firstString(args, ["file_path", "target_file", "target_directory", "path"]) || sourceString(envelope.
|
|
215
|
-
const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.
|
|
216
|
-
const url = firstString(args, ["url"]) || sourceString(envelope.
|
|
217
|
-
const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.
|
|
357
|
+
const path = firstString(args, ["file_path", "target_file", "target_directory", "path"]) || sourceString(envelope.callSource, ["file_path", "target_file", "target_directory", "path"]) || patchPath(envelope.source);
|
|
358
|
+
const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.callSource, ["query", "pattern", "q"]);
|
|
359
|
+
const url = firstString(args, ["url"]) || sourceString(envelope.callSource, ["url", "ref_id"]);
|
|
360
|
+
const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.callSource, ["subject", "description", "summary", "task", "prompt"]);
|
|
218
361
|
const background = /get_command_or_subagent_output|kill_command_or_subagent/i.test(envelope.name) ? "background task" : /write_stdin|^wait$/i.test(envelope.name) ? "background command" : "";
|
|
219
362
|
const items = planItems(args);
|
|
220
363
|
const taskId = firstString(args, ["taskId", "task_id"]);
|
|
221
364
|
const planTarget = category === "plan" ? items.length ? `${items.length} ${items.length === 1 ? "item" : "items"}` : taskId ? `task ${taskId}` : "" : "";
|
|
222
365
|
const target = path || command || query || url || subject || background || planTarget || (envelope.tools.length > 1 ? `${envelope.tools.length} coordinated actions` : toolTarget(entry.arguments));
|
|
223
|
-
const previewSource = category === "edit" ? editPreview(args,
|
|
366
|
+
const previewSource = category === "edit" ? editPreview(args, outcome.preview, envelope.source) : outcome.preview;
|
|
224
367
|
const result = record(entry.resultContent);
|
|
225
368
|
const metadata = record(entry.metadata);
|
|
226
369
|
const resultMetadata = record(result?.metadata);
|
|
@@ -239,8 +382,8 @@ function createToolPresentation(entry) {
|
|
|
239
382
|
fields: usefulToolFields(args),
|
|
240
383
|
items,
|
|
241
384
|
tools: envelope.tools,
|
|
242
|
-
exitCode: explicitNumber([result, resultMetadata, metadata], ["exit_code", "exitCode", "pi_bash_exit_code"]),
|
|
243
|
-
durationMs: explicitNumber([result, resultMetadata, metadata], ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs", "totalDurationMs"]),
|
|
385
|
+
exitCode: explicitNumber([outcome.value, result, resultMetadata, metadata], ["exit_code", "exitCode", "pi_bash_exit_code"]),
|
|
386
|
+
durationMs: explicitNumber([outcome.value, result, resultMetadata, metadata], ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs", "totalDurationMs"]) ?? outcome.durationMs,
|
|
244
387
|
additions: explicitNumber([result, resultMetadata, metadata], ["additions", "lines_added"]),
|
|
245
388
|
deletions: explicitNumber([result, resultMetadata, metadata], ["deletions", "lines_removed"]),
|
|
246
389
|
matches: explicitNumber([result, resultMetadata, metadata], ["matches", "match_count", "result_count"])
|
package/conversation.mjs
CHANGED
|
@@ -99,18 +99,116 @@ function decodedLiteral(value) {
|
|
|
99
99
|
function sourceString(source, keys) {
|
|
100
100
|
if (!source) return "";
|
|
101
101
|
const names = keys.join("|");
|
|
102
|
-
const match = new RegExp(
|
|
102
|
+
const match = new RegExp(`(?:^|[,{\\s])["']?(?:${names})["']?\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
|
|
103
103
|
return decodedLiteral(match?.[1]);
|
|
104
104
|
}
|
|
105
|
+
function callArgumentSource(source, open) {
|
|
106
|
+
let depth = 1;
|
|
107
|
+
let quote = "";
|
|
108
|
+
let escaped = false;
|
|
109
|
+
let lineComment = false;
|
|
110
|
+
let blockComment = false;
|
|
111
|
+
for (let index = open + 1; index < source.length; index += 1) {
|
|
112
|
+
const char = source[index];
|
|
113
|
+
const next = source[index + 1];
|
|
114
|
+
if (lineComment) {
|
|
115
|
+
if (char === "\n") lineComment = false;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (blockComment) {
|
|
119
|
+
if (char === "*" && next === "/") {
|
|
120
|
+
blockComment = false;
|
|
121
|
+
index += 1;
|
|
122
|
+
}
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (quote) {
|
|
126
|
+
if (escaped) escaped = false;
|
|
127
|
+
else if (char === "\\") escaped = true;
|
|
128
|
+
else if (char === quote) quote = "";
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (char === "/" && next === "/") {
|
|
132
|
+
lineComment = true;
|
|
133
|
+
index += 1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (char === "/" && next === "*") {
|
|
137
|
+
blockComment = true;
|
|
138
|
+
index += 1;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
142
|
+
quote = char;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (char === "(") depth += 1;
|
|
146
|
+
else if (char === ")" && (depth -= 1) === 0) return source.slice(open + 1, index);
|
|
147
|
+
}
|
|
148
|
+
return source.slice(open + 1);
|
|
149
|
+
}
|
|
150
|
+
function toolCalls(source) {
|
|
151
|
+
const calls = [];
|
|
152
|
+
let quote = "";
|
|
153
|
+
let escaped = false;
|
|
154
|
+
let lineComment = false;
|
|
155
|
+
let blockComment = false;
|
|
156
|
+
for (let index = 0; index < source.length && calls.length < 8; index += 1) {
|
|
157
|
+
const char = source[index];
|
|
158
|
+
const next = source[index + 1];
|
|
159
|
+
if (lineComment) {
|
|
160
|
+
if (char === "\n") lineComment = false;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (blockComment) {
|
|
164
|
+
if (char === "*" && next === "/") {
|
|
165
|
+
blockComment = false;
|
|
166
|
+
index += 1;
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (quote) {
|
|
171
|
+
if (escaped) escaped = false;
|
|
172
|
+
else if (char === "\\") escaped = true;
|
|
173
|
+
else if (char === quote) quote = "";
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (char === "/" && next === "/") {
|
|
177
|
+
lineComment = true;
|
|
178
|
+
index += 1;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (char === "/" && next === "*") {
|
|
182
|
+
blockComment = true;
|
|
183
|
+
index += 1;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
187
|
+
quote = char;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (!source.startsWith("tools.", index) || /[A-Za-z0-9_$]/.test(source[index - 1] ?? "")) continue;
|
|
191
|
+
const nameStart = index + 6;
|
|
192
|
+
let nameEnd = nameStart;
|
|
193
|
+
while (/[A-Za-z0-9_$]/.test(source[nameEnd] ?? "")) nameEnd += 1;
|
|
194
|
+
let open = nameEnd;
|
|
195
|
+
while (/\s/.test(source[open] ?? "")) open += 1;
|
|
196
|
+
if (nameEnd === nameStart || source[open] !== "(") continue;
|
|
197
|
+
calls.push({ name: source.slice(nameStart, nameEnd), arguments: callArgumentSource(source, open) });
|
|
198
|
+
index = open;
|
|
199
|
+
}
|
|
200
|
+
return calls;
|
|
201
|
+
}
|
|
105
202
|
function toolEnvelope(entry) {
|
|
106
203
|
const value = argumentValue(entry.arguments);
|
|
107
|
-
const source = typeof value === "string" ? value : "";
|
|
108
|
-
const
|
|
204
|
+
const source = typeof value === "string" ? value : value === null ? entry.arguments ?? "" : "";
|
|
205
|
+
const calls = source ? toolCalls(source) : [];
|
|
206
|
+
const tools = [...new Set(calls.map((call) => call.name))];
|
|
109
207
|
const name = tools.length === 1 ? tools[0] : entry.label ?? "tool";
|
|
110
|
-
return { args: record(value), source, tools, name };
|
|
208
|
+
return { args: record(value), source, callSource: calls[0]?.arguments ?? "", tools, name };
|
|
111
209
|
}
|
|
112
210
|
function patchPath(source) {
|
|
113
|
-
return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]
|
|
211
|
+
return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]*?)(?=\\[nr]|[\r\n]|$)/.exec(source)?.[1]?.trim() ?? "";
|
|
114
212
|
}
|
|
115
213
|
function explicitNumber(sources, keys) {
|
|
116
214
|
for (const source of sources) {
|
|
@@ -160,6 +258,50 @@ function editPreview(args, resultText, source) {
|
|
|
160
258
|
if (patch) return patch;
|
|
161
259
|
return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? "") ? resultText : "";
|
|
162
260
|
}
|
|
261
|
+
function toolResultEnvelope(resultText) {
|
|
262
|
+
const match = /^Script (?:completed|failed)\r?\nWall time ([0-9.]+) seconds\r?\nOutput:\r?\n([\s\S]*)$/.exec(resultText ?? "");
|
|
263
|
+
if (!match) return { preview: resultText ?? "", value: null, durationMs: null };
|
|
264
|
+
const durationMs = Number(match[1]) * 1e3;
|
|
265
|
+
try {
|
|
266
|
+
const value = record(JSON.parse(match[2]));
|
|
267
|
+
return {
|
|
268
|
+
preview: typeof value?.output === "string" ? value.output : match[2],
|
|
269
|
+
value,
|
|
270
|
+
durationMs: Number.isFinite(durationMs) ? durationMs : null
|
|
271
|
+
};
|
|
272
|
+
} catch {
|
|
273
|
+
return { preview: match[2], value: null, durationMs: Number.isFinite(durationMs) ? durationMs : null };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function verificationCommand(command) {
|
|
277
|
+
let shell = "";
|
|
278
|
+
let quote = "";
|
|
279
|
+
let escaped = false;
|
|
280
|
+
for (const char of command) {
|
|
281
|
+
if (escaped) {
|
|
282
|
+
escaped = false;
|
|
283
|
+
shell += quote ? " " : char;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (char === "\\") {
|
|
287
|
+
escaped = true;
|
|
288
|
+
shell += quote ? " " : char;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (quote) {
|
|
292
|
+
if (char === quote) quote = "";
|
|
293
|
+
shell += " ";
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
297
|
+
quote = char;
|
|
298
|
+
shell += " ";
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
shell += char;
|
|
302
|
+
}
|
|
303
|
+
return /(?:^|[;&|]\s*)(?:(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|typecheck|lint|build)|cargo\s+(?:test|clippy|build|check|fmt)|npx\s+(?:vitest|tsc|eslint|playwright)|pytest\b|go\s+test\b)/i.test(shell);
|
|
304
|
+
}
|
|
163
305
|
function classifyTool(name, command) {
|
|
164
306
|
const normalized = name.toLocaleLowerCase();
|
|
165
307
|
if (/write_stdin|^wait$/.test(normalized)) return "command";
|
|
@@ -170,7 +312,7 @@ function classifyTool(name, command) {
|
|
|
170
312
|
if (/browser|web|fetch|url/.test(normalized)) return "web";
|
|
171
313
|
if (/agent|subagent|sendmessage|delegate|^task$/.test(normalized)) return "agent";
|
|
172
314
|
if (/test|typecheck|lint|build/.test(normalized)) return "test";
|
|
173
|
-
if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return
|
|
315
|
+
if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return verificationCommand(command) ? "test" : "command";
|
|
174
316
|
return "other";
|
|
175
317
|
}
|
|
176
318
|
function toolAction(status, category, name, tools) {
|
|
@@ -195,18 +337,19 @@ function toolAction(status, category, name, tools) {
|
|
|
195
337
|
function createToolPresentation(entry) {
|
|
196
338
|
const envelope = toolEnvelope(entry);
|
|
197
339
|
const args = envelope.args;
|
|
198
|
-
const
|
|
340
|
+
const outcome = toolResultEnvelope(entry.resultText);
|
|
341
|
+
const command = firstString(args, ["command", "cmd"]) || sourceString(envelope.callSource, ["command", "cmd"]);
|
|
199
342
|
const category = classifyTool(envelope.name, command || envelope.source || entry.arguments || "");
|
|
200
|
-
const path = firstString(args, ["file_path", "target_file", "target_directory", "path"]) || sourceString(envelope.
|
|
201
|
-
const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.
|
|
202
|
-
const url = firstString(args, ["url"]) || sourceString(envelope.
|
|
203
|
-
const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.
|
|
343
|
+
const path = firstString(args, ["file_path", "target_file", "target_directory", "path"]) || sourceString(envelope.callSource, ["file_path", "target_file", "target_directory", "path"]) || patchPath(envelope.source);
|
|
344
|
+
const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.callSource, ["query", "pattern", "q"]);
|
|
345
|
+
const url = firstString(args, ["url"]) || sourceString(envelope.callSource, ["url", "ref_id"]);
|
|
346
|
+
const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.callSource, ["subject", "description", "summary", "task", "prompt"]);
|
|
204
347
|
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" : "";
|
|
205
348
|
const items = planItems(args);
|
|
206
349
|
const taskId = firstString(args, ["taskId", "task_id"]);
|
|
207
350
|
const planTarget = category === "plan" ? items.length ? `${items.length} ${items.length === 1 ? "item" : "items"}` : taskId ? `task ${taskId}` : "" : "";
|
|
208
351
|
const target = path || command || query || url || subject || background || planTarget || (envelope.tools.length > 1 ? `${envelope.tools.length} coordinated actions` : toolTarget(entry.arguments));
|
|
209
|
-
const previewSource = category === "edit" ? editPreview(args,
|
|
352
|
+
const previewSource = category === "edit" ? editPreview(args, outcome.preview, envelope.source) : outcome.preview;
|
|
210
353
|
const result = record(entry.resultContent);
|
|
211
354
|
const metadata = record(entry.metadata);
|
|
212
355
|
const resultMetadata = record(result?.metadata);
|
|
@@ -225,8 +368,8 @@ function createToolPresentation(entry) {
|
|
|
225
368
|
fields: usefulToolFields(args),
|
|
226
369
|
items,
|
|
227
370
|
tools: envelope.tools,
|
|
228
|
-
exitCode: explicitNumber([result, resultMetadata, metadata], ["exit_code", "exitCode", "pi_bash_exit_code"]),
|
|
229
|
-
durationMs: explicitNumber([result, resultMetadata, metadata], ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs", "totalDurationMs"]),
|
|
371
|
+
exitCode: explicitNumber([outcome.value, result, resultMetadata, metadata], ["exit_code", "exitCode", "pi_bash_exit_code"]),
|
|
372
|
+
durationMs: explicitNumber([outcome.value, result, resultMetadata, metadata], ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs", "totalDurationMs"]) ?? outcome.durationMs,
|
|
230
373
|
additions: explicitNumber([result, resultMetadata, metadata], ["additions", "lines_added"]),
|
|
231
374
|
deletions: explicitNumber([result, resultMetadata, metadata], ["deletions", "lines_removed"]),
|
|
232
375
|
matches: explicitNumber([result, resultMetadata, metadata], ["matches", "match_count", "result_count"])
|
package/core.mjs
CHANGED
|
@@ -127,22 +127,83 @@ function decodedLiteral(value) {
|
|
|
127
127
|
function sourceString(source, keys) {
|
|
128
128
|
if (!source) return '';
|
|
129
129
|
const names = keys.join('|');
|
|
130
|
-
const match = new RegExp(
|
|
130
|
+
const match = new RegExp(`(?:^|[,{\\s])[\"']?(?:${names})[\"']?\\s*:\\s*(\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
|
|
131
131
|
return decodedLiteral(match?.[1]);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
function callArgumentSource(source, open) {
|
|
135
|
+
let depth = 1;
|
|
136
|
+
let quote = '';
|
|
137
|
+
let escaped = false;
|
|
138
|
+
let lineComment = false;
|
|
139
|
+
let blockComment = false;
|
|
140
|
+
for (let index = open + 1; index < source.length; index += 1) {
|
|
141
|
+
const char = source[index];
|
|
142
|
+
const next = source[index + 1];
|
|
143
|
+
if (lineComment) { if (char === '\n') lineComment = false; continue; }
|
|
144
|
+
if (blockComment) { if (char === '*' && next === '/') { blockComment = false; index += 1; } continue; }
|
|
145
|
+
if (quote) {
|
|
146
|
+
if (escaped) escaped = false;
|
|
147
|
+
else if (char === '\\') escaped = true;
|
|
148
|
+
else if (char === quote) quote = '';
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (char === '/' && next === '/') { lineComment = true; index += 1; continue; }
|
|
152
|
+
if (char === '/' && next === '*') { blockComment = true; index += 1; continue; }
|
|
153
|
+
if (char === '"' || char === "'" || char === '`') { quote = char; continue; }
|
|
154
|
+
if (char === '(') depth += 1;
|
|
155
|
+
else if (char === ')' && (depth -= 1) === 0) return source.slice(open + 1, index);
|
|
156
|
+
}
|
|
157
|
+
return source.slice(open + 1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function toolCalls(source) {
|
|
161
|
+
const calls = [];
|
|
162
|
+
let quote = '';
|
|
163
|
+
let escaped = false;
|
|
164
|
+
let lineComment = false;
|
|
165
|
+
let blockComment = false;
|
|
166
|
+
for (let index = 0; index < source.length && calls.length < 8; index += 1) {
|
|
167
|
+
const char = source[index];
|
|
168
|
+
const next = source[index + 1];
|
|
169
|
+
if (lineComment) { if (char === '\n') lineComment = false; continue; }
|
|
170
|
+
if (blockComment) { if (char === '*' && next === '/') { blockComment = false; index += 1; } continue; }
|
|
171
|
+
if (quote) {
|
|
172
|
+
if (escaped) escaped = false;
|
|
173
|
+
else if (char === '\\') escaped = true;
|
|
174
|
+
else if (char === quote) quote = '';
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (char === '/' && next === '/') { lineComment = true; index += 1; continue; }
|
|
178
|
+
if (char === '/' && next === '*') { blockComment = true; index += 1; continue; }
|
|
179
|
+
if (char === '"' || char === "'" || char === '`') { quote = char; continue; }
|
|
180
|
+
if (!source.startsWith('tools.', index) || /[A-Za-z0-9_$]/.test(source[index - 1] ?? '')) continue;
|
|
181
|
+
const nameStart = index + 6;
|
|
182
|
+
let nameEnd = nameStart;
|
|
183
|
+
while (/[A-Za-z0-9_$]/.test(source[nameEnd] ?? '')) nameEnd += 1;
|
|
184
|
+
let open = nameEnd;
|
|
185
|
+
while (/\s/.test(source[open] ?? '')) open += 1;
|
|
186
|
+
if (nameEnd === nameStart || source[open] !== '(') continue;
|
|
187
|
+
calls.push({ name: source.slice(nameStart, nameEnd), arguments: callArgumentSource(source, open) });
|
|
188
|
+
index = open;
|
|
189
|
+
}
|
|
190
|
+
return calls;
|
|
191
|
+
}
|
|
192
|
+
|
|
134
193
|
function toolEnvelope(entry) {
|
|
135
194
|
const value = argumentValue(entry.arguments);
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
195
|
+
// Codex custom tools retain their safe JavaScript wrapper directly, while function-call
|
|
196
|
+
// envelopes usually retain JSON. Parse the latter, but treat unparsed text only as source for
|
|
197
|
+
// bounded literal extraction—never evaluate it and never surface the wrapper as primary UI.
|
|
198
|
+
const source = typeof value === 'string' ? value : value === null ? entry.arguments ?? '' : '';
|
|
199
|
+
const calls = source ? toolCalls(source) : [];
|
|
200
|
+
const tools = [...new Set(calls.map((call) => call.name))];
|
|
140
201
|
const name = tools.length === 1 ? tools[0] : entry.label ?? 'tool';
|
|
141
|
-
return { args: record(value), source, tools, name };
|
|
202
|
+
return { args: record(value), source, callSource: calls[0]?.arguments ?? '', tools, name };
|
|
142
203
|
}
|
|
143
204
|
|
|
144
205
|
function patchPath(source) {
|
|
145
|
-
return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]
|
|
206
|
+
return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]*?)(?=\\[nr]|[\r\n]|$)/.exec(source)?.[1]?.trim() ?? '';
|
|
146
207
|
}
|
|
147
208
|
|
|
148
209
|
function explicitNumber(sources, keys) {
|
|
@@ -198,6 +259,36 @@ function editPreview(args, resultText, source) {
|
|
|
198
259
|
return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? '') ? resultText : '';
|
|
199
260
|
}
|
|
200
261
|
|
|
262
|
+
function toolResultEnvelope(resultText) {
|
|
263
|
+
const match = /^Script (?:completed|failed)\r?\nWall time ([0-9.]+) seconds\r?\nOutput:\r?\n([\s\S]*)$/.exec(resultText ?? '');
|
|
264
|
+
if (!match) return { preview: resultText ?? '', value: null, durationMs: null };
|
|
265
|
+
const durationMs = Number(match[1]) * 1_000;
|
|
266
|
+
try {
|
|
267
|
+
const value = record(JSON.parse(match[2]));
|
|
268
|
+
return {
|
|
269
|
+
preview: typeof value?.output === 'string' ? value.output : match[2],
|
|
270
|
+
value,
|
|
271
|
+
durationMs: Number.isFinite(durationMs) ? durationMs : null,
|
|
272
|
+
};
|
|
273
|
+
} catch {
|
|
274
|
+
return { preview: match[2], value: null, durationMs: Number.isFinite(durationMs) ? durationMs : null };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function verificationCommand(command) {
|
|
279
|
+
let shell = '';
|
|
280
|
+
let quote = '';
|
|
281
|
+
let escaped = false;
|
|
282
|
+
for (const char of command) {
|
|
283
|
+
if (escaped) { escaped = false; shell += quote ? ' ' : char; continue; }
|
|
284
|
+
if (char === '\\') { escaped = true; shell += quote ? ' ' : char; continue; }
|
|
285
|
+
if (quote) { if (char === quote) quote = ''; shell += ' '; continue; }
|
|
286
|
+
if (char === '"' || char === "'" || char === '`') { quote = char; shell += ' '; continue; }
|
|
287
|
+
shell += char;
|
|
288
|
+
}
|
|
289
|
+
return /(?:^|[;&|]\s*)(?:(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|typecheck|lint|build)|cargo\s+(?:test|clippy|build|check|fmt)|npx\s+(?:vitest|tsc|eslint|playwright)|pytest\b|go\s+test\b)/i.test(shell);
|
|
290
|
+
}
|
|
291
|
+
|
|
201
292
|
function classifyTool(name, command) {
|
|
202
293
|
const normalized = name.toLocaleLowerCase();
|
|
203
294
|
if (/write_stdin|^wait$/.test(normalized)) return 'command';
|
|
@@ -208,7 +299,7 @@ function classifyTool(name, command) {
|
|
|
208
299
|
if (/browser|web|fetch|url/.test(normalized)) return 'web';
|
|
209
300
|
if (/agent|subagent|sendmessage|delegate|^task$/.test(normalized)) return 'agent';
|
|
210
301
|
if (/test|typecheck|lint|build/.test(normalized)) return 'test';
|
|
211
|
-
if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return
|
|
302
|
+
if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return verificationCommand(command) ? 'test' : 'command';
|
|
212
303
|
return 'other';
|
|
213
304
|
}
|
|
214
305
|
|
|
@@ -235,18 +326,19 @@ function toolAction(status, category, name, tools) {
|
|
|
235
326
|
export function createToolPresentation(entry) {
|
|
236
327
|
const envelope = toolEnvelope(entry);
|
|
237
328
|
const args = envelope.args;
|
|
238
|
-
const
|
|
329
|
+
const outcome = toolResultEnvelope(entry.resultText);
|
|
330
|
+
const command = firstString(args, ['command', 'cmd']) || sourceString(envelope.callSource, ['command', 'cmd']);
|
|
239
331
|
const category = classifyTool(envelope.name, command || envelope.source || entry.arguments || '');
|
|
240
|
-
const path = firstString(args, ['file_path', 'target_file', 'target_directory', 'path']) || sourceString(envelope.
|
|
241
|
-
const query = firstString(args, ['query', 'pattern']) || sourceString(envelope.
|
|
242
|
-
const url = firstString(args, ['url']) || sourceString(envelope.
|
|
243
|
-
const subject = firstString(args, ['subject', 'description', 'summary', 'task', 'prompt']) || sourceString(envelope.
|
|
332
|
+
const path = firstString(args, ['file_path', 'target_file', 'target_directory', 'path']) || sourceString(envelope.callSource, ['file_path', 'target_file', 'target_directory', 'path']) || patchPath(envelope.source);
|
|
333
|
+
const query = firstString(args, ['query', 'pattern']) || sourceString(envelope.callSource, ['query', 'pattern', 'q']);
|
|
334
|
+
const url = firstString(args, ['url']) || sourceString(envelope.callSource, ['url', 'ref_id']);
|
|
335
|
+
const subject = firstString(args, ['subject', 'description', 'summary', 'task', 'prompt']) || sourceString(envelope.callSource, ['subject', 'description', 'summary', 'task', 'prompt']);
|
|
244
336
|
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
337
|
const items = planItems(args);
|
|
246
338
|
const taskId = firstString(args, ['taskId', 'task_id']);
|
|
247
339
|
const planTarget = category === 'plan' ? items.length ? `${items.length} ${items.length === 1 ? 'item' : 'items'}` : taskId ? `task ${taskId}` : '' : '';
|
|
248
340
|
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,
|
|
341
|
+
const previewSource = category === 'edit' ? editPreview(args, outcome.preview, envelope.source) : outcome.preview;
|
|
250
342
|
const result = record(entry.resultContent);
|
|
251
343
|
const metadata = record(entry.metadata);
|
|
252
344
|
const resultMetadata = record(result?.metadata);
|
|
@@ -265,8 +357,8 @@ export function createToolPresentation(entry) {
|
|
|
265
357
|
fields: usefulToolFields(args),
|
|
266
358
|
items,
|
|
267
359
|
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']),
|
|
360
|
+
exitCode: explicitNumber([outcome.value, result, resultMetadata, metadata], ['exit_code', 'exitCode', 'pi_bash_exit_code']),
|
|
361
|
+
durationMs: explicitNumber([outcome.value, result, resultMetadata, metadata], ['duration_ms', 'durationMs', 'elapsed_ms', 'elapsedMs', 'totalDurationMs']) ?? outcome.durationMs,
|
|
270
362
|
additions: explicitNumber([result, resultMetadata, metadata], ['additions', 'lines_added']),
|
|
271
363
|
deletions: explicitNumber([result, resultMetadata, metadata], ['deletions', 'lines_removed']),
|
|
272
364
|
matches: explicitNumber([result, resultMetadata, metadata], ['matches', 'match_count', 'result_count']),
|
package/embed.mjs
CHANGED
|
@@ -116,18 +116,116 @@ function decodedLiteral(value) {
|
|
|
116
116
|
function sourceString(source, keys) {
|
|
117
117
|
if (!source) return "";
|
|
118
118
|
const names = keys.join("|");
|
|
119
|
-
const match = new RegExp(
|
|
119
|
+
const match = new RegExp(`(?:^|[,{\\s])["']?(?:${names})["']?\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
|
|
120
120
|
return decodedLiteral(match?.[1]);
|
|
121
121
|
}
|
|
122
|
+
function callArgumentSource(source, open) {
|
|
123
|
+
let depth = 1;
|
|
124
|
+
let quote = "";
|
|
125
|
+
let escaped = false;
|
|
126
|
+
let lineComment = false;
|
|
127
|
+
let blockComment = false;
|
|
128
|
+
for (let index = open + 1; index < source.length; index += 1) {
|
|
129
|
+
const char = source[index];
|
|
130
|
+
const next = source[index + 1];
|
|
131
|
+
if (lineComment) {
|
|
132
|
+
if (char === "\n") lineComment = false;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (blockComment) {
|
|
136
|
+
if (char === "*" && next === "/") {
|
|
137
|
+
blockComment = false;
|
|
138
|
+
index += 1;
|
|
139
|
+
}
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (quote) {
|
|
143
|
+
if (escaped) escaped = false;
|
|
144
|
+
else if (char === "\\") escaped = true;
|
|
145
|
+
else if (char === quote) quote = "";
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (char === "/" && next === "/") {
|
|
149
|
+
lineComment = true;
|
|
150
|
+
index += 1;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (char === "/" && next === "*") {
|
|
154
|
+
blockComment = true;
|
|
155
|
+
index += 1;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
159
|
+
quote = char;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (char === "(") depth += 1;
|
|
163
|
+
else if (char === ")" && (depth -= 1) === 0) return source.slice(open + 1, index);
|
|
164
|
+
}
|
|
165
|
+
return source.slice(open + 1);
|
|
166
|
+
}
|
|
167
|
+
function toolCalls(source) {
|
|
168
|
+
const calls = [];
|
|
169
|
+
let quote = "";
|
|
170
|
+
let escaped = false;
|
|
171
|
+
let lineComment = false;
|
|
172
|
+
let blockComment = false;
|
|
173
|
+
for (let index = 0; index < source.length && calls.length < 8; index += 1) {
|
|
174
|
+
const char = source[index];
|
|
175
|
+
const next = source[index + 1];
|
|
176
|
+
if (lineComment) {
|
|
177
|
+
if (char === "\n") lineComment = false;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (blockComment) {
|
|
181
|
+
if (char === "*" && next === "/") {
|
|
182
|
+
blockComment = false;
|
|
183
|
+
index += 1;
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (quote) {
|
|
188
|
+
if (escaped) escaped = false;
|
|
189
|
+
else if (char === "\\") escaped = true;
|
|
190
|
+
else if (char === quote) quote = "";
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (char === "/" && next === "/") {
|
|
194
|
+
lineComment = true;
|
|
195
|
+
index += 1;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (char === "/" && next === "*") {
|
|
199
|
+
blockComment = true;
|
|
200
|
+
index += 1;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
204
|
+
quote = char;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (!source.startsWith("tools.", index) || /[A-Za-z0-9_$]/.test(source[index - 1] ?? "")) continue;
|
|
208
|
+
const nameStart = index + 6;
|
|
209
|
+
let nameEnd = nameStart;
|
|
210
|
+
while (/[A-Za-z0-9_$]/.test(source[nameEnd] ?? "")) nameEnd += 1;
|
|
211
|
+
let open = nameEnd;
|
|
212
|
+
while (/\s/.test(source[open] ?? "")) open += 1;
|
|
213
|
+
if (nameEnd === nameStart || source[open] !== "(") continue;
|
|
214
|
+
calls.push({ name: source.slice(nameStart, nameEnd), arguments: callArgumentSource(source, open) });
|
|
215
|
+
index = open;
|
|
216
|
+
}
|
|
217
|
+
return calls;
|
|
218
|
+
}
|
|
122
219
|
function toolEnvelope(entry) {
|
|
123
220
|
const value = argumentValue(entry.arguments);
|
|
124
|
-
const source = typeof value === "string" ? value : "";
|
|
125
|
-
const
|
|
221
|
+
const source = typeof value === "string" ? value : value === null ? entry.arguments ?? "" : "";
|
|
222
|
+
const calls = source ? toolCalls(source) : [];
|
|
223
|
+
const tools = [...new Set(calls.map((call) => call.name))];
|
|
126
224
|
const name = tools.length === 1 ? tools[0] : entry.label ?? "tool";
|
|
127
|
-
return { args: record(value), source, tools, name };
|
|
225
|
+
return { args: record(value), source, callSource: calls[0]?.arguments ?? "", tools, name };
|
|
128
226
|
}
|
|
129
227
|
function patchPath(source) {
|
|
130
|
-
return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]
|
|
228
|
+
return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]*?)(?=\\[nr]|[\r\n]|$)/.exec(source)?.[1]?.trim() ?? "";
|
|
131
229
|
}
|
|
132
230
|
function explicitNumber(sources, keys) {
|
|
133
231
|
for (const source of sources) {
|
|
@@ -177,6 +275,50 @@ function editPreview(args, resultText, source) {
|
|
|
177
275
|
if (patch) return patch;
|
|
178
276
|
return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? "") ? resultText : "";
|
|
179
277
|
}
|
|
278
|
+
function toolResultEnvelope(resultText) {
|
|
279
|
+
const match = /^Script (?:completed|failed)\r?\nWall time ([0-9.]+) seconds\r?\nOutput:\r?\n([\s\S]*)$/.exec(resultText ?? "");
|
|
280
|
+
if (!match) return { preview: resultText ?? "", value: null, durationMs: null };
|
|
281
|
+
const durationMs = Number(match[1]) * 1e3;
|
|
282
|
+
try {
|
|
283
|
+
const value = record(JSON.parse(match[2]));
|
|
284
|
+
return {
|
|
285
|
+
preview: typeof value?.output === "string" ? value.output : match[2],
|
|
286
|
+
value,
|
|
287
|
+
durationMs: Number.isFinite(durationMs) ? durationMs : null
|
|
288
|
+
};
|
|
289
|
+
} catch {
|
|
290
|
+
return { preview: match[2], value: null, durationMs: Number.isFinite(durationMs) ? durationMs : null };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function verificationCommand(command) {
|
|
294
|
+
let shell = "";
|
|
295
|
+
let quote = "";
|
|
296
|
+
let escaped = false;
|
|
297
|
+
for (const char of command) {
|
|
298
|
+
if (escaped) {
|
|
299
|
+
escaped = false;
|
|
300
|
+
shell += quote ? " " : char;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (char === "\\") {
|
|
304
|
+
escaped = true;
|
|
305
|
+
shell += quote ? " " : char;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (quote) {
|
|
309
|
+
if (char === quote) quote = "";
|
|
310
|
+
shell += " ";
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
314
|
+
quote = char;
|
|
315
|
+
shell += " ";
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
shell += char;
|
|
319
|
+
}
|
|
320
|
+
return /(?:^|[;&|]\s*)(?:(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|typecheck|lint|build)|cargo\s+(?:test|clippy|build|check|fmt)|npx\s+(?:vitest|tsc|eslint|playwright)|pytest\b|go\s+test\b)/i.test(shell);
|
|
321
|
+
}
|
|
180
322
|
function classifyTool(name, command) {
|
|
181
323
|
const normalized = name.toLocaleLowerCase();
|
|
182
324
|
if (/write_stdin|^wait$/.test(normalized)) return "command";
|
|
@@ -187,7 +329,7 @@ function classifyTool(name, command) {
|
|
|
187
329
|
if (/browser|web|fetch|url/.test(normalized)) return "web";
|
|
188
330
|
if (/agent|subagent|sendmessage|delegate|^task$/.test(normalized)) return "agent";
|
|
189
331
|
if (/test|typecheck|lint|build/.test(normalized)) return "test";
|
|
190
|
-
if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return
|
|
332
|
+
if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return verificationCommand(command) ? "test" : "command";
|
|
191
333
|
return "other";
|
|
192
334
|
}
|
|
193
335
|
function toolAction(status, category, name, tools) {
|
|
@@ -212,18 +354,19 @@ function toolAction(status, category, name, tools) {
|
|
|
212
354
|
function createToolPresentation(entry) {
|
|
213
355
|
const envelope = toolEnvelope(entry);
|
|
214
356
|
const args = envelope.args;
|
|
215
|
-
const
|
|
357
|
+
const outcome = toolResultEnvelope(entry.resultText);
|
|
358
|
+
const command = firstString(args, ["command", "cmd"]) || sourceString(envelope.callSource, ["command", "cmd"]);
|
|
216
359
|
const category = classifyTool(envelope.name, command || envelope.source || entry.arguments || "");
|
|
217
|
-
const path = firstString(args, ["file_path", "target_file", "target_directory", "path"]) || sourceString(envelope.
|
|
218
|
-
const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.
|
|
219
|
-
const url = firstString(args, ["url"]) || sourceString(envelope.
|
|
220
|
-
const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.
|
|
360
|
+
const path = firstString(args, ["file_path", "target_file", "target_directory", "path"]) || sourceString(envelope.callSource, ["file_path", "target_file", "target_directory", "path"]) || patchPath(envelope.source);
|
|
361
|
+
const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.callSource, ["query", "pattern", "q"]);
|
|
362
|
+
const url = firstString(args, ["url"]) || sourceString(envelope.callSource, ["url", "ref_id"]);
|
|
363
|
+
const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.callSource, ["subject", "description", "summary", "task", "prompt"]);
|
|
221
364
|
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
365
|
const items = planItems(args);
|
|
223
366
|
const taskId = firstString(args, ["taskId", "task_id"]);
|
|
224
367
|
const planTarget = category === "plan" ? items.length ? `${items.length} ${items.length === 1 ? "item" : "items"}` : taskId ? `task ${taskId}` : "" : "";
|
|
225
368
|
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,
|
|
369
|
+
const previewSource = category === "edit" ? editPreview(args, outcome.preview, envelope.source) : outcome.preview;
|
|
227
370
|
const result = record(entry.resultContent);
|
|
228
371
|
const metadata = record(entry.metadata);
|
|
229
372
|
const resultMetadata = record(result?.metadata);
|
|
@@ -242,8 +385,8 @@ function createToolPresentation(entry) {
|
|
|
242
385
|
fields: usefulToolFields(args),
|
|
243
386
|
items,
|
|
244
387
|
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"]),
|
|
388
|
+
exitCode: explicitNumber([outcome.value, result, resultMetadata, metadata], ["exit_code", "exitCode", "pi_bash_exit_code"]),
|
|
389
|
+
durationMs: explicitNumber([outcome.value, result, resultMetadata, metadata], ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs", "totalDurationMs"]) ?? outcome.durationMs,
|
|
247
390
|
additions: explicitNumber([result, resultMetadata, metadata], ["additions", "lines_added"]),
|
|
248
391
|
deletions: explicitNumber([result, resultMetadata, metadata], ["deletions", "lines_removed"]),
|
|
249
392
|
matches: explicitNumber([result, resultMetadata, metadata], ["matches", "match_count", "result_count"])
|
package/messenger.mjs
CHANGED
|
@@ -113,18 +113,116 @@ function decodedLiteral(value) {
|
|
|
113
113
|
function sourceString(source, keys) {
|
|
114
114
|
if (!source) return "";
|
|
115
115
|
const names = keys.join("|");
|
|
116
|
-
const match = new RegExp(
|
|
116
|
+
const match = new RegExp(`(?:^|[,{\\s])["']?(?:${names})["']?\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
|
|
117
117
|
return decodedLiteral(match?.[1]);
|
|
118
118
|
}
|
|
119
|
+
function callArgumentSource(source, open) {
|
|
120
|
+
let depth = 1;
|
|
121
|
+
let quote = "";
|
|
122
|
+
let escaped = false;
|
|
123
|
+
let lineComment = false;
|
|
124
|
+
let blockComment = false;
|
|
125
|
+
for (let index = open + 1; index < source.length; index += 1) {
|
|
126
|
+
const char = source[index];
|
|
127
|
+
const next = source[index + 1];
|
|
128
|
+
if (lineComment) {
|
|
129
|
+
if (char === "\n") lineComment = false;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (blockComment) {
|
|
133
|
+
if (char === "*" && next === "/") {
|
|
134
|
+
blockComment = false;
|
|
135
|
+
index += 1;
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (quote) {
|
|
140
|
+
if (escaped) escaped = false;
|
|
141
|
+
else if (char === "\\") escaped = true;
|
|
142
|
+
else if (char === quote) quote = "";
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (char === "/" && next === "/") {
|
|
146
|
+
lineComment = true;
|
|
147
|
+
index += 1;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (char === "/" && next === "*") {
|
|
151
|
+
blockComment = true;
|
|
152
|
+
index += 1;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
156
|
+
quote = char;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (char === "(") depth += 1;
|
|
160
|
+
else if (char === ")" && (depth -= 1) === 0) return source.slice(open + 1, index);
|
|
161
|
+
}
|
|
162
|
+
return source.slice(open + 1);
|
|
163
|
+
}
|
|
164
|
+
function toolCalls(source) {
|
|
165
|
+
const calls = [];
|
|
166
|
+
let quote = "";
|
|
167
|
+
let escaped = false;
|
|
168
|
+
let lineComment = false;
|
|
169
|
+
let blockComment = false;
|
|
170
|
+
for (let index = 0; index < source.length && calls.length < 8; index += 1) {
|
|
171
|
+
const char = source[index];
|
|
172
|
+
const next = source[index + 1];
|
|
173
|
+
if (lineComment) {
|
|
174
|
+
if (char === "\n") lineComment = false;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (blockComment) {
|
|
178
|
+
if (char === "*" && next === "/") {
|
|
179
|
+
blockComment = false;
|
|
180
|
+
index += 1;
|
|
181
|
+
}
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (quote) {
|
|
185
|
+
if (escaped) escaped = false;
|
|
186
|
+
else if (char === "\\") escaped = true;
|
|
187
|
+
else if (char === quote) quote = "";
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (char === "/" && next === "/") {
|
|
191
|
+
lineComment = true;
|
|
192
|
+
index += 1;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (char === "/" && next === "*") {
|
|
196
|
+
blockComment = true;
|
|
197
|
+
index += 1;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
201
|
+
quote = char;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (!source.startsWith("tools.", index) || /[A-Za-z0-9_$]/.test(source[index - 1] ?? "")) continue;
|
|
205
|
+
const nameStart = index + 6;
|
|
206
|
+
let nameEnd = nameStart;
|
|
207
|
+
while (/[A-Za-z0-9_$]/.test(source[nameEnd] ?? "")) nameEnd += 1;
|
|
208
|
+
let open = nameEnd;
|
|
209
|
+
while (/\s/.test(source[open] ?? "")) open += 1;
|
|
210
|
+
if (nameEnd === nameStart || source[open] !== "(") continue;
|
|
211
|
+
calls.push({ name: source.slice(nameStart, nameEnd), arguments: callArgumentSource(source, open) });
|
|
212
|
+
index = open;
|
|
213
|
+
}
|
|
214
|
+
return calls;
|
|
215
|
+
}
|
|
119
216
|
function toolEnvelope(entry) {
|
|
120
217
|
const value = argumentValue(entry.arguments);
|
|
121
|
-
const source = typeof value === "string" ? value : "";
|
|
122
|
-
const
|
|
218
|
+
const source = typeof value === "string" ? value : value === null ? entry.arguments ?? "" : "";
|
|
219
|
+
const calls = source ? toolCalls(source) : [];
|
|
220
|
+
const tools = [...new Set(calls.map((call) => call.name))];
|
|
123
221
|
const name = tools.length === 1 ? tools[0] : entry.label ?? "tool";
|
|
124
|
-
return { args: record(value), source, tools, name };
|
|
222
|
+
return { args: record(value), source, callSource: calls[0]?.arguments ?? "", tools, name };
|
|
125
223
|
}
|
|
126
224
|
function patchPath(source) {
|
|
127
|
-
return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]
|
|
225
|
+
return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]*?)(?=\\[nr]|[\r\n]|$)/.exec(source)?.[1]?.trim() ?? "";
|
|
128
226
|
}
|
|
129
227
|
function explicitNumber(sources, keys) {
|
|
130
228
|
for (const source of sources) {
|
|
@@ -174,6 +272,50 @@ function editPreview(args, resultText, source) {
|
|
|
174
272
|
if (patch) return patch;
|
|
175
273
|
return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? "") ? resultText : "";
|
|
176
274
|
}
|
|
275
|
+
function toolResultEnvelope(resultText) {
|
|
276
|
+
const match = /^Script (?:completed|failed)\r?\nWall time ([0-9.]+) seconds\r?\nOutput:\r?\n([\s\S]*)$/.exec(resultText ?? "");
|
|
277
|
+
if (!match) return { preview: resultText ?? "", value: null, durationMs: null };
|
|
278
|
+
const durationMs = Number(match[1]) * 1e3;
|
|
279
|
+
try {
|
|
280
|
+
const value = record(JSON.parse(match[2]));
|
|
281
|
+
return {
|
|
282
|
+
preview: typeof value?.output === "string" ? value.output : match[2],
|
|
283
|
+
value,
|
|
284
|
+
durationMs: Number.isFinite(durationMs) ? durationMs : null
|
|
285
|
+
};
|
|
286
|
+
} catch {
|
|
287
|
+
return { preview: match[2], value: null, durationMs: Number.isFinite(durationMs) ? durationMs : null };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function verificationCommand(command) {
|
|
291
|
+
let shell = "";
|
|
292
|
+
let quote = "";
|
|
293
|
+
let escaped = false;
|
|
294
|
+
for (const char of command) {
|
|
295
|
+
if (escaped) {
|
|
296
|
+
escaped = false;
|
|
297
|
+
shell += quote ? " " : char;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (char === "\\") {
|
|
301
|
+
escaped = true;
|
|
302
|
+
shell += quote ? " " : char;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (quote) {
|
|
306
|
+
if (char === quote) quote = "";
|
|
307
|
+
shell += " ";
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
311
|
+
quote = char;
|
|
312
|
+
shell += " ";
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
shell += char;
|
|
316
|
+
}
|
|
317
|
+
return /(?:^|[;&|]\s*)(?:(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|typecheck|lint|build)|cargo\s+(?:test|clippy|build|check|fmt)|npx\s+(?:vitest|tsc|eslint|playwright)|pytest\b|go\s+test\b)/i.test(shell);
|
|
318
|
+
}
|
|
177
319
|
function classifyTool(name, command) {
|
|
178
320
|
const normalized = name.toLocaleLowerCase();
|
|
179
321
|
if (/write_stdin|^wait$/.test(normalized)) return "command";
|
|
@@ -184,7 +326,7 @@ function classifyTool(name, command) {
|
|
|
184
326
|
if (/browser|web|fetch|url/.test(normalized)) return "web";
|
|
185
327
|
if (/agent|subagent|sendmessage|delegate|^task$/.test(normalized)) return "agent";
|
|
186
328
|
if (/test|typecheck|lint|build/.test(normalized)) return "test";
|
|
187
|
-
if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return
|
|
329
|
+
if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return verificationCommand(command) ? "test" : "command";
|
|
188
330
|
return "other";
|
|
189
331
|
}
|
|
190
332
|
function toolAction(status, category, name, tools) {
|
|
@@ -209,18 +351,19 @@ function toolAction(status, category, name, tools) {
|
|
|
209
351
|
function createToolPresentation(entry) {
|
|
210
352
|
const envelope = toolEnvelope(entry);
|
|
211
353
|
const args = envelope.args;
|
|
212
|
-
const
|
|
354
|
+
const outcome = toolResultEnvelope(entry.resultText);
|
|
355
|
+
const command = firstString(args, ["command", "cmd"]) || sourceString(envelope.callSource, ["command", "cmd"]);
|
|
213
356
|
const category = classifyTool(envelope.name, command || envelope.source || entry.arguments || "");
|
|
214
|
-
const path = firstString(args, ["file_path", "target_file", "target_directory", "path"]) || sourceString(envelope.
|
|
215
|
-
const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.
|
|
216
|
-
const url = firstString(args, ["url"]) || sourceString(envelope.
|
|
217
|
-
const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.
|
|
357
|
+
const path = firstString(args, ["file_path", "target_file", "target_directory", "path"]) || sourceString(envelope.callSource, ["file_path", "target_file", "target_directory", "path"]) || patchPath(envelope.source);
|
|
358
|
+
const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.callSource, ["query", "pattern", "q"]);
|
|
359
|
+
const url = firstString(args, ["url"]) || sourceString(envelope.callSource, ["url", "ref_id"]);
|
|
360
|
+
const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.callSource, ["subject", "description", "summary", "task", "prompt"]);
|
|
218
361
|
const background = /get_command_or_subagent_output|kill_command_or_subagent/i.test(envelope.name) ? "background task" : /write_stdin|^wait$/i.test(envelope.name) ? "background command" : "";
|
|
219
362
|
const items = planItems(args);
|
|
220
363
|
const taskId = firstString(args, ["taskId", "task_id"]);
|
|
221
364
|
const planTarget = category === "plan" ? items.length ? `${items.length} ${items.length === 1 ? "item" : "items"}` : taskId ? `task ${taskId}` : "" : "";
|
|
222
365
|
const target = path || command || query || url || subject || background || planTarget || (envelope.tools.length > 1 ? `${envelope.tools.length} coordinated actions` : toolTarget(entry.arguments));
|
|
223
|
-
const previewSource = category === "edit" ? editPreview(args,
|
|
366
|
+
const previewSource = category === "edit" ? editPreview(args, outcome.preview, envelope.source) : outcome.preview;
|
|
224
367
|
const result = record(entry.resultContent);
|
|
225
368
|
const metadata = record(entry.metadata);
|
|
226
369
|
const resultMetadata = record(result?.metadata);
|
|
@@ -239,8 +382,8 @@ function createToolPresentation(entry) {
|
|
|
239
382
|
fields: usefulToolFields(args),
|
|
240
383
|
items,
|
|
241
384
|
tools: envelope.tools,
|
|
242
|
-
exitCode: explicitNumber([result, resultMetadata, metadata], ["exit_code", "exitCode", "pi_bash_exit_code"]),
|
|
243
|
-
durationMs: explicitNumber([result, resultMetadata, metadata], ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs", "totalDurationMs"]),
|
|
385
|
+
exitCode: explicitNumber([outcome.value, result, resultMetadata, metadata], ["exit_code", "exitCode", "pi_bash_exit_code"]),
|
|
386
|
+
durationMs: explicitNumber([outcome.value, result, resultMetadata, metadata], ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs", "totalDurationMs"]) ?? outcome.durationMs,
|
|
244
387
|
additions: explicitNumber([result, resultMetadata, metadata], ["additions", "lines_added"]),
|
|
245
388
|
deletions: explicitNumber([result, resultMetadata, metadata], ["deletions", "lines_removed"]),
|
|
246
389
|
matches: explicitNumber([result, resultMetadata, metadata], ["matches", "match_count", "result_count"])
|