opencode-immune 1.0.40 → 1.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin.js +48 -11
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -11,7 +11,7 @@ const child_process_1 = require("child_process");
|
|
|
11
11
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
12
12
|
// PLUGIN VERSION CHECK
|
|
13
13
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
14
|
-
const PLUGIN_VERSION = "1.0.
|
|
14
|
+
const PLUGIN_VERSION = "1.0.42";
|
|
15
15
|
const PLUGIN_PACKAGE_NAME = "opencode-immune";
|
|
16
16
|
/**
|
|
17
17
|
* Read plugin version from package.json at runtime.
|
|
@@ -75,6 +75,7 @@ function createState(input) {
|
|
|
75
75
|
recoveryContext: null,
|
|
76
76
|
managedUltraworkSessions: new Map(),
|
|
77
77
|
sessionRetryTimers: new Map(),
|
|
78
|
+
providerRetryWatchdogs: new Map(),
|
|
78
79
|
sessionErrorRetryCount: new Map(),
|
|
79
80
|
ultraworkMarkerPath: (0, path_1.join)(input.directory, ".opencode", "state", "ultrawork-active.json"),
|
|
80
81
|
diagnosticsLogPath: (0, path_1.join)(input.directory, ".opencode", "state", "opencode-immune-debug.log"),
|
|
@@ -91,6 +92,7 @@ function createState(input) {
|
|
|
91
92
|
}
|
|
92
93
|
const ULTRAWORK_AGENT = "0-ultrawork";
|
|
93
94
|
const MANAGED_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
95
|
+
const PROVIDER_RETRY_WATCHDOG_MS = 30_000;
|
|
94
96
|
const RATE_LIMIT_FALLBACK_MODEL = {
|
|
95
97
|
providerID: "externcash",
|
|
96
98
|
modelID: "gpt-5.5",
|
|
@@ -206,8 +208,17 @@ function cancelPendingSessionRetry(state, sessionID, reason) {
|
|
|
206
208
|
state.sessionRetryTimers.delete(sessionID);
|
|
207
209
|
console.log(`[opencode-immune] Cancelled pending retry for session ${sessionID}: ${reason}`);
|
|
208
210
|
}
|
|
211
|
+
function cancelProviderRetryWatchdog(state, sessionID, reason) {
|
|
212
|
+
const timer = state.providerRetryWatchdogs.get(sessionID);
|
|
213
|
+
if (!timer)
|
|
214
|
+
return;
|
|
215
|
+
clearTimeout(timer);
|
|
216
|
+
state.providerRetryWatchdogs.delete(sessionID);
|
|
217
|
+
console.log(`[opencode-immune] Cancelled provider retry watchdog for session ${sessionID}: ${reason}`);
|
|
218
|
+
}
|
|
209
219
|
async function removeManagedUltraworkSession(state, sessionID, reason) {
|
|
210
220
|
cancelPendingSessionRetry(state, sessionID, reason);
|
|
221
|
+
cancelProviderRetryWatchdog(state, sessionID, reason);
|
|
211
222
|
state.sessionErrorRetryCount.delete(sessionID);
|
|
212
223
|
const existed = state.managedUltraworkSessions.delete(sessionID);
|
|
213
224
|
if (!existed)
|
|
@@ -353,6 +364,31 @@ function isCertificateApiError(error) {
|
|
|
353
364
|
function isProviderRetryBanner(text) {
|
|
354
365
|
return /(?:<none>\s*)?retrying in \d+s\s*-\s*attempt #\d+/i.test(text);
|
|
355
366
|
}
|
|
367
|
+
function scheduleProviderRetryWatchdog(state, sessionID, model) {
|
|
368
|
+
if (!isManagedRootUltraworkSession(state, sessionID))
|
|
369
|
+
return;
|
|
370
|
+
if (state.providerRetryWatchdogs.has(sessionID))
|
|
371
|
+
return;
|
|
372
|
+
const timer = setTimeout(async () => {
|
|
373
|
+
state.providerRetryWatchdogs.delete(sessionID);
|
|
374
|
+
if (!isManagedRootUltraworkSession(state, sessionID))
|
|
375
|
+
return;
|
|
376
|
+
if (state.sessionRetryTimers.has(sessionID))
|
|
377
|
+
return;
|
|
378
|
+
await setSessionFallbackModel(state, sessionID, model);
|
|
379
|
+
await writeDiagnosticLog(state, "provider-retry-watchdog:fired", {
|
|
380
|
+
sessionID,
|
|
381
|
+
fallbackModel: model,
|
|
382
|
+
});
|
|
383
|
+
scheduleManagedSessionRetry(state, sessionID, {
|
|
384
|
+
delayMs: 1_000,
|
|
385
|
+
reason: "provider retry watchdog",
|
|
386
|
+
countAgainstBudget: false,
|
|
387
|
+
abortBeforePrompt: true,
|
|
388
|
+
});
|
|
389
|
+
}, PROVIDER_RETRY_WATCHDOG_MS);
|
|
390
|
+
state.providerRetryWatchdogs.set(sessionID, timer);
|
|
391
|
+
}
|
|
356
392
|
async function setSessionFallbackModel(state, sessionID, model) {
|
|
357
393
|
const existing = state.managedUltraworkSessions.get(sessionID);
|
|
358
394
|
if (!existing)
|
|
@@ -1175,6 +1211,7 @@ function createFallbackModels(state) {
|
|
|
1175
1211
|
const wasAlreadyManaged = isManagedUltraworkSession(state, input.sessionID);
|
|
1176
1212
|
await addManagedUltraworkSession(state, input.sessionID);
|
|
1177
1213
|
await writeUltraworkMarker(state);
|
|
1214
|
+
scheduleProviderRetryWatchdog(state, input.sessionID, RATE_LIMIT_FALLBACK_MODEL);
|
|
1178
1215
|
// First contact with 0-ultrawork after plugin restart:
|
|
1179
1216
|
// if marker is active and tasks.md has incomplete work, send AUTO-RESUME prompt.
|
|
1180
1217
|
if (!wasAlreadyManaged && !state.autoResumeAttempted) {
|
|
@@ -1294,12 +1331,13 @@ function createEventHandler(state) {
|
|
|
1294
1331
|
if (count < MAX_RETRIES) {
|
|
1295
1332
|
const delay = Math.min(BASE_DELAY_MS * Math.pow(2, count), MAX_DELAY_MS);
|
|
1296
1333
|
state.sessionErrorRetryCount.set(sessionID, count + 1);
|
|
1297
|
-
//
|
|
1334
|
+
// Child/subagent failures are owned by the router. Auto-resuming a failed
|
|
1335
|
+
// child after the router advances can create two writers in one pipeline.
|
|
1298
1336
|
if (isChild) {
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1337
|
+
console.log(`[opencode-immune] Child session ${sessionID}: retryable error detected. ` +
|
|
1338
|
+
`Skipping plugin auto-retry; router owns sequential fallback decisions.`);
|
|
1339
|
+
state.sessionErrorRetryCount.set(sessionID, count);
|
|
1340
|
+
return;
|
|
1303
1341
|
}
|
|
1304
1342
|
else if (isRoot && (isRateLimitApiError(error) || isCertificateApiError(error))) {
|
|
1305
1343
|
await setSessionFallbackModel(state, sessionID, RATE_LIMIT_FALLBACK_MODEL);
|
|
@@ -1324,6 +1362,7 @@ function createEventHandler(state) {
|
|
|
1324
1362
|
// Reset retry counter on successful activity
|
|
1325
1363
|
if (eventType === "session.updated" && sessionID) {
|
|
1326
1364
|
cancelPendingSessionRetry(state, sessionID, "session updated");
|
|
1365
|
+
cancelProviderRetryWatchdog(state, sessionID, "session updated");
|
|
1327
1366
|
state.sessionErrorRetryCount.delete(sessionID);
|
|
1328
1367
|
if (markUltraworkSessionActive(state, sessionID)) {
|
|
1329
1368
|
// session activity tracked in-memory only
|
|
@@ -1473,13 +1512,11 @@ function createTextCompleteHandler(state) {
|
|
|
1473
1512
|
const text = output.text ?? "";
|
|
1474
1513
|
if (!text)
|
|
1475
1514
|
return;
|
|
1515
|
+
cancelProviderRetryWatchdog(state, sessionID, "assistant text completed");
|
|
1476
1516
|
// Some provider/SDK failures render as a retry banner instead of a
|
|
1477
1517
|
// session.error event, leaving the UI waiting for long internal backoff.
|
|
1478
|
-
if (isProviderRetryBanner(text) &&
|
|
1479
|
-
const
|
|
1480
|
-
const fallbackModel = managedSession?.kind === "child"
|
|
1481
|
-
? CHILD_SESSION_FALLBACK_MODEL
|
|
1482
|
-
: RATE_LIMIT_FALLBACK_MODEL;
|
|
1518
|
+
if (isProviderRetryBanner(text) && isManagedRootUltraworkSession(state, sessionID)) {
|
|
1519
|
+
const fallbackModel = RATE_LIMIT_FALLBACK_MODEL;
|
|
1483
1520
|
await setSessionFallbackModel(state, sessionID, fallbackModel);
|
|
1484
1521
|
scheduleManagedSessionRetry(state, sessionID, {
|
|
1485
1522
|
delayMs: 1_000,
|