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.
Files changed (2) hide show
  1. package/dist/plugin.js +53 -10
  2. 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.44";
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 = `${maybeError.message ?? ""} ${maybeError.data?.message ?? ""}`.toLowerCase();
406
- if (message.includes("не разрешен") ||
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)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-immune",
3
- "version": "1.0.48",
3
+ "version": "1.0.49",
4
4
  "description": "OpenCode plugin: session recovery, auto-retry, multi-cycle automation, context monitoring",
5
5
  "exports": {
6
6
  "./server": "./dist/plugin.js"