fix(observer): narrow parallel_session detector to tool_result evidence (C-2)
extractEnvironment was scanning JSON.stringify(turn) for collision markers
(чужой staged / foreign git index / index.lock / another git process). Prose
mentions in user/assistant text flipped parallel_session=true. Live FP proven
on episodes-2026-05.jsonl line 20: my own analysis turn was non-parallel but
recorded parallel_session: true because the finding text mentioned the markers.
Fix: collectToolResultText(turn) — gather text only from tool_result blocks
(both string content and structured `[{type:text,text}]` arrays). Scan THAT
for collision markers; prose is no longer a signal.
Tests: rewrote `parallel_session narrowed` block — false on user/assistant
prose / no-tool-result turns; true on tool_result strings + structured form.
106/106 parser tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -165,11 +165,41 @@ export function extractEnvironment(allEntries, turnStartIdx) {
|
||||
|
||||
// Only strong collision evidence — a bare mention of "parallel sessions" is
|
||||
// not a signal (best-effort per spec R2; prefer false-negative over false-positive).
|
||||
const parallel_session = /чужой staged|foreign git index|index\.lock|another git process/i.test(rawTurn);
|
||||
// Scope NARROWED to tool_result content (real command output / Bash stderr): prose
|
||||
// mentions in user prompts / assistant text — including analysis text that
|
||||
// references collision phrases — must not trigger. Fixes live FP (episode line 20).
|
||||
const parallel_session = /чужой staged|foreign git index|index\.lock|another git process/i.test(
|
||||
collectToolResultText(turn)
|
||||
);
|
||||
|
||||
return { economy_level, model, post_compaction, session_turn, parallel_session };
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect text content from tool_result blocks in the turn — the only surface
|
||||
* trusted for parallel_session collision evidence (see extractEnvironment).
|
||||
* Supports both string content and the structured array form
|
||||
* (`content: [{ type: 'text', text }]`).
|
||||
*/
|
||||
function collectToolResultText(turn) {
|
||||
const parts = [];
|
||||
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_result') continue;
|
||||
const c = b.content;
|
||||
if (typeof c === 'string') {
|
||||
parts.push(c);
|
||||
} else if (Array.isArray(c)) {
|
||||
for (const sub of c) {
|
||||
if (sub && typeof sub.text === 'string') parts.push(sub.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
/** Task size: total tool calls + unique file paths touched (per spec §3, gap-resolution 2). */
|
||||
export function extractTaskSize(turn) {
|
||||
let tool_calls = 0;
|
||||
|
||||
Reference in New Issue
Block a user