fix/extensions: rule-enforcer — setInterval gegen uncaught exceptions härten

Pis Loader (loader.js:298-311) kapselt nur die synchrone Factory-Ausführung.
Das 30s-setInterval feuert später und läge außerhalb dieses Schutzes — eine
Exception dort wäre uncaught und könnte den Pi-Prozess beenden. Daher: gesamter
Intervall-Rumpf in try/catch.

Empirisch verifiziert (Pi-SDK createAgentSession, agentDir=~/.pi/agent):
- rule-enforcer.ts lädt mit 0 Fehlern (10/10 Extensions geladen)
- absichtlich kaputte Test-Extension crasht Pi NICHT — Fehler isoliert, gesunde
  Extensions laden weiter, Session startet normal
This commit is contained in:
Raimund Bauer 2026-06-02 18:40:44 +02:00
parent 115cc61627
commit 6371fb9f60

View file

@ -212,62 +212,70 @@ export default function (pi: ExtensionAPI) {
}
// ── 30s Background-Check: Invariante erzwingen ───────────────────────────
// WICHTIG: Der gesamte Rumpf ist in try/catch gekapselt. Pis Loader umschließt
// nur die synchrone Factory-Ausführung (loader.js:298-311); dieses Intervall
// feuert später und läge sonst AUSSERHALB von Pis Schutz — eine Exception hier
// wäre uncaught und könnte den Pi-Prozess beenden. Darum: niemals werfen.
const interval = setInterval(() => {
const subagenten = laufendeSubagenten();
if (subagenten.length === 0) return; // kein laufender Subagent ⇒ idle ist erlaubt
const dialoge = subagenten.filter((s) => s.dialog);
const beschreibung = subagenten
.map(
(s) =>
`${s.name}${s.dialog ? " — WARTET AUF BESTÄTIGUNG" : " — läuft"}`,
)
.join("\n");
// Passiver Pfad: greift, falls der Orchestrator gerade in einem Turn ist
schreibeAlert(
`[30s-Check] Laufende Subagenten — Orchestrator muss überwachen:\n${beschreibung}`,
);
// Idle-Erkennung: primär zeitbasiert (ctx-unabhängig)
const idleMs = Date.now() - letzteAktivitaet;
if (idleMs < IDLE_SCHWELLE_MS) return; // Orchestrator aktiv ⇒ nichts tun
// Zusätzliches Veto, falls ctx zuverlässig "beschäftigt" meldet
try {
if (letzterCtx && typeof letzterCtx.isIdle === "function") {
if (letzterCtx.isIdle() === false) return;
}
} catch {}
const subagenten = laufendeSubagenten();
if (subagenten.length === 0) return; // kein laufender Subagent ⇒ idle ist erlaubt
const jetzt = Date.now();
if (jetzt - letzteWeckung < WECK_MIN_ABSTAND_MS) return;
letzteWeckung = jetzt;
const dialoge = subagenten.filter((s) => s.dialog);
const beschreibung = subagenten
.map(
(s) =>
`${s.name}${s.dialog ? " — WARTET AUF BESTÄTIGUNG" : " — läuft"}`,
)
.join("\n");
const dringlich = dialoge.length > 0;
const dialogText = dialoge
.map((d) => `--- ${d.name} ---\n${d.dialog}`)
.join("\n");
try {
letzterCtx?.ui?.notify?.(
dringlich
? "🚨 Subagent wartet auf Bestätigung — Orchestrator wird geweckt."
: "⏱️ Subagent läuft, Orchestrator war idle — wird geweckt.",
"warning",
// Passiver Pfad: greift, falls der Orchestrator gerade in einem Turn ist
schreibeAlert(
`[30s-Check] Laufende Subagenten — Orchestrator muss überwachen:\n${beschreibung}`,
);
} catch {}
try {
pi.sendUserMessage(
`🚨 [Auto-Weckung] Es läuft mindestens ein Subagent, aber du warst idle. ` +
`Das ist nicht erlaubt, solange Subagenten laufen.\n${beschreibung}\n` +
(dringlich
? `\nEin Subagent wartet auf eine Bestätigung:\n${dialogText}\n→ Sofort watch_subagents aufrufen, den Dialog prüfen und nach Regel bestätigen ODER den Benutzer informieren.\n`
: ``) +
`→ Nimm die watch_subagents-Überwachung sofort wieder auf und halte sie, bis ALLE Subagenten fertig sind.`,
);
} catch {}
// Idle-Erkennung: primär zeitbasiert (ctx-unabhängig)
const idleMs = Date.now() - letzteAktivitaet;
if (idleMs < IDLE_SCHWELLE_MS) return; // Orchestrator aktiv ⇒ nichts tun
// Zusätzliches Veto, falls ctx zuverlässig "beschäftigt" meldet
try {
if (letzterCtx && typeof letzterCtx.isIdle === "function") {
if (letzterCtx.isIdle() === false) return;
}
} catch {}
const jetzt = Date.now();
if (jetzt - letzteWeckung < WECK_MIN_ABSTAND_MS) return;
letzteWeckung = jetzt;
const dringlich = dialoge.length > 0;
const dialogText = dialoge
.map((d) => `--- ${d.name} ---\n${d.dialog}`)
.join("\n");
try {
letzterCtx?.ui?.notify?.(
dringlich
? "🚨 Subagent wartet auf Bestätigung — Orchestrator wird geweckt."
: "⏱️ Subagent läuft, Orchestrator war idle — wird geweckt.",
"warning",
);
} catch {}
try {
pi.sendUserMessage(
`🚨 [Auto-Weckung] Es läuft mindestens ein Subagent, aber du warst idle. ` +
`Das ist nicht erlaubt, solange Subagenten laufen.\n${beschreibung}\n` +
(dringlich
? `\nEin Subagent wartet auf eine Bestätigung:\n${dialogText}\n→ Sofort watch_subagents aufrufen, den Dialog prüfen und nach Regel bestätigen ODER den Benutzer informieren.\n`
: ``) +
`→ Nimm die watch_subagents-Überwachung sofort wieder auf und halte sie, bis ALLE Subagenten fertig sind.`,
);
} catch {}
} catch {
// Defensive Notbremse: Das Intervall darf den Pi-Prozess NIE crashen.
}
}, CHECK_INTERVAL_MS);
pi.on("session_shutdown", async () => {