@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 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(`\\b(?:${names})\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
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 tools = source ? [...new Set([...source.matchAll(/\btools\.([A-Za-z0-9_]+)/g)].map((match) => match[1]))].slice(0, 8) : [];
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]+)/.exec(source)?.[1]?.trim() ?? "";
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 /\b(test|typecheck|lint|build)\b/i.test(command) ? "test" : "command";
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 command = firstString(args, ["command", "cmd"]) || sourceString(envelope.source, ["command", "cmd"]);
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.source, ["file_path", "target_file", "target_directory", "path"]) || patchPath(envelope.source);
215
- const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.source, ["query", "pattern", "q"]);
216
- const url = firstString(args, ["url"]) || sourceString(envelope.source, ["url", "ref_id"]);
217
- const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.source, ["subject", "description", "summary", "task", "prompt"]);
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, entry.resultText, envelope.source) : entry.resultText ?? "";
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(`\\b(?:${names})\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
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 tools = source ? [...new Set([...source.matchAll(/\btools\.([A-Za-z0-9_]+)/g)].map((match) => match[1]))].slice(0, 8) : [];
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]+)/.exec(source)?.[1]?.trim() ?? "";
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 /\b(test|typecheck|lint|build)\b/i.test(command) ? "test" : "command";
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 command = firstString(args, ["command", "cmd"]) || sourceString(envelope.source, ["command", "cmd"]);
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.source, ["file_path", "target_file", "target_directory", "path"]) || patchPath(envelope.source);
201
- const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.source, ["query", "pattern", "q"]);
202
- const url = firstString(args, ["url"]) || sourceString(envelope.source, ["url", "ref_id"]);
203
- const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.source, ["subject", "description", "summary", "task", "prompt"]);
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, entry.resultText, envelope.source) : entry.resultText ?? "";
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(`\\b(?:${names})\\s*:\\s*(\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
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
- 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
- : [];
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]+)/.exec(source)?.[1]?.trim() ?? '';
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 /\b(test|typecheck|lint|build)\b/i.test(command) ? 'test' : 'command';
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 command = firstString(args, ['command', 'cmd']) || sourceString(envelope.source, ['command', 'cmd']);
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.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']);
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, entry.resultText, envelope.source) : (entry.resultText ?? '');
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(`\\b(?:${names})\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
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 tools = source ? [...new Set([...source.matchAll(/\btools\.([A-Za-z0-9_]+)/g)].map((match) => match[1]))].slice(0, 8) : [];
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]+)/.exec(source)?.[1]?.trim() ?? "";
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 /\b(test|typecheck|lint|build)\b/i.test(command) ? "test" : "command";
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 command = firstString(args, ["command", "cmd"]) || sourceString(envelope.source, ["command", "cmd"]);
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.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"]);
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, entry.resultText, envelope.source) : entry.resultText ?? "";
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(`\\b(?:${names})\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
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 tools = source ? [...new Set([...source.matchAll(/\btools\.([A-Za-z0-9_]+)/g)].map((match) => match[1]))].slice(0, 8) : [];
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]+)/.exec(source)?.[1]?.trim() ?? "";
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 /\b(test|typecheck|lint|build)\b/i.test(command) ? "test" : "command";
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 command = firstString(args, ["command", "cmd"]) || sourceString(envelope.source, ["command", "cmd"]);
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.source, ["file_path", "target_file", "target_directory", "path"]) || patchPath(envelope.source);
215
- const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.source, ["query", "pattern", "q"]);
216
- const url = firstString(args, ["url"]) || sourceString(envelope.source, ["url", "ref_id"]);
217
- const subject = firstString(args, ["subject", "description", "summary", "task", "prompt"]) || sourceString(envelope.source, ["subject", "description", "summary", "task", "prompt"]);
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, entry.resultText, envelope.source) : entry.resultText ?? "";
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter-ai-dev/supercode-ui",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "description": "Composable default UI kit for Supercode-powered coding-agent experiences",
6
6
  "exports": {