@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
28 lines (27 loc) • 1.12 kB
JavaScript
function canonical(value) {
if (Array.isArray(value))
return value.map(canonical);
if (value && typeof value === "object")
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => [k, canonical(v)]));
return value;
}
export class LoopWatchdog {
calls = [];
observe(event) {
if (event.type !== "tool_use")
return;
const part = event.part;
this.calls.push((part?.tool ?? "") + JSON.stringify(canonical(part?.state?.input ?? {})));
if (this.calls.length > 60)
this.calls.shift();
}
reason() {
const distinct = new Set(this.calls).size;
return this.calls.length === 60 && distinct <= 2 ? `tool loop: last 60 calls had ${distinct} distinct inputs` : undefined;
}
}
export function stderrRefusal(tail) {
const matches = [...tail.matchAll(/permission requested:\s*([^\s(]+)\s*\(([^\n]*?)\);\s*auto-rejecting/g)];
const last = matches.at(-1);
return last ? { type: last[1], pattern: last[2], message: `blocked: ${last[1]} ${last[2]}` } : undefined;
}