@mmerterden/multi-agent-pipeline 20.24.0 → 20.25.0
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/CHANGELOG.md +24 -0
- package/README.md +6 -5
- package/README.tr.md +6 -5
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +7 -6
- package/docs/features.md +9 -4
- package/install/catalog-history.json +1 -1
- package/install/templates/claude-hooks.json +1 -1
- package/install/templates/codex-hooks.json +18 -0
- package/manifest.json +89 -82
- package/package.json +1 -1
- package/pipeline/agents/task-clarifier.md +1 -1
- package/pipeline/commands/multi-agent/deps/SKILL.md +61 -0
- package/pipeline/commands/multi-agent/model/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/postmortem/SKILL.md +18 -6
- package/pipeline/commands/multi-agent/review/SKILL.md +5 -5
- package/pipeline/commands/multi-agent/route-on/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +7 -5
- package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +2 -3
- package/pipeline/contract/fixtures/runs-failed.json +1 -1
- package/pipeline/contract/fixtures/runs-old-schema.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
- package/pipeline/contract/fixtures/runs-running.json +1 -1
- package/pipeline/lib/credential-inventory.sh +5 -5
- package/pipeline/lib/fetch-crashlytics.sh +6 -8
- package/pipeline/lib/model-rung.sh +1 -1
- package/pipeline/lib/plan-todos.sh +21 -4
- package/pipeline/lib/post-pr-review.sh +4 -6
- package/pipeline/lib/route-state.sh +1 -1
- package/pipeline/lib/shadow-git.sh +4 -5
- package/pipeline/multi-agent-refs/cross-cli-contract.md +8 -9
- package/pipeline/multi-agent-refs/features/cost-analysis.md +4 -0
- package/pipeline/multi-agent-refs/features/dep-bump.md +176 -0
- package/pipeline/multi-agent-refs/features/model-fallback.md +17 -12
- package/pipeline/multi-agent-refs/help/en.md +1 -0
- package/pipeline/multi-agent-refs/help/tr.md +1 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +2 -0
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +5 -5
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/multi-agent-refs/tracker-contract.md +6 -6
- package/pipeline/schemas/agent-state.schema.json +5 -0
- package/pipeline/schemas/phases.json +4 -0
- package/pipeline/schemas/plan-todos.schema.json +4 -4
- package/pipeline/schemas/prefs.schema.json +27 -4
- package/pipeline/scripts/_code-graph.mjs +2 -2
- package/pipeline/scripts/_transcript-hosts.mjs +406 -0
- package/pipeline/scripts/build-stack-plugins.mjs +3 -3
- package/pipeline/scripts/capture-flush.sh +7 -7
- package/pipeline/scripts/code-graph-rules/android.json +1 -1
- package/pipeline/scripts/cost-analyze.mjs +26 -19
- package/pipeline/scripts/cost-table.json +35 -19
- package/pipeline/scripts/dep-bump.mjs +1556 -0
- package/pipeline/scripts/edit-syntax-gate.mjs +151 -29
- package/pipeline/scripts/edit-syntax-gate.sh +9 -6
- package/pipeline/scripts/gc-abandoned.sh +2 -2
- package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
- package/pipeline/scripts/git-path.mjs +1 -2
- package/pipeline/scripts/host-hooks.mjs +265 -0
- package/pipeline/scripts/jira-attach.sh +1 -1
- package/pipeline/scripts/keychain.py +3 -3
- package/pipeline/scripts/output-quality-check.sh +1 -1
- package/pipeline/scripts/phase-tracker.sh +5 -6
- package/pipeline/scripts/phase0-exit-gate.mjs +3 -3
- package/pipeline/scripts/pre-push-check.sh +3 -6
- package/pipeline/scripts/transcript-index.mjs +423 -242
- package/pipeline/scripts/uninstall.mjs +90 -4
- package/pipeline/scripts/update-issue-progress.sh +4 -6
- package/pipeline/scripts/usage-report.mjs +2 -2
- package/pipeline/skills/.skill-manifest.json +14 -10
- package/pipeline/skills/.skills-index.json +12 -1
- package/pipeline/skills/shared/README.md +4 -3
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +6 -6
- package/pipeline/skills/shared/core/multi-agent-deps/SKILL.md +60 -0
- package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-postmortem/SKILL.md +10 -7
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +5 -5
- package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +7 -5
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +3 -3
- package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +2 -3
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
- package/pipeline/skills/skills-index.md +2 -1
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* session actually did on the way: which tool calls it repeated, where it sat
|
|
8
8
|
* waiting, where the context was compacted, which subagent failed, what was
|
|
9
9
|
* denied. This reads a Claude Code session file (`<session>.jsonl`, plus the
|
|
10
|
-
* subagent files beside it)
|
|
10
|
+
* subagent files beside it), a Copilot CLI session log
|
|
11
|
+
* (`session-state/<session>/events.jsonl`) or a Codex rollout
|
|
12
|
+
* (`sessions/YYYY/MM/DD/rollout-*.jsonl`), detecting the format per file
|
|
13
|
+
* (_transcript-hosts.mjs), and extracts those facts deterministically, so the
|
|
11
14
|
* same transcript always gives the same index and every finding carries the
|
|
12
15
|
* `file:line` it came from. Interpreting them is left to the model that writes
|
|
13
16
|
* the post-mortem (/multi-agent:postmortem); nothing here guesses.
|
|
@@ -46,7 +49,10 @@
|
|
|
46
49
|
* options: --json --no-subagents --max-mb N --max-events N
|
|
47
50
|
* --loop-threshold N --stall-minutes N --excerpt N
|
|
48
51
|
*
|
|
49
|
-
* TRANSCRIPTS_ROOT overrides ~/.claude/projects,
|
|
52
|
+
* TRANSCRIPTS_ROOT overrides ~/.claude/projects, COPILOT_TRANSCRIPTS_ROOT
|
|
53
|
+
* ~/.copilot/session-state (else $COPILOT_HOME/session-state),
|
|
54
|
+
* CODEX_TRANSCRIPTS_ROOT ~/.codex/sessions (else $CODEX_HOME/sessions), and
|
|
55
|
+
* LOGS_ROOT the run log root.
|
|
50
56
|
*
|
|
51
57
|
* Exit codes: 0 indexed | 2 usage | 3 nothing to read | 4 redaction guard
|
|
52
58
|
*
|
|
@@ -58,6 +64,7 @@ import { existsSync, readdirSync, statSync } from "node:fs";
|
|
|
58
64
|
import { homedir } from "node:os";
|
|
59
65
|
import { basename, dirname, join } from "node:path";
|
|
60
66
|
import { readJsonl } from "./_jsonl.mjs";
|
|
67
|
+
import { createAdapter, detectFormat, sessionCwd } from "./_transcript-hosts.mjs";
|
|
61
68
|
import { redactCredentials, scrubCounted } from "./_redact.mjs";
|
|
62
69
|
import { listRuns, logsRoot, taskIdVariants } from "./_run-paths.mjs";
|
|
63
70
|
import { redact } from "../lib/redact.mjs";
|
|
@@ -75,7 +82,8 @@ export const DEFAULTS = {
|
|
|
75
82
|
subagents: true,
|
|
76
83
|
};
|
|
77
84
|
|
|
78
|
-
const SUBAGENT_TOOLS = new Set(["Agent", "Task"]);
|
|
85
|
+
const SUBAGENT_TOOLS = new Set(["Agent", "Task", "task", "spawn_agent"]);
|
|
86
|
+
const SKILL_TOOL = /^skill$/i;
|
|
79
87
|
const DENIAL =
|
|
80
88
|
/doesn't want to proceed|User rejected tool use|^Permission to use \S+ (?:has been|was) denied|permission denied by the user/i;
|
|
81
89
|
const HOOK_BLOCK = /\bhook (?:error|blocked)|PreToolUse:|PostToolUse:/;
|
|
@@ -84,6 +92,33 @@ const INTERRUPT = /^\[Request interrupted by user/;
|
|
|
84
92
|
export const defaultProjectsRoot = () =>
|
|
85
93
|
process.env.TRANSCRIPTS_ROOT || join(homedir(), ".claude", "projects");
|
|
86
94
|
|
|
95
|
+
export const defaultCopilotRoot = () =>
|
|
96
|
+
process.env.COPILOT_TRANSCRIPTS_ROOT ||
|
|
97
|
+
join(process.env.COPILOT_HOME || join(homedir(), ".copilot"), "session-state");
|
|
98
|
+
|
|
99
|
+
export const defaultCodexRoot = () =>
|
|
100
|
+
process.env.CODEX_TRANSCRIPTS_ROOT ||
|
|
101
|
+
join(process.env.CODEX_HOME || join(homedir(), ".codex"), "sessions");
|
|
102
|
+
|
|
103
|
+
export { detectFormat };
|
|
104
|
+
|
|
105
|
+
const COPILOT_LOG = "events.jsonl";
|
|
106
|
+
|
|
107
|
+
const CODEX_NAME = /^rollout-[\dT:-]+-([0-9a-f]{8})[0-9a-f-]*\.jsonl$/i;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The name a file is cited by. Every Copilot CLI log is `events.jsonl`, so it
|
|
111
|
+
* is prefixed with the first eight characters of its session directory; a
|
|
112
|
+
* Codex rollout name carries its start time and full session id, so it is cut
|
|
113
|
+
* to `rollout-<first eight of the id>.jsonl`. Claude Code names stay as they are.
|
|
114
|
+
*/
|
|
115
|
+
export function displayName(path) {
|
|
116
|
+
const name = basename(path);
|
|
117
|
+
if (name === COPILOT_LOG) return `${basename(dirname(path)).slice(0, 8)}/${name}`;
|
|
118
|
+
const codex = name.match(CODEX_NAME);
|
|
119
|
+
return codex ? `rollout-${codex[1]}.jsonl` : name;
|
|
120
|
+
}
|
|
121
|
+
|
|
87
122
|
/** A session id as Claude Code writes it (a UUID), or any token safe as a file name. */
|
|
88
123
|
const SAFE_SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
89
124
|
|
|
@@ -202,6 +237,7 @@ function inputGist(name, input) {
|
|
|
202
237
|
if (!input || typeof input !== "object") return "";
|
|
203
238
|
for (const k of [
|
|
204
239
|
"command",
|
|
240
|
+
"cmd",
|
|
205
241
|
"file_path",
|
|
206
242
|
"path",
|
|
207
243
|
"pattern",
|
|
@@ -209,6 +245,7 @@ function inputGist(name, input) {
|
|
|
209
245
|
"query",
|
|
210
246
|
"skill",
|
|
211
247
|
"description",
|
|
248
|
+
"input",
|
|
212
249
|
]) {
|
|
213
250
|
if (typeof input[k] === "string" && input[k]) return input[k];
|
|
214
251
|
}
|
|
@@ -274,10 +311,18 @@ export async function indexTranscripts(paths, options = {}) {
|
|
|
274
311
|
let idleGaps = 0;
|
|
275
312
|
|
|
276
313
|
for (const path of expandFiles(paths, o.subagents)) {
|
|
277
|
-
const name =
|
|
314
|
+
const name = displayName(path);
|
|
278
315
|
const at = (line) => `${name}:${line}`;
|
|
279
316
|
const stats = {};
|
|
280
|
-
const fileRow = {
|
|
317
|
+
const fileRow = {
|
|
318
|
+
name,
|
|
319
|
+
path: R.clean(path),
|
|
320
|
+
format: null,
|
|
321
|
+
sidechain: false,
|
|
322
|
+
usagePartial: false,
|
|
323
|
+
malformedAt: [],
|
|
324
|
+
};
|
|
325
|
+
let adapt = null;
|
|
281
326
|
const pending = new Map();
|
|
282
327
|
const groups = new Map();
|
|
283
328
|
let lastKey = null;
|
|
@@ -306,246 +351,287 @@ export async function indexTranscripts(paths, options = {}) {
|
|
|
306
351
|
if (row.reason !== "truncated") fileRow.malformedAt.push(at(row.line));
|
|
307
352
|
continue;
|
|
308
353
|
}
|
|
309
|
-
|
|
354
|
+
if (!adapt) {
|
|
355
|
+
fileRow.format = detectFormat(row.record);
|
|
356
|
+
adapt = createAdapter(fileRow.format);
|
|
357
|
+
}
|
|
310
358
|
const line = row.line;
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
359
|
+
for (const rec of adapt(row.record)) {
|
|
360
|
+
const ts = tsOf(rec);
|
|
361
|
+
if (rec.isSidechain && fileRow.format === "claude") fileRow.sidechain = true;
|
|
362
|
+
|
|
363
|
+
if (ts !== null && prev && prev.ts !== null) {
|
|
364
|
+
const gap = ts - prev.ts;
|
|
365
|
+
if (gap >= stallMs) {
|
|
366
|
+
const startsTurn = rec.type === "user" && isPrompt(rec);
|
|
367
|
+
if (startsTurn && pending.size === 0) idleGaps++;
|
|
368
|
+
else {
|
|
369
|
+
const running = [...pending.values()].at(-1);
|
|
370
|
+
stalls.push({
|
|
371
|
+
from: at(prev.line),
|
|
372
|
+
to: at(line),
|
|
373
|
+
gapMs: gap,
|
|
374
|
+
pendingTool: running ? running.name : null,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
327
377
|
}
|
|
328
378
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (turn) turn.durationMs = numberOr(rec.durationMs, null);
|
|
346
|
-
} else if (Array.isArray(rec.hookErrors) && rec.hookErrors.length) {
|
|
347
|
-
errors.push({ at: at(line), kind: "hook", tool: null, text: ex(textOf(rec.hookErrors)) });
|
|
348
|
-
if (turn) turn.errors++;
|
|
349
|
-
} else if (rec.level === "error") {
|
|
350
|
-
errors.push({ at: at(line), kind: "system", tool: null, text: ex(rec.content) });
|
|
351
|
-
if (turn) turn.errors++;
|
|
379
|
+
if (ts !== null) prev = { ts, line };
|
|
380
|
+
if (turn && ts !== null) turn.lastTs = ts;
|
|
381
|
+
|
|
382
|
+
if (rec.type === "x-usage") {
|
|
383
|
+
const add = rec.usage || {};
|
|
384
|
+
for (const key of ["input", "output", "cacheRead", "cacheWrite"]) {
|
|
385
|
+
const n = numberOr(add[key]);
|
|
386
|
+
usage[key] += n;
|
|
387
|
+
if (turn && rec.inTurn) turn.usage[key] += n;
|
|
388
|
+
}
|
|
389
|
+
if (rec.countsMessage) usage.messages++;
|
|
390
|
+
if (turn && rec.inTurn) {
|
|
391
|
+
const ctx = numberOr(add.input) + numberOr(add.cacheRead) + numberOr(add.cacheWrite);
|
|
392
|
+
if (ctx > turn.usage.peakContext) turn.usage.peakContext = ctx;
|
|
393
|
+
}
|
|
394
|
+
continue;
|
|
352
395
|
}
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
396
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
397
|
+
if (rec.type === "system") {
|
|
398
|
+
if (rec.subtype === "compact_boundary") {
|
|
399
|
+
const m = rec.compactMetadata || {};
|
|
400
|
+
compactions.push({
|
|
401
|
+
at: at(line),
|
|
402
|
+
kind: "boundary",
|
|
403
|
+
trigger: R.clean(m.trigger || "unknown"),
|
|
404
|
+
preTokens: numberOr(m.preTokens, null),
|
|
405
|
+
postTokens: numberOr(m.postTokens, null),
|
|
406
|
+
});
|
|
407
|
+
push({ at: at(line), role: "system", kind: "compaction" });
|
|
408
|
+
} else if (rec.subtype === "turn_duration") {
|
|
409
|
+
if (turn) turn.durationMs = numberOr(rec.durationMs, null);
|
|
410
|
+
} else if (Array.isArray(rec.hookErrors) && rec.hookErrors.length) {
|
|
411
|
+
errors.push({
|
|
412
|
+
at: at(line),
|
|
413
|
+
kind: "hook",
|
|
414
|
+
tool: null,
|
|
415
|
+
text: ex(textOf(rec.hookErrors)),
|
|
416
|
+
});
|
|
417
|
+
if (turn) turn.errors++;
|
|
418
|
+
} else if (rec.level === "error") {
|
|
419
|
+
errors.push({ at: at(line), kind: "system", tool: null, text: ex(rec.content) });
|
|
420
|
+
if (turn) turn.errors++;
|
|
421
|
+
}
|
|
360
422
|
continue;
|
|
361
423
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
call.
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
424
|
+
|
|
425
|
+
if (rec.type === "user") {
|
|
426
|
+
const content = rec.message?.content;
|
|
427
|
+
if (rec.isCompactSummary) {
|
|
428
|
+
compactions.push({ at: at(line), kind: "summary" });
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (Array.isArray(content) && content.some((b) => b?.type === "tool_result")) {
|
|
432
|
+
let first = true;
|
|
433
|
+
for (const b of content) {
|
|
434
|
+
if (b?.type !== "tool_result") continue;
|
|
435
|
+
const call = pending.get(b.tool_use_id);
|
|
436
|
+
pending.delete(b.tool_use_id);
|
|
437
|
+
const result =
|
|
438
|
+
first && rec.toolUseResult !== undefined ? rec.toolUseResult : undefined;
|
|
439
|
+
first = false;
|
|
440
|
+
const body = textOf(b.content) || (typeof result === "string" ? result : "");
|
|
441
|
+
const denied =
|
|
442
|
+
b.denied === true ||
|
|
443
|
+
DENIAL.test(body) ||
|
|
444
|
+
(typeof result === "string" && DENIAL.test(result));
|
|
445
|
+
const isError = b.is_error === true || denied;
|
|
446
|
+
if (call) {
|
|
447
|
+
call.resultAt = at(line);
|
|
448
|
+
call.isError = isError;
|
|
449
|
+
const reported =
|
|
450
|
+
result && typeof result === "object"
|
|
451
|
+
? (result.durationMs ?? result.totalDurationMs)
|
|
452
|
+
: undefined;
|
|
453
|
+
if (Number.isFinite(reported)) {
|
|
454
|
+
call.durationMs = reported;
|
|
455
|
+
call.durationSource = "reported";
|
|
456
|
+
} else if (call.ts !== null && ts !== null) {
|
|
457
|
+
call.durationMs = ts - call.ts;
|
|
458
|
+
call.durationSource = "timestamps";
|
|
459
|
+
}
|
|
460
|
+
if (call.group) call.group.errors += isError ? 1 : 0;
|
|
461
|
+
const sub = subagentByToolId.get(b.tool_use_id);
|
|
462
|
+
if (sub) {
|
|
463
|
+
sub.resultAt = at(line);
|
|
464
|
+
if (result && typeof result === "object") {
|
|
465
|
+
sub.status = R.clean(result.status || (isError ? "error" : "completed"));
|
|
466
|
+
if (result.agentId) subagentByAgentId.set(String(result.agentId), sub);
|
|
467
|
+
if (Number.isFinite(result.totalDurationMs))
|
|
468
|
+
sub.durationMs = result.totalDurationMs;
|
|
469
|
+
if (Number.isFinite(result.totalTokens)) sub.tokens = result.totalTokens;
|
|
470
|
+
if (Number.isFinite(result.totalToolUseCount))
|
|
471
|
+
sub.toolUses = result.totalToolUseCount;
|
|
472
|
+
} else {
|
|
473
|
+
sub.status = isError ? "error" : "completed";
|
|
474
|
+
}
|
|
475
|
+
if (
|
|
476
|
+
!sub.durationMs &&
|
|
477
|
+
call.durationMs !== undefined &&
|
|
478
|
+
sub.status !== "async_launched"
|
|
479
|
+
)
|
|
480
|
+
sub.durationMs = call.durationMs;
|
|
481
|
+
sub.result = ex(body);
|
|
401
482
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
sub.result = ex(body);
|
|
483
|
+
}
|
|
484
|
+
if (isError) {
|
|
485
|
+
const kind = denied ? "denial" : HOOK_BLOCK.test(body) ? "hook" : "error";
|
|
486
|
+
errors.push({ at: at(line), kind, tool: call ? call.name : null, text: ex(body) });
|
|
487
|
+
if (turn) turn.errors++;
|
|
488
|
+
push({ at: at(line), role: "user", kind, name: call ? call.name : null });
|
|
409
489
|
}
|
|
410
490
|
}
|
|
411
|
-
|
|
412
|
-
const kind = denied ? "denial" : HOOK_BLOCK.test(body) ? "hook" : "error";
|
|
413
|
-
errors.push({ at: at(line), kind, tool: call ? call.name : null, text: ex(body) });
|
|
414
|
-
if (turn) turn.errors++;
|
|
415
|
-
push({ at: at(line), role: "user", kind, name: call ? call.name : null });
|
|
416
|
-
}
|
|
491
|
+
continue;
|
|
417
492
|
}
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
493
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
494
|
+
const text = textOf(content);
|
|
495
|
+
if (rec.isSidechain) continue;
|
|
496
|
+
const notice = text.match(/<task-notification>([\s\S]*?)<\/task-notification>/);
|
|
497
|
+
if (notice) {
|
|
498
|
+
const id = (notice[1].match(/<task-id>([^<]+)<\/task-id>/) || [])[1];
|
|
499
|
+
const status = (notice[1].match(/<status>([^<]+)<\/status>/) || [])[1];
|
|
500
|
+
const sub = id ? subagentByAgentId.get(id.trim()) : null;
|
|
501
|
+
if (sub) {
|
|
502
|
+
sub.notifiedAt = at(line);
|
|
503
|
+
if (status) sub.status = R.clean(status.trim());
|
|
504
|
+
const st = rec.subagentStats;
|
|
505
|
+
if (st) {
|
|
506
|
+
if (Number.isFinite(st.tokens)) sub.tokens = st.tokens;
|
|
507
|
+
if (Number.isFinite(st.durationMs)) sub.durationMs = st.durationMs;
|
|
508
|
+
if (Number.isFinite(st.toolUses)) sub.toolUses = st.toolUses;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
push({ at: at(line), role: "user", kind: "notification", text: ex(status || "") });
|
|
512
|
+
continue;
|
|
431
513
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
514
|
+
if (INTERRUPT.test(text)) {
|
|
515
|
+
errors.push({ at: at(line), kind: "interrupt", tool: null, text: ex(text) });
|
|
516
|
+
if (turn) turn.errors++;
|
|
517
|
+
push({ at: at(line), role: "user", kind: "interrupt" });
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
if (rec.isMeta) continue;
|
|
521
|
+
const command = text.match(/<command-name>\s*([^<\s]+)\s*<\/command-name>/);
|
|
522
|
+
if (command) {
|
|
523
|
+
const cmd = R.clean(command[1]);
|
|
524
|
+
invocations.push({ at: at(line), kind: "command", name: cmd });
|
|
525
|
+
openTurn(line, ts, cmd);
|
|
526
|
+
push({ at: at(line), role: "user", kind: "command", name: cmd });
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (!text.trim()) continue;
|
|
530
|
+
const prompt = ex(text);
|
|
531
|
+
openTurn(line, ts, prompt);
|
|
532
|
+
push({ at: at(line), role: "user", kind: "prompt", text: prompt });
|
|
448
533
|
continue;
|
|
449
534
|
}
|
|
450
|
-
if (!text.trim()) continue;
|
|
451
|
-
const prompt = ex(text);
|
|
452
|
-
openTurn(line, ts, prompt);
|
|
453
|
-
push({ at: at(line), role: "user", kind: "prompt", text: prompt });
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
535
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
if (rec.isApiErrorMessage) {
|
|
479
|
-
errors.push({
|
|
480
|
-
at: at(line),
|
|
481
|
-
kind: "api-error",
|
|
482
|
-
tool: null,
|
|
483
|
-
text: ex(textOf(msg.content)),
|
|
484
|
-
});
|
|
485
|
-
if (turn) turn.errors++;
|
|
486
|
-
}
|
|
487
|
-
for (const b of Array.isArray(msg.content) ? msg.content : []) {
|
|
488
|
-
if (b?.type === "text" && b.text?.trim()) {
|
|
489
|
-
push({ at: at(line), role: "assistant", kind: "text", text: ex(b.text) });
|
|
490
|
-
continue;
|
|
536
|
+
if (rec.type === "assistant") {
|
|
537
|
+
const msg = rec.message || {};
|
|
538
|
+
if (!turn && !rec.isSidechain) openTurn(line, ts, null);
|
|
539
|
+
if (turn) turn.assistantMessages++;
|
|
540
|
+
const u = msg.usage;
|
|
541
|
+
if (u && !(msg.id && seenMessages.has(msg.id))) {
|
|
542
|
+
if (msg.id) seenMessages.add(msg.id);
|
|
543
|
+
const add = {
|
|
544
|
+
input: numberOr(u.input_tokens),
|
|
545
|
+
output: numberOr(u.output_tokens),
|
|
546
|
+
cacheRead: numberOr(u.cache_read_input_tokens),
|
|
547
|
+
cacheWrite: numberOr(u.cache_creation_input_tokens),
|
|
548
|
+
};
|
|
549
|
+
for (const k of Object.keys(add)) usage[k] += add[k];
|
|
550
|
+
usage.messages++;
|
|
551
|
+
if (turn) {
|
|
552
|
+
for (const k of Object.keys(add)) turn.usage[k] += add[k];
|
|
553
|
+
const ctx = add.input + add.cacheRead + add.cacheWrite;
|
|
554
|
+
if (ctx > turn.usage.peakContext) turn.usage.peakContext = ctx;
|
|
555
|
+
}
|
|
491
556
|
}
|
|
492
|
-
if (
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
groups.set(key, group);
|
|
557
|
+
if (rec.isApiErrorMessage) {
|
|
558
|
+
errors.push({
|
|
559
|
+
at: at(line),
|
|
560
|
+
kind: "api-error",
|
|
561
|
+
tool: null,
|
|
562
|
+
text: ex(textOf(msg.content)),
|
|
563
|
+
});
|
|
564
|
+
if (turn) turn.errors++;
|
|
501
565
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
name
|
|
509
|
-
input
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if (SUBAGENT_TOOLS.has(b.name)) {
|
|
523
|
-
const sub = {
|
|
566
|
+
for (const b of Array.isArray(msg.content) ? msg.content : []) {
|
|
567
|
+
if (b?.type === "text" && b.text?.trim()) {
|
|
568
|
+
push({ at: at(line), role: "assistant", kind: "text", text: ex(b.text) });
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
if (b?.type !== "tool_use") continue;
|
|
572
|
+
const toolName = R.clean(b.name || "unknown");
|
|
573
|
+
const input = b.input || {};
|
|
574
|
+
const key = `${b.name}\u0000${stable(input)}`;
|
|
575
|
+
let group = groups.get(key);
|
|
576
|
+
if (!group) {
|
|
577
|
+
group = { name: toolName, hash: digest(key), at: [], errors: 0, consecutive: 0 };
|
|
578
|
+
group.input = ex(inputGist(b.name, input));
|
|
579
|
+
groups.set(key, group);
|
|
580
|
+
}
|
|
581
|
+
group.at.push(at(line));
|
|
582
|
+
streak = key === lastKey ? streak + 1 : 1;
|
|
583
|
+
lastKey = key;
|
|
584
|
+
if (streak > group.consecutive) group.consecutive = streak;
|
|
585
|
+
const call = {
|
|
524
586
|
at: at(line),
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
587
|
+
name: toolName,
|
|
588
|
+
input: group.input,
|
|
589
|
+
ts,
|
|
590
|
+
durationMs: undefined,
|
|
591
|
+
durationSource: null,
|
|
530
592
|
resultAt: null,
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
tokens: null,
|
|
534
|
-
toolUses: null,
|
|
535
|
-
result: null,
|
|
593
|
+
isError: false,
|
|
594
|
+
group,
|
|
536
595
|
};
|
|
537
|
-
|
|
538
|
-
if (b.id)
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
invocations.push({
|
|
596
|
+
toolCalls.push(call);
|
|
597
|
+
if (b.id) pending.set(b.id, call);
|
|
598
|
+
if (turn) turn.toolCalls++;
|
|
599
|
+
push({
|
|
542
600
|
at: at(line),
|
|
543
|
-
|
|
544
|
-
|
|
601
|
+
role: "assistant",
|
|
602
|
+
kind: "tool",
|
|
603
|
+
name: toolName,
|
|
604
|
+
text: call.input,
|
|
545
605
|
});
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
606
|
+
|
|
607
|
+
if (SUBAGENT_TOOLS.has(b.name)) {
|
|
608
|
+
const sub = {
|
|
609
|
+
at: at(line),
|
|
610
|
+
agentType: R.clean(input.subagent_type || input.agent_type || "general-purpose"),
|
|
611
|
+
description: ex(input.description || input.message || ""),
|
|
612
|
+
background: input.run_in_background === true || input.mode === "background",
|
|
613
|
+
model: input.model ? R.clean(input.model) : null,
|
|
614
|
+
status: null,
|
|
615
|
+
resultAt: null,
|
|
616
|
+
notifiedAt: null,
|
|
617
|
+
durationMs: null,
|
|
618
|
+
tokens: null,
|
|
619
|
+
toolUses: null,
|
|
620
|
+
result: null,
|
|
621
|
+
};
|
|
622
|
+
subagents.push(sub);
|
|
623
|
+
if (b.id) subagentByToolId.set(b.id, sub);
|
|
624
|
+
}
|
|
625
|
+
if (SKILL_TOOL.test(b.name || "") && (input.skill || input.command)) {
|
|
626
|
+
invocations.push({
|
|
627
|
+
at: at(line),
|
|
628
|
+
kind: "skill",
|
|
629
|
+
name: R.clean(input.skill || input.command),
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
if (b.name === "SlashCommand" && input.command) {
|
|
633
|
+
invocations.push({ at: at(line), kind: "command", name: R.clean(input.command) });
|
|
634
|
+
}
|
|
549
635
|
}
|
|
550
636
|
}
|
|
551
637
|
}
|
|
@@ -564,6 +650,7 @@ export async function indexTranscripts(paths, options = {}) {
|
|
|
564
650
|
});
|
|
565
651
|
}
|
|
566
652
|
}
|
|
653
|
+
fileRow.usagePartial = Boolean(adapt?.usagePartial?.());
|
|
567
654
|
fileRow.lines = stats.lines;
|
|
568
655
|
fileRow.malformed = stats.malformed;
|
|
569
656
|
fileRow.oversized = stats.oversized;
|
|
@@ -603,7 +690,7 @@ export async function indexTranscripts(paths, options = {}) {
|
|
|
603
690
|
errors: errors.length,
|
|
604
691
|
malformedLines: files.reduce((a, f) => a + f.malformed, 0),
|
|
605
692
|
},
|
|
606
|
-
usage,
|
|
693
|
+
usage: { ...usage, partial: files.some((f) => f.usagePartial) },
|
|
607
694
|
turns,
|
|
608
695
|
loops,
|
|
609
696
|
stalls,
|
|
@@ -685,22 +772,99 @@ async function windowMatches(dirs, start, end) {
|
|
|
685
772
|
return files;
|
|
686
773
|
}
|
|
687
774
|
|
|
775
|
+
/** Every Copilot CLI session log under `root` (`<session>/events.jsonl`). */
|
|
776
|
+
function copilotLogs(root) {
|
|
777
|
+
let dirs;
|
|
778
|
+
try {
|
|
779
|
+
dirs = readdirSync(root, { withFileTypes: true });
|
|
780
|
+
} catch {
|
|
781
|
+
return [];
|
|
782
|
+
}
|
|
783
|
+
return dirs
|
|
784
|
+
.filter((e) => e.isDirectory())
|
|
785
|
+
.map((e) => join(root, e.name, COPILOT_LOG))
|
|
786
|
+
.filter((p) => existsSync(p))
|
|
787
|
+
.sort();
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const CODEX_ROLLOUT = /^rollout-.*\.jsonl$/;
|
|
791
|
+
|
|
792
|
+
/** Every Codex rollout under `root` (`YYYY/MM/DD/rollout-*.jsonl`), at most four levels down. */
|
|
793
|
+
function codexRollouts(root, depth = 0) {
|
|
794
|
+
let entries;
|
|
795
|
+
try {
|
|
796
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
797
|
+
} catch {
|
|
798
|
+
return [];
|
|
799
|
+
}
|
|
800
|
+
const out = [];
|
|
801
|
+
for (const e of entries) {
|
|
802
|
+
const p = join(root, e.name);
|
|
803
|
+
if (e.isDirectory() && depth < 3) out.push(...codexRollouts(p, depth + 1));
|
|
804
|
+
else if (e.isFile() && CODEX_ROLLOUT.test(e.name)) out.push(p);
|
|
805
|
+
}
|
|
806
|
+
return out.sort();
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/** The first timestamp and the starting cwd of a Copilot CLI or Codex session. */
|
|
810
|
+
async function sessionHead(path) {
|
|
811
|
+
let first = null;
|
|
812
|
+
let cwd = null;
|
|
813
|
+
let seen = 0;
|
|
814
|
+
for await (const row of readJsonl(path, { maxBytes: 1024 * 1024 })) {
|
|
815
|
+
if (!row.record) continue;
|
|
816
|
+
if (first === null) first = tsOf(row.record);
|
|
817
|
+
cwd = cwd ?? sessionCwd(row.record);
|
|
818
|
+
if ((first !== null && cwd !== null) || ++seen >= 8) break;
|
|
819
|
+
}
|
|
820
|
+
return { first, cwd };
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const samePath = (p) => String(p).replace(/\/+$/, "");
|
|
824
|
+
|
|
825
|
+
async function hostWindowMatches(paths, cwds, start, end) {
|
|
826
|
+
const files = [];
|
|
827
|
+
for (const p of paths) {
|
|
828
|
+
let mtime;
|
|
829
|
+
try {
|
|
830
|
+
mtime = statSync(p).mtimeMs;
|
|
831
|
+
} catch {
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
if (mtime < start) continue;
|
|
835
|
+
const head = await sessionHead(p);
|
|
836
|
+
if (head.first === null || head.first > end) continue;
|
|
837
|
+
if (head.cwd === null || !cwds.has(samePath(head.cwd))) continue;
|
|
838
|
+
files.push(p);
|
|
839
|
+
}
|
|
840
|
+
return files;
|
|
841
|
+
}
|
|
842
|
+
|
|
688
843
|
/**
|
|
689
|
-
* The session files a run ran in.
|
|
844
|
+
* The session files a run ran in, across Claude Code, Copilot CLI and Codex.
|
|
690
845
|
*
|
|
691
846
|
* A run launched with a session id (continuous mode, a client that set
|
|
692
847
|
* MULTI_AGENT_SESSION_ID) has it recorded in agent-state.json, and the file is
|
|
693
|
-
* `<sessionId>.jsonl` in whichever project directory holds it
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
848
|
+
* `<sessionId>.jsonl` in whichever Claude Code project directory holds it,
|
|
849
|
+
* `<sessionId>/events.jsonl` under Copilot CLI's session-state, or the Codex
|
|
850
|
+
* rollout whose name ends in `-<sessionId>.jsonl`. An attended run records
|
|
851
|
+
* none, so the fallback is the sessions whose working directory was the
|
|
852
|
+
* worktree or the main checkout and whose time span overlaps the run's:
|
|
853
|
+
* started before the run finished, last written after it started. Claude Code
|
|
854
|
+
* keys its directories by the working directory; Copilot CLI and Codex record
|
|
855
|
+
* it in the session's opening record. That match is a heuristic and is
|
|
856
|
+
* reported as `time-window`.
|
|
698
857
|
*
|
|
699
858
|
* @returns {Promise<{taskId:string|null, runDir:string|null, matchedBy:"session"|"time-window"|null, files:string[], reason?:string}>}
|
|
700
859
|
*/
|
|
701
860
|
export async function findRunTranscripts(
|
|
702
861
|
id,
|
|
703
|
-
{
|
|
862
|
+
{
|
|
863
|
+
logsRoot: root = logsRoot(),
|
|
864
|
+
projectsRoot = defaultProjectsRoot(),
|
|
865
|
+
copilotRoot = defaultCopilotRoot(),
|
|
866
|
+
codexRoot = defaultCodexRoot(),
|
|
867
|
+
} = {},
|
|
704
868
|
) {
|
|
705
869
|
const hit = locateRun(id, root);
|
|
706
870
|
if (!hit)
|
|
@@ -715,17 +879,24 @@ export async function findRunTranscripts(
|
|
|
715
879
|
const base = { taskId: doc.taskId || hit.run.taskId, runDir: hit.run.dir };
|
|
716
880
|
const none = (reason) => ({ ...base, matchedBy: null, files: [], reason });
|
|
717
881
|
|
|
718
|
-
let projectDirs;
|
|
882
|
+
let projectDirs = [];
|
|
719
883
|
try {
|
|
720
884
|
projectDirs = readdirSync(projectsRoot).map((d) => join(projectsRoot, d));
|
|
721
885
|
} catch {
|
|
722
|
-
|
|
886
|
+
// No Claude Code transcripts on this machine; the other hosts may have some.
|
|
723
887
|
}
|
|
888
|
+
const copilot = copilotLogs(copilotRoot);
|
|
889
|
+
const codex = codexRollouts(codexRoot);
|
|
890
|
+
if (!projectDirs.length && !copilot.length && !codex.length)
|
|
891
|
+
return none("no transcript directory");
|
|
724
892
|
|
|
725
893
|
if (typeof doc.sessionId === "string" && SAFE_SESSION_ID.test(doc.sessionId)) {
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
.filter((p) => existsSync(p))
|
|
894
|
+
const sid = doc.sessionId;
|
|
895
|
+
const files = [
|
|
896
|
+
...projectDirs.map((d) => join(d, `${sid}.jsonl`)).filter((p) => existsSync(p)),
|
|
897
|
+
...copilot.filter((p) => basename(dirname(p)) === sid),
|
|
898
|
+
...codex.filter((p) => basename(p).endsWith(`-${sid}.jsonl`)),
|
|
899
|
+
];
|
|
729
900
|
if (files.length) return { ...base, matchedBy: "session", files };
|
|
730
901
|
}
|
|
731
902
|
|
|
@@ -741,12 +912,16 @@ export async function findRunTranscripts(
|
|
|
741
912
|
const cwds = [doc.worktreePath, doc.projectRoot];
|
|
742
913
|
for (const p of Array.isArray(doc.projects) ? doc.projects : [])
|
|
743
914
|
cwds.push(p?.worktreePath, p?.projectRoot);
|
|
744
|
-
const
|
|
915
|
+
const known = cwds.filter((c) => typeof c === "string" && c);
|
|
916
|
+
const slugs = new Set(known.map(projectSlug));
|
|
745
917
|
const dirs = projectDirs.filter((d) => slugs.has(basename(d)));
|
|
746
|
-
if (!Number.isFinite(start) || !dirs.length)
|
|
918
|
+
if (!Number.isFinite(start) || (!dirs.length && !copilot.length && !codex.length))
|
|
747
919
|
return none("no session id recorded and no transcript directory for the run's checkout");
|
|
748
920
|
|
|
749
|
-
const files =
|
|
921
|
+
const files = [
|
|
922
|
+
...(await windowMatches(dirs, start, end)),
|
|
923
|
+
...(await hostWindowMatches([...copilot, ...codex], new Set(known.map(samePath)), start, end)),
|
|
924
|
+
];
|
|
750
925
|
return files.length
|
|
751
926
|
? { ...base, matchedBy: "time-window", files }
|
|
752
927
|
: none("no session overlaps the run's time window");
|
|
@@ -781,7 +956,10 @@ export function renderIndex(idx) {
|
|
|
781
956
|
`${c.malformedLines} malformed line(s) skipped, redacted ${idx.redaction.total} item(s)${by ? ` (${by})` : ""}`,
|
|
782
957
|
);
|
|
783
958
|
L.push(
|
|
784
|
-
`usage: in ${k(idx.usage.input)} / out ${k(idx.usage.output)} / cache read ${k(idx.usage.cacheRead)} / cache write ${k(idx.usage.cacheWrite)} over ${idx.usage.messages} message(s)
|
|
959
|
+
`usage: in ${k(idx.usage.input)} / out ${k(idx.usage.output)} / cache read ${k(idx.usage.cacheRead)} / cache write ${k(idx.usage.cacheWrite)} over ${idx.usage.messages} message(s)` +
|
|
960
|
+
(idx.usage.partial
|
|
961
|
+
? " - partial: a Copilot CLI session wrote no session.shutdown record, so only its output tokens are counted"
|
|
962
|
+
: ""),
|
|
785
963
|
);
|
|
786
964
|
L.push("", "files");
|
|
787
965
|
for (const f of idx.files) {
|
|
@@ -791,8 +969,11 @@ export function renderIndex(idx) {
|
|
|
791
969
|
f.oversized ? `oversized ${f.oversized}` : null,
|
|
792
970
|
f.truncated ? "truncated at the byte cap" : null,
|
|
793
971
|
f.unreadable ? "unreadable" : null,
|
|
972
|
+
f.usagePartial ? "usage partial (no session.shutdown)" : null,
|
|
794
973
|
].filter(Boolean);
|
|
795
|
-
L.push(
|
|
974
|
+
L.push(
|
|
975
|
+
` ${f.name} ${f.format ?? "empty"} ${f.lines} lines${flags.length ? ` ${flags.join(" ")}` : ""}`,
|
|
976
|
+
);
|
|
796
977
|
}
|
|
797
978
|
L.push("", `turns (${idx.turns.length})`);
|
|
798
979
|
idx.turns.forEach((t, i) => {
|
|
@@ -821,7 +1002,7 @@ export function renderIndex(idx) {
|
|
|
821
1002
|
for (const m of idx.compactions)
|
|
822
1003
|
L.push(
|
|
823
1004
|
m.kind === "boundary"
|
|
824
|
-
? ` ${m.at} ${m.trigger} ${k(m.preTokens
|
|
1005
|
+
? ` ${m.at} ${m.trigger} ${m.preTokens === null ? "?" : k(m.preTokens)} -> ${m.postTokens === null ? "?" : k(m.postTokens)} tokens`
|
|
825
1006
|
: ` ${m.at} summary turn`,
|
|
826
1007
|
);
|
|
827
1008
|
L.push("", `subagents (${idx.subagents.length})`);
|
|
@@ -913,7 +1094,7 @@ async function main() {
|
|
|
913
1094
|
taskId: run.taskId ? R.clean(run.taskId) : null,
|
|
914
1095
|
runDir: run.runDir ? tilde(run.runDir) : null,
|
|
915
1096
|
matchedBy: run.matchedBy,
|
|
916
|
-
files: run.files.map((f) =>
|
|
1097
|
+
files: run.files.map((f) => displayName(f)),
|
|
917
1098
|
reason: run.reason ?? null,
|
|
918
1099
|
};
|
|
919
1100
|
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
|