opencode-immune 1.0.48 → 1.0.49
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 +53 -10
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -12,7 +12,7 @@ const child_process_1 = require("child_process");
|
|
|
12
12
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
13
13
|
// PLUGIN VERSION CHECK
|
|
14
14
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
15
|
-
const PLUGIN_VERSION = "1.0.
|
|
15
|
+
const PLUGIN_VERSION = "1.0.49";
|
|
16
16
|
const PLUGIN_PACKAGE_NAME = "opencode-immune";
|
|
17
17
|
/**
|
|
18
18
|
* Read plugin version from package.json at runtime.
|
|
@@ -390,7 +390,7 @@ function isRetryableApiError(error) {
|
|
|
390
390
|
return true;
|
|
391
391
|
}
|
|
392
392
|
// HTTP status code based detection (retryable server/gateway errors + rate limits)
|
|
393
|
-
const status = maybeError.status ?? maybeError.statusCode ?? maybeError.data?.status;
|
|
393
|
+
const status = maybeError.status ?? maybeError.statusCode ?? maybeError.data?.status ?? maybeError.error?.status;
|
|
394
394
|
if (status && (status === 404 || // transient endpoint not found (API gateway issues)
|
|
395
395
|
status === 429 || // rate limit
|
|
396
396
|
status === 500 || // internal server error
|
|
@@ -402,8 +402,18 @@ function isRetryableApiError(error) {
|
|
|
402
402
|
}
|
|
403
403
|
// Text-based detection for model access errors (not marked as retryable
|
|
404
404
|
// by the API but retryable with a fallback model)
|
|
405
|
-
const message =
|
|
406
|
-
|
|
405
|
+
const message = [
|
|
406
|
+
maybeError.message,
|
|
407
|
+
maybeError.code,
|
|
408
|
+
maybeError.data?.message,
|
|
409
|
+
maybeError.data?.type,
|
|
410
|
+
maybeError.data?.code,
|
|
411
|
+
maybeError.error?.message,
|
|
412
|
+
maybeError.error?.type,
|
|
413
|
+
maybeError.error?.code,
|
|
414
|
+
].filter((value) => value !== undefined && value !== null).join(" ").toLowerCase();
|
|
415
|
+
if (message.includes("api_error") ||
|
|
416
|
+
message.includes("не разрешен") ||
|
|
407
417
|
message.includes("not allowed") ||
|
|
408
418
|
message.includes("internal error") ||
|
|
409
419
|
message.includes("internal server error") ||
|
|
@@ -439,6 +449,7 @@ function isRetryableApiError(error) {
|
|
|
439
449
|
// Certificate/TLS provider failures must pass this primary retry gate
|
|
440
450
|
// before the managed-session fallback model branch can run.
|
|
441
451
|
message.includes("unknown certificate verification error") ||
|
|
452
|
+
message.includes("unknown_certificate_verification_error") ||
|
|
442
453
|
message.includes("certificate has expired") ||
|
|
443
454
|
message.includes("certificate verification") ||
|
|
444
455
|
message.includes("tls") ||
|
|
@@ -468,23 +479,25 @@ function isRateLimitApiError(error) {
|
|
|
468
479
|
if (!error || typeof error !== "object")
|
|
469
480
|
return false;
|
|
470
481
|
const maybeError = error;
|
|
471
|
-
const message = `${maybeError.message ?? ""} ${maybeError.data?.message ?? ""}`.toLowerCase();
|
|
472
|
-
const type = `${maybeError.data?.type ?? ""}`.toLowerCase();
|
|
482
|
+
const message = `${maybeError.message ?? ""} ${maybeError.data?.message ?? ""} ${maybeError.error?.message ?? ""}`.toLowerCase();
|
|
483
|
+
const type = `${maybeError.data?.type ?? ""} ${maybeError.error?.type ?? ""}`.toLowerCase();
|
|
473
484
|
return ((type.includes("rate_limit") || message.includes("too many requests") || message.includes("rate limit")));
|
|
474
485
|
}
|
|
475
486
|
function isCertificateApiError(error) {
|
|
476
487
|
if (!error || typeof error !== "object")
|
|
477
488
|
return false;
|
|
478
489
|
const maybeError = error;
|
|
479
|
-
const message = `${maybeError.message ?? ""} ${maybeError.data?.message ?? ""}`.toLowerCase();
|
|
480
|
-
const code = `${maybeError.code ?? ""} ${maybeError.data?.code ?? ""}`.toLowerCase();
|
|
481
|
-
const type = `${maybeError.data?.type ?? ""}`.toLowerCase();
|
|
490
|
+
const message = `${maybeError.message ?? ""} ${maybeError.data?.message ?? ""} ${maybeError.error?.message ?? ""}`.toLowerCase();
|
|
491
|
+
const code = `${maybeError.code ?? ""} ${maybeError.data?.code ?? ""} ${maybeError.error?.code ?? ""}`.toLowerCase();
|
|
492
|
+
const type = `${maybeError.data?.type ?? ""} ${maybeError.error?.type ?? ""}`.toLowerCase();
|
|
482
493
|
return (message.includes("unknown certificate verification error") ||
|
|
494
|
+
message.includes("unknown_certificate_verification_error") ||
|
|
483
495
|
message.includes("certificate has expired") ||
|
|
484
496
|
message.includes("certificate verification") ||
|
|
485
497
|
message.includes("tls") ||
|
|
486
498
|
message.includes("ssl") ||
|
|
487
499
|
code.includes("cert_has_expired") ||
|
|
500
|
+
code.includes("unknown_certificate_verification_error") ||
|
|
488
501
|
code.includes("unable_to_verify_leaf_signature") ||
|
|
489
502
|
code.includes("self_signed_cert") ||
|
|
490
503
|
code.includes("tls") ||
|
|
@@ -560,6 +573,28 @@ async function setSessionFallbackModel(state, sessionID, model) {
|
|
|
560
573
|
fallbackModel: model,
|
|
561
574
|
});
|
|
562
575
|
}
|
|
576
|
+
async function recoverUntrackedRootSessionForRetry(state, sessionID, error) {
|
|
577
|
+
if (!isRetryableApiError(error))
|
|
578
|
+
return undefined;
|
|
579
|
+
const markerActive = await isUltraworkMarkerActive(state);
|
|
580
|
+
if (!markerActive)
|
|
581
|
+
return undefined;
|
|
582
|
+
const recovery = await parseTasksFile(state.input.directory);
|
|
583
|
+
if (!recovery || recovery.phase === "ARCHIVE: DONE")
|
|
584
|
+
return undefined;
|
|
585
|
+
state.recoveryContext = recovery;
|
|
586
|
+
await addManagedUltraworkSession(state, sessionID);
|
|
587
|
+
const recovered = getManagedSession(state, sessionID);
|
|
588
|
+
await writeDiagnosticLog(state, "session-retry:recovered-untracked-root", {
|
|
589
|
+
sessionID,
|
|
590
|
+
task: recovery.task,
|
|
591
|
+
level: recovery.level,
|
|
592
|
+
phase: recovery.phase,
|
|
593
|
+
errorType: getRetryableErrorType(error),
|
|
594
|
+
});
|
|
595
|
+
writePluginLog(state, "warn", `[opencode-immune] Recovered untracked root session ${sessionID} for retry after plugin restart or state loss.`);
|
|
596
|
+
return recovered;
|
|
597
|
+
}
|
|
563
598
|
function getManagedSessionRetryContext(state, sessionID) {
|
|
564
599
|
const managedSession = state.managedUltraworkSessions.get(sessionID);
|
|
565
600
|
if (!managedSession)
|
|
@@ -1459,10 +1494,18 @@ function createEventHandler(state) {
|
|
|
1459
1494
|
}
|
|
1460
1495
|
// ── Auto-retry on retryable API error for managed ultrawork sessions ──
|
|
1461
1496
|
if (eventType === "session.error" && sessionID) {
|
|
1462
|
-
const managedSession = getManagedSession(state, sessionID)
|
|
1497
|
+
const managedSession = getManagedSession(state, sessionID) ??
|
|
1498
|
+
await recoverUntrackedRootSessionForRetry(state, sessionID, error);
|
|
1463
1499
|
const isRoot = managedSession?.kind === "root";
|
|
1464
1500
|
const isChild = managedSession?.kind === "child";
|
|
1465
1501
|
if (!managedSession) {
|
|
1502
|
+
if (isRetryableApiError(error)) {
|
|
1503
|
+
await writeDiagnosticLog(state, "session-retry:unmanaged-retryable-error", {
|
|
1504
|
+
sessionID,
|
|
1505
|
+
eventType,
|
|
1506
|
+
errorType: getRetryableErrorType(error),
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1466
1509
|
return;
|
|
1467
1510
|
}
|
|
1468
1511
|
if (!isRetryableApiError(error)) {
|