opencode-immune 1.0.47 → 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 +56 -11
  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.
@@ -59,7 +59,9 @@ async function checkPluginUpdate(state) {
59
59
  if (latest && latest !== currentVersion) {
60
60
  state.pluginUpdateMessage =
61
61
  `[PLUGIN UPDATE] opencode-immune ${currentVersion} → ${latest} is available. ` +
62
- `Please inform the user: a plugin update is available. They should restart opencode to get the latest version.`;
62
+ `Please inform the user: run \`opencode plugin opencode-immune@latest --force\`, ` +
63
+ `then restart opencode. If opencode still loads the old package, clear its package cache with ` +
64
+ `\`rm -rf ~/.cache/opencode/packages/opencode-immune@latest\` and run the update command again.`;
63
65
  writePluginLog(state, "warn", `[opencode-immune] Plugin update available: ${currentVersion} → ${latest}.`);
64
66
  }
65
67
  else if (latest) {
@@ -388,7 +390,7 @@ function isRetryableApiError(error) {
388
390
  return true;
389
391
  }
390
392
  // HTTP status code based detection (retryable server/gateway errors + rate limits)
391
- const status = maybeError.status ?? maybeError.statusCode ?? maybeError.data?.status;
393
+ const status = maybeError.status ?? maybeError.statusCode ?? maybeError.data?.status ?? maybeError.error?.status;
392
394
  if (status && (status === 404 || // transient endpoint not found (API gateway issues)
393
395
  status === 429 || // rate limit
394
396
  status === 500 || // internal server error
@@ -400,8 +402,18 @@ function isRetryableApiError(error) {
400
402
  }
401
403
  // Text-based detection for model access errors (not marked as retryable
402
404
  // by the API but retryable with a fallback model)
403
- const message = `${maybeError.message ?? ""} ${maybeError.data?.message ?? ""}`.toLowerCase();
404
- 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("не разрешен") ||
405
417
  message.includes("not allowed") ||
406
418
  message.includes("internal error") ||
407
419
  message.includes("internal server error") ||
@@ -437,6 +449,7 @@ function isRetryableApiError(error) {
437
449
  // Certificate/TLS provider failures must pass this primary retry gate
438
450
  // before the managed-session fallback model branch can run.
439
451
  message.includes("unknown certificate verification error") ||
452
+ message.includes("unknown_certificate_verification_error") ||
440
453
  message.includes("certificate has expired") ||
441
454
  message.includes("certificate verification") ||
442
455
  message.includes("tls") ||
@@ -466,23 +479,25 @@ function isRateLimitApiError(error) {
466
479
  if (!error || typeof error !== "object")
467
480
  return false;
468
481
  const maybeError = error;
469
- const message = `${maybeError.message ?? ""} ${maybeError.data?.message ?? ""}`.toLowerCase();
470
- 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();
471
484
  return ((type.includes("rate_limit") || message.includes("too many requests") || message.includes("rate limit")));
472
485
  }
473
486
  function isCertificateApiError(error) {
474
487
  if (!error || typeof error !== "object")
475
488
  return false;
476
489
  const maybeError = error;
477
- const message = `${maybeError.message ?? ""} ${maybeError.data?.message ?? ""}`.toLowerCase();
478
- const code = `${maybeError.code ?? ""} ${maybeError.data?.code ?? ""}`.toLowerCase();
479
- 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();
480
493
  return (message.includes("unknown certificate verification error") ||
494
+ message.includes("unknown_certificate_verification_error") ||
481
495
  message.includes("certificate has expired") ||
482
496
  message.includes("certificate verification") ||
483
497
  message.includes("tls") ||
484
498
  message.includes("ssl") ||
485
499
  code.includes("cert_has_expired") ||
500
+ code.includes("unknown_certificate_verification_error") ||
486
501
  code.includes("unable_to_verify_leaf_signature") ||
487
502
  code.includes("self_signed_cert") ||
488
503
  code.includes("tls") ||
@@ -558,6 +573,28 @@ async function setSessionFallbackModel(state, sessionID, model) {
558
573
  fallbackModel: model,
559
574
  });
560
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
+ }
561
598
  function getManagedSessionRetryContext(state, sessionID) {
562
599
  const managedSession = state.managedUltraworkSessions.get(sessionID);
563
600
  if (!managedSession)
@@ -1457,10 +1494,18 @@ function createEventHandler(state) {
1457
1494
  }
1458
1495
  // ── Auto-retry on retryable API error for managed ultrawork sessions ──
1459
1496
  if (eventType === "session.error" && sessionID) {
1460
- const managedSession = getManagedSession(state, sessionID);
1497
+ const managedSession = getManagedSession(state, sessionID) ??
1498
+ await recoverUntrackedRootSessionForRetry(state, sessionID, error);
1461
1499
  const isRoot = managedSession?.kind === "root";
1462
1500
  const isChild = managedSession?.kind === "child";
1463
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
+ }
1464
1509
  return;
1465
1510
  }
1466
1511
  if (!isRetryableApiError(error)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-immune",
3
- "version": "1.0.47",
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"