fix: sendAndWait-Retry + Judge-Server-Bereitschaftscheck vor Loop

- sendAndWait(): fängt "Agent is already processing" mit exponentiellem
  Backoff ab (5 Versuche: 500ms, 1s, 2s, 4s). Race Condition zwischen
  waitForIdle() und sendUserMessage() wird damit toleriert.
- /optimize: prüft Port 8002 vor dem Loop (20× alle 3s = max. 60s).
  Bei 503 "Loading model" wird gewartet statt sofort zu scheitern.
  Ist der Server nach 60s nicht erreichbar: Abbruch mit Hinweis.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-05-20 21:44:37 +02:00
commit 14c47ecd01

View file

@ -389,14 +389,25 @@ async function switchModel(
} }
// Sendet eine Nachricht und wartet bis der Agent fertig ist. // Sendet eine Nachricht und wartet bis der Agent fertig ist.
// Erst idle abwarten, dann als followUp einstellen — verhindert "Agent is already processing". // Retry-Schleife fängt "Agent is already processing" ab — tritt auf wenn
// waitForIdle() zu früh zurückkehrt (Race Condition im pi-Agent).
async function sendAndWait( async function sendAndWait(
pi: ExtensionAPI, pi: ExtensionAPI,
ctx: ExtensionCommandContext, ctx: ExtensionCommandContext,
content: string content: string
): Promise<void> { ): Promise<void> {
await ctx.waitForIdle(); await ctx.waitForIdle();
pi.sendUserMessage(content, { deliverAs: "followUp" }); for (let attempt = 1; attempt <= 5; attempt++) {
try {
pi.sendUserMessage(content, { deliverAs: "followUp" });
break;
} catch (e: any) {
if (attempt === 5) throw e;
// Exponentieller Backoff: 500ms, 1s, 2s, 4s
await new Promise(r => setTimeout(r, 500 * Math.pow(2, attempt - 1)));
await ctx.waitForIdle();
}
}
await new Promise(r => setTimeout(r, 400)); await new Promise(r => setTimeout(r, 400));
await ctx.waitForIdle(); await ctx.waitForIdle();
} }
@ -738,6 +749,22 @@ export default function (pi: ExtensionAPI) {
if (cancelRequested) { cancelRequested = false; finalNotify(ctx, "⛔ Abgebrochen", "Nach Implementierung"); return; } if (cancelRequested) { cancelRequested = false; finalNotify(ctx, "⛔ Abgebrochen", "Nach Implementierung"); return; }
} }
// Judge-Server-Bereitschaft prüfen — bei 503 (Modell lädt noch) bis zu 60s warten.
ctx.ui.setStatus("optimize", "Judge-Server wird geprüft…");
let serverReady = false;
for (let i = 0; i < 20; i++) {
const hc = await pi.exec("bash", ["-c",
"curl -sf --max-time 3 http://localhost:8002/health || " +
"curl -sf --max-time 3 http://localhost:8002/v1/models"
], { cwd: ctx.cwd });
if (hc.code === 0) { serverReady = true; break; }
await new Promise(r => setTimeout(r, 3000));
}
if (!serverReady) {
finalNotify(ctx, "⛔ Judge nicht erreichbar", "Port 8002 antwortet nicht — start-judge.sh ausführen");
return;
}
// Test-Suiten einmalig ermitteln: --test-cmd überschreibt Auto-Erkennung. // Test-Suiten einmalig ermitteln: --test-cmd überschreibt Auto-Erkennung.
// Läuft nach Coder, damit neu angelegte Test-Dateien bereits erkannt werden. // Läuft nach Coder, damit neu angelegte Test-Dateien bereits erkannt werden.
ctx.ui.setStatus("optimize", "Test-Suiten werden erkannt…"); ctx.ui.setStatus("optimize", "Test-Suiten werden erkannt…");