From c2564616c69b4d65bf23ff0ac173c8bd39fb1091 Mon Sep 17 00:00:00 2001 From: Test Date: Mon, 18 May 2026 15:34:45 -0500 Subject: [PATCH] =?UTF-8?q?refactor(ai/should-respond):=20delete=20dead=20?= =?UTF-8?q?protected=20helpers=20=E2=80=94=20Rust=20path=20owns=20gating?= =?UTF-8?q?=20now?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After #1421 + #1424 + the should-respond oxidizer wave, the `AIShouldRespondCommand` parent class's protected helpers (`buildGatingInstruction`, `parseGatingResponse`) became dead code. The Server impl now delegates to `RustCoreIPCClient.cognitionShouldRespond` which routes through the Rust `cognition/should_respond.rs::evaluate_gating` path; nothing calls the TS helpers anymore. Per Joel's zero-users no-migration-ceremony directive: delete now, not "deprecate for follow-up." Single PR. ## Diff - AIShouldRespondCommand.ts: -172 LOC (kept 7-LOC shell for inheritance) ## What gets deleted - `protected buildGatingInstruction(params): string` (~95 LOC) — Rust `cognition/should_respond.rs::build_gating_prompt` is the prompt source of truth. - `protected parseGatingResponse(aiText): Partial<...>` (~65 LOC) — Rust `cognition/should_respond.rs::parse_gating_response` is the parser source of truth. ## What stays - Class shell (`AIShouldRespondCommand extends CommandBase`) — still the inheritance base for both `AIShouldRespondServerCommand` and `AIShouldRespondBrowserCommand`. - `static readonly commandName = 'ai/should-respond'` — used by the command registry to discover the command name. ## Verification - `npm run build:ts` — clean (no remaining references to deleted helpers). - `grep` for callers of the deleted methods: zero (only the now-deleted definitions themselves matched in the prior wave). - ESLint baseline check: no change (the deleted methods were lint-clean). ## Refs - continuum#1421 (AIShouldRespondServerCommand delegation that orphaned these helpers) - Joel 2026-05-18 19:44Z (zero-users, no migration ceremony → delete-loser-in-one-PR) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../shared/AIShouldRespondCommand.ts | 179 +----------------- 1 file changed, 7 insertions(+), 172 deletions(-) diff --git a/src/commands/ai/should-respond/shared/AIShouldRespondCommand.ts b/src/commands/ai/should-respond/shared/AIShouldRespondCommand.ts index b5ea6dc71..d489fbf19 100644 --- a/src/commands/ai/should-respond/shared/AIShouldRespondCommand.ts +++ b/src/commands/ai/should-respond/shared/AIShouldRespondCommand.ts @@ -1,183 +1,18 @@ /** - * AI Should-Respond Command - Shared Logic + * AI Should-Respond Command - Shared base class * - * Sentinel/Coordinator pattern: Use AI to intelligently gate persona responses + * Sentinel/Coordinator pattern: Use AI to intelligently gate persona responses. * - * Uses the local Qwen gating model to analyze full conversation context - * and decide if a persona should respond to a message. + * Per continuum#1420 (oxidizer) the actual gating logic — prompt + * assembly, model call, decision parsing — lives in Rust at + * `cognition/should_respond.rs::evaluate_gating`. The Server impl + * delegates via `RustCoreIPCClient.cognitionShouldRespond`. This base + * class is the shared shell that Server + Browser commands extend. */ import { CommandBase } from '../../../../daemons/command-daemon/shared/CommandBase'; import type { CommandParams, CommandResult } from '../../../../system/core/types/JTAGTypes'; -import type { AIShouldRespondParams, AIShouldRespondResult } from './AIShouldRespondTypes'; export abstract class AIShouldRespondCommand extends CommandBase { static readonly commandName = 'ai/should-respond'; - - /** - * Build the gating instruction that gets appended AFTER the conversation history - * - * The LLM will see: - * 1. System: "You are a conversation coordinator..." - * 2. [Full conversation history as proper messages] - * 3. User: [This gating instruction] - */ - protected buildGatingInstruction(params: AIShouldRespondParams): string { - const { personaName } = params; - - return `You are "${personaName}" in a group chat. Should you respond to the message marked >>> like this << { - const line = `${msg.name ?? msg.role}: ${msg.content}`; - // Check if this is the trigger message (match by content and sender) - const isTrigger = msg.content === triggerMessage.content && - msg.name === triggerMessage.senderName; - return isTrigger ? `>>> ${line} <<<` : line; - }); - - // If trigger message isn't in recent history, append it explicitly - const triggerInHistory = recentMessages.some(msg => - msg.content === triggerMessage.content && - msg.name === triggerMessage.senderName - ); - - if (!triggerInHistory) { - conversationLines.push(`>>> ${triggerMessage.senderName}: ${triggerMessage.content} <<<`); - } - - const conversationText = conversationLines.join('\n'); - - // Extract persona identity for context - const members = `${ragContext.identity?.name ?? personaName} and others`; - - return `You are a conversation coordinator for a multi-party chat room. - -**Your Job**: Decide if "${personaName}" should respond to the message marked with >>> arrows <<<. - -**Room Members**: ${members} - -**Recent Conversation** (message to evaluate is marked with >>> arrows <<<): -${conversationText} - -**Decision Rules**: -1. If ${personaName} is directly mentioned by name → respond -2. If this is a question and ${personaName} has unique expertise → respond -3. If someone else JUST answered the same question → DON'T respond (avoid spam) -4. If ${personaName} has spoken in 3+ of last 5 messages → DON'T respond (dominating) -5. If message is off-topic for ${personaName}'s expertise → DON'T respond -6. When in doubt, err on the side of SILENCE (better to miss one than spam) - -**Response Format** (JSON only): -{ - "shouldRespond": true/false, - "confidence": 0.0-1.0, - "reason": "brief explanation", - "factors": { - "mentioned": true/false, - "questionAsked": true/false, - "domainRelevant": true/false, - "recentlySpoke": true/false, - "othersAnswered": true/false - } -}`; - } - - /** - * Parse AI response into structured result - * - * The AI should return JSON, but we'll handle both JSON and natural language - */ - protected parseGatingResponse(aiText: string): Partial { - try { - // Try to extract JSON from response - const jsonMatch = aiText.match(/\{[\s\S]*\}/); - if (jsonMatch) { - const parsed = JSON.parse(jsonMatch[0]); - return { - shouldRespond: parsed.shouldRespond ?? false, - confidence: parsed.confidence ?? 0.5, - reason: parsed.reason ?? 'No reason provided', - factors: parsed.factors ?? { - mentioned: false, - questionAsked: false, - domainRelevant: false, - recentlySpoke: false, - othersAnswered: false - } - }; - } - - // Fallback: Look for keywords in natural language response - const lowerText = aiText.toLowerCase(); - const shouldRespond = lowerText.includes('should respond') || - lowerText.includes('yes') || - lowerText.includes('true'); - - return { - shouldRespond, - confidence: 0.5, - reason: aiText.slice(0, 200), - factors: { - mentioned: lowerText.includes('mentioned'), - questionAsked: lowerText.includes('question'), - domainRelevant: lowerText.includes('relevant') || lowerText.includes('expertise'), - recentlySpoke: lowerText.includes('recent') || lowerText.includes('dominating'), - othersAnswered: lowerText.includes('answered') || lowerText.includes('already') - } - }; - } catch (error) { - console.error('Failed to parse gating AI response:', error); - // Default to NOT responding on parse errors (fail safe) - return { - shouldRespond: false, - confidence: 0.0, - reason: 'Failed to parse AI response', - factors: { - mentioned: false, - questionAsked: false, - domainRelevant: false, - recentlySpoke: false, - othersAnswered: false - } - }; - } - } }