opencode-immune 1.0.39 → 1.0.41
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 +61 -1
- 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.41";
|
|
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)
|
|
@@ -350,6 +361,34 @@ function isCertificateApiError(error) {
|
|
|
350
361
|
type.includes("tls") ||
|
|
351
362
|
type.includes("ssl"));
|
|
352
363
|
}
|
|
364
|
+
function isProviderRetryBanner(text) {
|
|
365
|
+
return /(?:<none>\s*)?retrying in \d+s\s*-\s*attempt #\d+/i.test(text);
|
|
366
|
+
}
|
|
367
|
+
function scheduleProviderRetryWatchdog(state, sessionID, model) {
|
|
368
|
+
if (!isManagedUltraworkSession(state, sessionID))
|
|
369
|
+
return;
|
|
370
|
+
if (state.providerRetryWatchdogs.has(sessionID))
|
|
371
|
+
return;
|
|
372
|
+
const timer = setTimeout(async () => {
|
|
373
|
+
state.providerRetryWatchdogs.delete(sessionID);
|
|
374
|
+
if (!isManagedUltraworkSession(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
|
+
}
|
|
353
392
|
async function setSessionFallbackModel(state, sessionID, model) {
|
|
354
393
|
const existing = state.managedUltraworkSessions.get(sessionID);
|
|
355
394
|
if (!existing)
|
|
@@ -1172,6 +1211,7 @@ function createFallbackModels(state) {
|
|
|
1172
1211
|
const wasAlreadyManaged = isManagedUltraworkSession(state, input.sessionID);
|
|
1173
1212
|
await addManagedUltraworkSession(state, input.sessionID);
|
|
1174
1213
|
await writeUltraworkMarker(state);
|
|
1214
|
+
scheduleProviderRetryWatchdog(state, input.sessionID, RATE_LIMIT_FALLBACK_MODEL);
|
|
1175
1215
|
// First contact with 0-ultrawork after plugin restart:
|
|
1176
1216
|
// if marker is active and tasks.md has incomplete work, send AUTO-RESUME prompt.
|
|
1177
1217
|
if (!wasAlreadyManaged && !state.autoResumeAttempted) {
|
|
@@ -1211,6 +1251,7 @@ function createFallbackModels(state) {
|
|
|
1211
1251
|
}
|
|
1212
1252
|
else if (getManagedSession(state, input.sessionID)?.kind === "child") {
|
|
1213
1253
|
await updateManagedSessionAgent(state, input.sessionID, input.agent);
|
|
1254
|
+
scheduleProviderRetryWatchdog(state, input.sessionID, CHILD_SESSION_FALLBACK_MODEL);
|
|
1214
1255
|
}
|
|
1215
1256
|
// NOTE: Do NOT remove managed root sessions when agent changes in chat.params.
|
|
1216
1257
|
// Subagent calls from 0-ultrawork (1-van, 7-backlog, etc.) use the same session.
|
|
@@ -1321,6 +1362,7 @@ function createEventHandler(state) {
|
|
|
1321
1362
|
// Reset retry counter on successful activity
|
|
1322
1363
|
if (eventType === "session.updated" && sessionID) {
|
|
1323
1364
|
cancelPendingSessionRetry(state, sessionID, "session updated");
|
|
1365
|
+
cancelProviderRetryWatchdog(state, sessionID, "session updated");
|
|
1324
1366
|
state.sessionErrorRetryCount.delete(sessionID);
|
|
1325
1367
|
if (markUltraworkSessionActive(state, sessionID)) {
|
|
1326
1368
|
// session activity tracked in-memory only
|
|
@@ -1470,6 +1512,24 @@ function createTextCompleteHandler(state) {
|
|
|
1470
1512
|
const text = output.text ?? "";
|
|
1471
1513
|
if (!text)
|
|
1472
1514
|
return;
|
|
1515
|
+
cancelProviderRetryWatchdog(state, sessionID, "assistant text completed");
|
|
1516
|
+
// Some provider/SDK failures render as a retry banner instead of a
|
|
1517
|
+
// session.error event, leaving the UI waiting for long internal backoff.
|
|
1518
|
+
if (isProviderRetryBanner(text) && isManagedUltraworkSession(state, sessionID)) {
|
|
1519
|
+
const managedSession = getManagedSession(state, sessionID);
|
|
1520
|
+
const fallbackModel = managedSession?.kind === "child"
|
|
1521
|
+
? CHILD_SESSION_FALLBACK_MODEL
|
|
1522
|
+
: RATE_LIMIT_FALLBACK_MODEL;
|
|
1523
|
+
await setSessionFallbackModel(state, sessionID, fallbackModel);
|
|
1524
|
+
scheduleManagedSessionRetry(state, sessionID, {
|
|
1525
|
+
delayMs: 1_000,
|
|
1526
|
+
reason: "provider retry banner fallback",
|
|
1527
|
+
countAgainstBudget: false,
|
|
1528
|
+
abortBeforePrompt: true,
|
|
1529
|
+
});
|
|
1530
|
+
console.log(`[opencode-immune] Provider retry banner detected for session ${sessionID}. ` +
|
|
1531
|
+
`Fallback model pinned to ${fallbackModel.providerID}/${fallbackModel.modelID}.`);
|
|
1532
|
+
}
|
|
1473
1533
|
// ── ALL_CYCLES_COMPLETE: clear ultrawork marker ──
|
|
1474
1534
|
if (text.includes(ALL_CYCLES_COMPLETE_MARKER)) {
|
|
1475
1535
|
await clearUltraworkMarker(state);
|