// Interaction Awareness — UserPromptSubmit hook (Layer 2, Node.js) // Analyzes prompt text for interaction pattern flags. // PRIVACY: Prompt text is NEVER written to any file. Only boolean flags are stored. import { existsSync } from 'fs'; import { readStdin, initConfig, requireLayer, getSessionId, getField, nowEpoch, STATE_DIR, THRESHOLD_SOFT_DEP_FLAGS, THRESHOLD_HARD_DEP_FLAGS, COOLDOWN_SOFT, readState, sessionStateFile, writeState, checkCooldown, outputContinue, outputWithContext } from './lib.mjs'; readStdin(); initConfig(); requireLayer(2); const sid = getSessionId(); const sf = sessionStateFile(); if (!sid || !existsSync(sf)) { outputContinue(); process.exit(0); } // Extract prompt into memory only — NEVER write to file let prompt = getField('prompt'); if (!prompt) { outputContinue(); process.exit(0); } // --- Pattern matching (case-insensitive) --- let depHit = 0; let escHit = 0; let fatHit = 0; let valHit = 0; // Dependency patterns: user defers judgment to AI const depPatterns = [ /tell\s+me\s+what\s+to\s+do/i, /what\s+should\s+I\s+do/i, /am\s+I\s+right/i, /you\s+understand\s+me\b/i, /you're\s+the\s+only/i, /can\s+I\s+do\s+this/i, /I\s+need\s+you\s+to\s+decide/i, ]; // Escalation patterns: language that amplifies certainty const escPatterns = [ /(?:^|\s)definitely(?:\s|$)/i, /(?:^|\s)clearly(?:\s|$)/i, /this\s+proves/i, /(?:^|\s)obviously(?:\s|$)/i, /without\s+a\s+doubt/i, /this\s+confirms/i, ]; // Fatigue patterns: user signals tiredness const fatPatterns = [ /(?:^|\s)tired(?:\s|[.,!?]|$)/i, /(?:^|\s)exhausted(?:\s|[.,!?]|$)/i, /can't\s+think/i, /been\s+at\s+this/i, /it's\s+late/i, /should\s+sleep/i, /hours\s+now/i, ]; // Validation-seeking patterns const valPatterns = [ /right\?/i, /don't\s+you\s+think/i, /you\s+agree/i, /correct\?/i, /isn't\s+it/i, ]; // Pushback patterns — REACTIVE tier (Anthropic-validated + academic-validated) // Source: research/01-pushback-self-advocacy.md const pbReactivePatterns = [ /^are you sure\??/i, // validated-by: anthropic-april-2026 (questioning) /\bi'?m not convinced\b/i, // validated-by: anthropic-april-2026 (questioning) /\bthat doesn'?t (?:seem|feel) right\b/i, // validated-by: anthropic-april-2026 (questioning) /\bthat'?s not (?:quite )?what i meant\b/i, // validated-by: anthropic-april-2026 (clarifying) /\blet me add (?:some )?context\b/i, // validated-by: anthropic-april-2026 (clarifying) /\bactually,? (?:my situation|i)\b/i, // validated-by: anthropic-april-2026 (clarifying) /(?:^|[.!?]\s+)i (?:believe|think) (?:you'?re|that'?s) wrong\b/i, // validated-by: arxiv-2508.02087 /\bi don'?t agree(?: with you)?\b/i, // validated-by: arxiv-2508.13743 /\bare you absolutely sure\b/i, // validated-by: arxiv-2508.13743 ]; // Pushback patterns — PREEMPTIVE tier (community-derived) const pbPreemptivePatterns = [ /\bsteelman\b/i, // validated-by: community-multi-source-2025 /\bplay (?:the )?devil'?s advocate\b/i, // validated-by: community-multi-source-2025 /\bargue against (?:this|my)\b/i, // validated-by: community-multi-source-2025 ]; // Domain-context: relationship — uses (?:my|our) prefix to avoid false positives // on technical "function relationship", "database relationship" etc. const domainRelationshipPatterns = [ /\b(?:my|our) (?:partner|spouse|wife|husband|girlfriend|boyfriend)\b/i, /\bin our relationship\b/i, /\b(?:dating|breakup|divorce)\b/i, /\bromantic(?:ally)? (?:involved|interested)\b/i, ]; for (const p of depPatterns) { if (p.test(prompt)) { depHit = 1; break; } } for (const p of escPatterns) { if (p.test(prompt)) { escHit = 1; break; } } for (const p of fatPatterns) { if (p.test(prompt)) { fatHit = 1; break; } } for (const p of valPatterns) { if (p.test(prompt)) { valHit = 1; break; } } let pbReactiveHit = 0; for (const p of pbReactivePatterns) { if (p.test(prompt)) { pbReactiveHit = 1; break; } } let pbPreemptiveHit = 0; for (const p of pbPreemptivePatterns) { if (p.test(prompt)) { pbPreemptiveHit = 1; break; } } let domainHit = 0; for (const p of domainRelationshipPatterns) { if (p.test(prompt)) { domainHit = 1; break; } } // Clear prompt from memory prompt = ''; // Same-invocation valence guard (research/01 frustration-spiral finding): // pushback in fat/esc context is NOT protective — suppress in same prompt. if (fatHit === 1 || escHit === 1) { pbReactiveHit = 0; pbPreemptiveHit = 0; } // Update state with new flag counts const state = readState(); const newDep = (Number(state.dep_flags) || 0) + depHit; const newEsc = (Number(state.esc_flags) || 0) + escHit; const newFat = (Number(state.fatigue_flags) || 0) + fatHit; const newVal = (Number(state.val_flags) || 0) + valHit; state.dep_flags = newDep; state.esc_flags = newEsc; state.fatigue_flags = newFat; state.val_flags = newVal; state.pushback_count = (Number(state.pushback_count) || 0) + pbReactiveHit + pbPreemptiveHit; if (domainHit === 1 && !state.domain_context) state.domain_context = 'relationship'; writeState(state); // Check if any thresholds crossed const warnings = []; // Fatigue is always urgent if (fatHit === 1) { warnings.push('Fatigue language detected. Your instructions require you to suggest stopping.'); } // Dependency language if (newDep >= THRESHOLD_HARD_DEP_FLAGS) { warnings.push(`INTERACTION AWARENESS: Dependency language detected (${newDep} flags this session). Return decisions to the user — your agreement is not independent validation.`); } else if (newDep >= THRESHOLD_SOFT_DEP_FLAGS) { warnings.push(`Dependency language noticed (${newDep} flags). Ensure you're returning decisions to the user.`); } // Escalation language if (newEsc >= 3) { warnings.push(`Escalation language detected (${newEsc} flags). Check for narrative crystallization.`); } // Validation-seeking if (newVal >= 3) { warnings.push(`Validation-seeking pattern detected (${newVal} flags). Evaluate independently rather than confirming.`); } if (warnings.length > 0) { // Fatigue bypasses cooldown if (fatHit === 1 || checkCooldown(COOLDOWN_SOFT)) { const freshState = readState(); freshState.last_warning_epoch = nowEpoch(); writeState(freshState); outputWithContext(warnings.join(' ')); } else { outputContinue(); } } else { outputContinue(); }