opencode-immune 1.0.41 → 1.0.43
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 +59 -14
- 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.43";
|
|
15
15
|
const PLUGIN_PACKAGE_NAME = "opencode-immune";
|
|
16
16
|
/**
|
|
17
17
|
* Read plugin version from package.json at runtime.
|
|
@@ -76,6 +76,7 @@ function createState(input) {
|
|
|
76
76
|
managedUltraworkSessions: new Map(),
|
|
77
77
|
sessionRetryTimers: new Map(),
|
|
78
78
|
providerRetryWatchdogs: new Map(),
|
|
79
|
+
childFallbackRequests: new Map(),
|
|
79
80
|
sessionErrorRetryCount: new Map(),
|
|
80
81
|
ultraworkMarkerPath: (0, path_1.join)(input.directory, ".opencode", "state", "ultrawork-active.json"),
|
|
81
82
|
diagnosticsLogPath: (0, path_1.join)(input.directory, ".opencode", "state", "opencode-immune-debug.log"),
|
|
@@ -93,6 +94,7 @@ function createState(input) {
|
|
|
93
94
|
const ULTRAWORK_AGENT = "0-ultrawork";
|
|
94
95
|
const MANAGED_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
95
96
|
const PROVIDER_RETRY_WATCHDOG_MS = 30_000;
|
|
97
|
+
const CHILD_FALLBACK_REQUEST_TTL_MS = 10 * 60 * 1000;
|
|
96
98
|
const RATE_LIMIT_FALLBACK_MODEL = {
|
|
97
99
|
providerID: "externcash",
|
|
98
100
|
modelID: "gpt-5.5",
|
|
@@ -364,14 +366,44 @@ function isCertificateApiError(error) {
|
|
|
364
366
|
function isProviderRetryBanner(text) {
|
|
365
367
|
return /(?:<none>\s*)?retrying in \d+s\s*-\s*attempt #\d+/i.test(text);
|
|
366
368
|
}
|
|
369
|
+
function getRetryableErrorType(error) {
|
|
370
|
+
if (isModelAccessError(error))
|
|
371
|
+
return "model access error";
|
|
372
|
+
if (isRateLimitApiError(error))
|
|
373
|
+
return "rate limit";
|
|
374
|
+
if (isCertificateApiError(error))
|
|
375
|
+
return "certificate error";
|
|
376
|
+
return "retryable provider error";
|
|
377
|
+
}
|
|
378
|
+
function recordChildFallbackRequest(state, managedSession, childSessionID, error) {
|
|
379
|
+
const request = {
|
|
380
|
+
childSessionID,
|
|
381
|
+
rootSessionID: managedSession.rootSessionID,
|
|
382
|
+
agent: managedSession.agent || "unknown",
|
|
383
|
+
errorType: getRetryableErrorType(error),
|
|
384
|
+
fallbackModel: CHILD_SESSION_FALLBACK_MODEL,
|
|
385
|
+
createdAt: Date.now(),
|
|
386
|
+
};
|
|
387
|
+
state.childFallbackRequests.set(managedSession.rootSessionID, request);
|
|
388
|
+
}
|
|
389
|
+
function getChildFallbackRequest(state, rootSessionID, now = Date.now()) {
|
|
390
|
+
const request = state.childFallbackRequests.get(rootSessionID);
|
|
391
|
+
if (!request)
|
|
392
|
+
return undefined;
|
|
393
|
+
if (now - request.createdAt > CHILD_FALLBACK_REQUEST_TTL_MS) {
|
|
394
|
+
state.childFallbackRequests.delete(rootSessionID);
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
397
|
+
return request;
|
|
398
|
+
}
|
|
367
399
|
function scheduleProviderRetryWatchdog(state, sessionID, model) {
|
|
368
|
-
if (!
|
|
400
|
+
if (!isManagedRootUltraworkSession(state, sessionID))
|
|
369
401
|
return;
|
|
370
402
|
if (state.providerRetryWatchdogs.has(sessionID))
|
|
371
403
|
return;
|
|
372
404
|
const timer = setTimeout(async () => {
|
|
373
405
|
state.providerRetryWatchdogs.delete(sessionID);
|
|
374
|
-
if (!
|
|
406
|
+
if (!isManagedRootUltraworkSession(state, sessionID))
|
|
375
407
|
return;
|
|
376
408
|
if (state.sessionRetryTimers.has(sessionID))
|
|
377
409
|
return;
|
|
@@ -1041,6 +1073,21 @@ function createSystemTransform(state) {
|
|
|
1041
1073
|
`Read memory-bank/tasks.md and memory-bank/activeContext.md to resume work.`);
|
|
1042
1074
|
}
|
|
1043
1075
|
}
|
|
1076
|
+
const rootSessionID = input.sessionID;
|
|
1077
|
+
if (rootSessionID && isManagedRootUltraworkSession(state, rootSessionID)) {
|
|
1078
|
+
const childFallbackRequest = getChildFallbackRequest(state, rootSessionID);
|
|
1079
|
+
if (childFallbackRequest) {
|
|
1080
|
+
output.system.push(`[Router-Owned Child Retry Required]\n` +
|
|
1081
|
+
`A child/subagent session failed with a retryable provider/model error. ` +
|
|
1082
|
+
`Do not advance to the next sequential pipeline slot until this failed slot is explicitly resolved.\n` +
|
|
1083
|
+
`- Failed child session: ${childFallbackRequest.childSessionID}\n` +
|
|
1084
|
+
`- Agent: ${childFallbackRequest.agent}\n` +
|
|
1085
|
+
`- Error type: ${childFallbackRequest.errorType}\n` +
|
|
1086
|
+
`- Required fallback model: ${childFallbackRequest.fallbackModel.providerID}/${childFallbackRequest.fallbackModel.modelID}\n` +
|
|
1087
|
+
`Router action: retry the SAME agent/slot once using the fallback model if available; if it still fails or cannot be retried, record DECLINE/failed-provider-retry for that slot before continuing. ` +
|
|
1088
|
+
`Never resume the failed child session directly; create a router-owned replacement attempt instead.`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1044
1091
|
// Ralph Loop injection
|
|
1045
1092
|
if (state.lastEditAttempt) {
|
|
1046
1093
|
const edit = state.lastEditAttempt;
|
|
@@ -1251,7 +1298,6 @@ function createFallbackModels(state) {
|
|
|
1251
1298
|
}
|
|
1252
1299
|
else if (getManagedSession(state, input.sessionID)?.kind === "child") {
|
|
1253
1300
|
await updateManagedSessionAgent(state, input.sessionID, input.agent);
|
|
1254
|
-
scheduleProviderRetryWatchdog(state, input.sessionID, CHILD_SESSION_FALLBACK_MODEL);
|
|
1255
1301
|
}
|
|
1256
1302
|
// NOTE: Do NOT remove managed root sessions when agent changes in chat.params.
|
|
1257
1303
|
// Subagent calls from 0-ultrawork (1-van, 7-backlog, etc.) use the same session.
|
|
@@ -1332,12 +1378,14 @@ function createEventHandler(state) {
|
|
|
1332
1378
|
if (count < MAX_RETRIES) {
|
|
1333
1379
|
const delay = Math.min(BASE_DELAY_MS * Math.pow(2, count), MAX_DELAY_MS);
|
|
1334
1380
|
state.sessionErrorRetryCount.set(sessionID, count + 1);
|
|
1335
|
-
//
|
|
1381
|
+
// Child/subagent failures are owned by the router. Auto-resuming a failed
|
|
1382
|
+
// child after the router advances can create two writers in one pipeline.
|
|
1336
1383
|
if (isChild) {
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1384
|
+
recordChildFallbackRequest(state, managedSession, sessionID, error);
|
|
1385
|
+
console.log(`[opencode-immune] Child session ${sessionID}: retryable error detected. ` +
|
|
1386
|
+
`Recorded router-owned fallback request and skipped plugin auto-retry.`);
|
|
1387
|
+
state.sessionErrorRetryCount.set(sessionID, count);
|
|
1388
|
+
return;
|
|
1341
1389
|
}
|
|
1342
1390
|
else if (isRoot && (isRateLimitApiError(error) || isCertificateApiError(error))) {
|
|
1343
1391
|
await setSessionFallbackModel(state, sessionID, RATE_LIMIT_FALLBACK_MODEL);
|
|
@@ -1515,11 +1563,8 @@ function createTextCompleteHandler(state) {
|
|
|
1515
1563
|
cancelProviderRetryWatchdog(state, sessionID, "assistant text completed");
|
|
1516
1564
|
// Some provider/SDK failures render as a retry banner instead of a
|
|
1517
1565
|
// session.error event, leaving the UI waiting for long internal backoff.
|
|
1518
|
-
if (isProviderRetryBanner(text) &&
|
|
1519
|
-
const
|
|
1520
|
-
const fallbackModel = managedSession?.kind === "child"
|
|
1521
|
-
? CHILD_SESSION_FALLBACK_MODEL
|
|
1522
|
-
: RATE_LIMIT_FALLBACK_MODEL;
|
|
1566
|
+
if (isProviderRetryBanner(text) && isManagedRootUltraworkSession(state, sessionID)) {
|
|
1567
|
+
const fallbackModel = RATE_LIMIT_FALLBACK_MODEL;
|
|
1523
1568
|
await setSessionFallbackModel(state, sessionID, fallbackModel);
|
|
1524
1569
|
scheduleManagedSessionRetry(state, sessionID, {
|
|
1525
1570
|
delayMs: 1_000,
|