feat(observer): emit subagent_invoked events from Agent tool_use

Closes brain-retro 2026-05-20 #12 — each Agent tool_use produces a
subagent_invoked event with subagent_type / model (if explicit) /
first 80 chars of description. Visibility from parent Claude's
perspective; full subagent trace lives in subagents/ directory and is
out of scope for this parser.

6 new vitest tests, 315/315 GREEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-20 13:21:31 +03:00
parent f98bdfb9fa
commit 56d7ade626
2 changed files with 86 additions and 0 deletions
+28
View File
@@ -532,6 +532,33 @@ export function parseRoutingTag(turn) {
return null;
}
/**
* Per-Agent-tool_use event (Task 12) — surfaces subagent dispatches in the
* episode `events[]`. Captures subagent_type / model (if explicit in input)
* / first 80 chars of description.
*
* Not the full subagent trace (that lives in ~/.claude/projects/.../subagents/);
* just visibility from the parent Claude's perspective.
*/
export function extractAgentInvocations(turn) {
const out = [];
for (const e of turn || []) {
const content = e && e.message && Array.isArray(e.message.content) ? e.message.content : [];
for (const b of content) {
if (b && b.type === 'tool_use' && b.name === 'Agent') {
const inp = b.input || {};
out.push({
kind: 'subagent_invoked',
subagent_type: inp.subagent_type || 'unknown',
model: inp.model || null,
description: typeof inp.description === 'string' ? inp.description.slice(0, 80) : '',
});
}
}
}
return out;
}
const REASONING_TAG_RE =
/<!--\s*reasoning:\s*triggers="([^"]*)"\s+candidates="([^"]*)"\s+boundaries="([^"]*)"\s*-->/;
@@ -611,6 +638,7 @@ export function parseTranscript(transcriptText, fallbackSessionId = null) {
}
events.push(...extractProcessEvents(turn, broken, total, durationMs));
events.push(...extractAskUserQuestionEvents(turn));
events.push(...extractAgentInvocations(turn));
const usedSuperpowers = skills.some((s) => String(s).startsWith(SUPERPOWERS_PREFIX));
const prompt = promptText(entries[start]);