fix(observer): exclude synthetic user-role messages from turn detection
Root cause (systematic-debugging): isRealUserPrompt treated skill-content
("Base directory for this skill:"), local-command output
(<local-command-stdout>), and interrupt markers as genuine prompts.
findTurnStart then anchored a turn on the synthetic message — the turn
slice missed the genuine prompt's UserPromptSubmit hook_additional_context
attachment → economy_level: null, wrong prompt_signal/task_classification.
Same cause made extractLastUserPromptText return skill content, so the
Stop-hook routing-gate false-positive-blocked autonomous §12 skill
invocations (detectMethodDirected saw the node name in skill text).
Fix: SYNTHETIC_PROMPT_MARKERS + isSyntheticPrompt — isRealUserPrompt
returns false for synthetic messages. One fix closes both the
economy_level capture gap and the 2nd routing-gate FP class.
160/160 tools tests GREEN (+3 new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,16 +36,40 @@ function parseLines(text) {
|
||||
return { entries, broken, total };
|
||||
}
|
||||
|
||||
// A genuine user prompt (turn boundary) — not a tool_result carrier message.
|
||||
// Synthetic user-role messages — NOT genuine prompts, must not be turn boundaries.
|
||||
// Skill invocation content, local slash-command output/invocation, interrupt markers
|
||||
// are recorded with role:'user' but carry no UserPromptSubmit hook context.
|
||||
const SYNTHETIC_PROMPT_MARKERS = [
|
||||
'Base directory for this skill:',
|
||||
'<local-command-stdout>',
|
||||
'<local-command-caveat>',
|
||||
'<command-name>',
|
||||
'[Request interrupted by user]',
|
||||
];
|
||||
|
||||
function isSyntheticPrompt(text) {
|
||||
const t = String(text || '').trimStart();
|
||||
return SYNTHETIC_PROMPT_MARKERS.some((m) => t.startsWith(m));
|
||||
}
|
||||
|
||||
// A genuine user prompt (turn boundary) — not a tool_result carrier nor a
|
||||
// synthetic skill/command/interrupt message.
|
||||
function isRealUserPrompt(entry) {
|
||||
const msg = entry && entry.message;
|
||||
if (!msg || msg.role !== 'user') return false;
|
||||
const c = msg.content;
|
||||
if (typeof c === 'string') return c.trim().length > 0;
|
||||
if (typeof c === 'string') {
|
||||
return c.trim().length > 0 && !isSyntheticPrompt(c);
|
||||
}
|
||||
if (Array.isArray(c)) {
|
||||
const hasToolResult = c.some((b) => b && b.type === 'tool_result');
|
||||
const hasText = c.some((b) => b && b.type === 'text');
|
||||
return hasText && !hasToolResult;
|
||||
if (!hasText || hasToolResult) return false;
|
||||
const text = c
|
||||
.filter((b) => b && b.type === 'text')
|
||||
.map((b) => b.text || '')
|
||||
.join(' ');
|
||||
return !isSyntheticPrompt(text);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user