Files
boocode/apps/server/src/services/inference/sentinels.ts
indifferentketchup 8c200216eb refactor: codebase audit cleanup — dead code, dedup, module splits
Multi-agent audit + aggressive cleanup across server/web/coder/booterm,
delivered behind a DEFER discipline so none of the in-flight files were
touched. Removes dead code/deps/columns, dedups server + coder helpers,
and splits the oversized modules (tools.ts, opencode-server.ts,
sentinel-summaries, turn.ts, TerminalPane.tsx) behind stable contracts.
Adds 78 parity/unit tests (server 587, coder 323); fixes two latent bugs
(ChatPane queue keys, FileViewerOverlay blank-line parity).

Intended tag: v2.7.12-audit-cleanup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:12:29 +00:00

75 lines
2.8 KiB
TypeScript

import type { Message, ToolCall } from '../../types/api.js';
// v1.11.6: doom-loop guard. When the model calls the same tool with the
// same arguments DOOM_LOOP_THRESHOLD times in a row within one user-message
// turn, abort the recursion and run the same wrap-up summary path as the
// cap-hit case. Ported from opencode (DOOM_LOOP_THRESHOLD in
// session/processor.ts). Threshold of 3 is the smallest value that doesn't
// false-positive on a model that retries once after a transient error.
export const DOOM_LOOP_THRESHOLD = 3;
// Returns the name + args of the looping tool when the LAST
// DOOM_LOOP_THRESHOLD entries in `recentToolCalls` are identical (same name
// AND deep-equal args via JSON.stringify). Returns null otherwise.
// Pure; exported for unit-test access.
export function detectDoomLoop(
recentToolCalls: ToolCall[],
): { name: string; args: Record<string, unknown> } | null {
if (recentToolCalls.length < DOOM_LOOP_THRESHOLD) return null;
const last = recentToolCalls.slice(-DOOM_LOOP_THRESHOLD);
const ref = last[0]!;
const refArgs = JSON.stringify(ref.args);
for (let i = 1; i < last.length; i++) {
const tc = last[i]!;
if (tc.name !== ref.name) return null;
if (JSON.stringify(tc.args) !== refArgs) return null;
}
return { name: ref.name, args: ref.args };
}
// All sentinel kinds. isAnySentinel and compaction.ts's local predicate both
// consume this set — single source so a new kind can't be missed in one.
export const SENTINEL_KINDS = new Set(['cap_hit', 'doom_loop', 'mistake_recovery']);
export function isCapHitSentinel(m: Message): boolean {
return (
m.role === 'system' &&
m.metadata !== null &&
typeof m.metadata === 'object' &&
(m.metadata as { kind?: unknown }).kind === 'cap_hit'
);
}
// v1.11.6: parallel predicate. Same UI-only semantics as cap-hit sentinels —
// never sent to the LLM (filtered by buildMessagesPayload through the
// isAnySentinel check below).
export function isDoomLoopSentinel(m: Message): boolean {
return (
m.role === 'system' &&
m.metadata !== null &&
typeof m.metadata === 'object' &&
(m.metadata as { kind?: unknown }).kind === 'doom_loop'
);
}
// #12: mistake-recovery sentinel. Same UI-only semantics as cap-hit /
// doom-loop — never sent to the LLM (filtered via the isAnySentinel check
// below, which buildMessagesPayload + buildHeadPayload both consult).
export function isMistakeRecoverySentinel(m: Message): boolean {
return (
m.role === 'system' &&
m.metadata !== null &&
typeof m.metadata === 'object' &&
(m.metadata as { kind?: unknown }).kind === 'mistake_recovery'
);
}
export function isAnySentinel(m: Message): boolean {
return (
m.role === 'system' &&
m.metadata !== null &&
typeof m.metadata === 'object' &&
SENTINEL_KINDS.has((m.metadata as { kind?: unknown }).kind as string)
);
}