From 5057f500a0a09149fe21524e6cc116de0cce0223 Mon Sep 17 00:00:00 2001 From: Raimund Bauer Date: Tue, 2 Jun 2026 12:17:13 +0200 Subject: [PATCH] =?UTF-8?q?feat/guard:=20watch=5Fsubagents=20Custom-Tool?= =?UTF-8?q?=20=E2=80=94=20proaktiver=2030s-Watcher=20ohne=20externen=20Pro?= =?UTF-8?q?zess?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pi.registerTool('watch_subagents'): wartet 30s (respektiert AbortSignal), gibt dann tmux-Status aller Sessions zurück. AGENTS.md: Orchestrator MUSS das Tool nach jedem Return sofort neu aufrufen — permanente Polling-Schleife. Kein SubConfirm, kein tmux send-keys, kein externer Prozess nötig. --- agent/AGENTS.md | 11 ++++----- extensions/arbeitsweise-guard.ts | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/agent/AGENTS.md b/agent/AGENTS.md index 62cc07d..cdfb721 100644 --- a/agent/AGENTS.md +++ b/agent/AGENTS.md @@ -52,13 +52,10 @@ Zu Beginn einer komplexen Aufgabe (bevor eigene Lösungen gebaut werden): 6. **Arbeitsweise laden:** `~/.pi/agent/memory/arbeitsweise.md` — Orchestrator + SubAgenten-Workflow. Der Orchestrator delegiert alle Arbeiten, SubAgenten speichern Ergebnisse in aufgabenspezifischen Unterverzeichnissen. -7. **SubConfirm starten:** - ```bash - _ME=$(tmux display-message -p '#S') - SubConfirm --orchestrator "$_ME" --skip "$_ME" & - ``` - Startet den Stasis-Detektor. Bei Stasis: schreibt in Alert-Datei UND injiziert direkt in diese Session. - Läuft bereits? `pgrep -fa SubConfirm` prüfen — nicht doppelt starten. +7. **`watch_subagents` sofort aufrufen** — das ist der permanente Subagenten-Watcher. + Das Tool wartet 30 Sekunden, gibt dann den Status aller tmux-Sessions zurück. + **Nach dem Return SOFORT wieder aufrufen** — immer und ohne Ausnahme. + Das ist die Polling-Schleife. Sie stoppt nie während die Session läuft. 8. **SubAgent Auto-Check System laden:** `~/.pi/agent/memory/subagent-autocheck.md` — Hintergrundinformation; SubConfirm übernimmt die proaktive Erkennung automatisch. diff --git a/extensions/arbeitsweise-guard.ts b/extensions/arbeitsweise-guard.ts index daa241a..5e6be0b 100644 --- a/extensions/arbeitsweise-guard.ts +++ b/extensions/arbeitsweise-guard.ts @@ -25,6 +25,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { isToolCallEventType } from "@earendil-works/pi-coding-agent"; +import { Type } from "typebox"; import { execSync } from "node:child_process"; import { readFileSync, writeFileSync, existsSync } from "node:fs"; @@ -205,6 +206,46 @@ function tmuxSubagentenStatus(): string | null { export default function (pi: ExtensionAPI) { if (!ORCHESTRATOR_AKTIV) return; + // ------------------------------------------------------------------------- + // watch_subagents — Proaktiver 30s-Watcher + // + // Wartet 30 Sekunden, dann gibt es den aktuellen Status aller tmux-Sessions + // zurück. Der Orchestrator MUSS dieses Tool sofort nach dem Return neu + // aufrufen — so entsteht eine permanente 30s-Polling-Schleife ohne externen + // Prozess. AbortSignal wird respektiert: bei Nutzer-Input endet der Sleep + // sofort und der Status wird sofort zurückgegeben. + // ------------------------------------------------------------------------- + pi.registerTool({ + name: "watch_subagents", + label: "Subagenten-Watcher (30s)", + description: + "Wartet 30 Sekunden und prüft dann alle Subagenten-Sessions. " + + "PFLICHT: Nach dem Return sofort wieder aufrufen. " + + "Nur im Orchestrator aktiv. Nutzer-Input unterbricht den Sleep sofort.", + parameters: Type.Object({}), + execute: async (_toolCallId, _params, signal, _onUpdate, _ctx) => { + await new Promise((resolve) => { + const timer = setTimeout(resolve, 30_000); + signal?.addEventListener("abort", () => { + clearTimeout(timer); + resolve(); + }); + }); + + const status = tmuxSubagentenStatus(); + const alerts = leseSubWatcherAlerts(); + const parts = ["⏱️ [watch_subagents] 30s-Check:"]; + if (status) parts.push(status); + if (alerts) parts.push(alerts); + if (!status && !alerts) parts.push("Alle Sessions aktiv, kein Handlungsbedarf."); + + return { + content: [{ type: "text" as const, text: parts.join("\n\n") }], + details: {}, + }; + }, + }); + pi.on("tool_call", async (event, ctx) => { let grund: string | null = null;