opencode-immune 1.0.70 → 1.0.72

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.
@@ -3777,7 +3777,7 @@ import { fileURLToPath } from "url";
3777
3777
  import { createHash } from "crypto";
3778
3778
  import { tmpdir } from "os";
3779
3779
  import { execFile } from "child_process";
3780
- var PLUGIN_VERSION = "1.0.70";
3780
+ var PLUGIN_VERSION = "1.0.72";
3781
3781
  var PLUGIN_PACKAGE_NAME = "opencode-immune";
3782
3782
  var PLUGIN_DIRNAME = dirname(fileURLToPath(import.meta.url));
3783
3783
  function getServerAuthHeaders() {
@@ -3848,6 +3848,7 @@ function createState(input) {
3848
3848
  recoveryContext: null,
3849
3849
  managedUltraworkSessions: /* @__PURE__ */ new Map(),
3850
3850
  sessionRetryTimers: /* @__PURE__ */ new Map(),
3851
+ abortedMessageRetries: /* @__PURE__ */ new Set(),
3851
3852
  providerRetryWatchdogs: /* @__PURE__ */ new Map(),
3852
3853
  childFallbackRequests: /* @__PURE__ */ new Map(),
3853
3854
  sessionErrorRetryCount: /* @__PURE__ */ new Map(),
@@ -3918,11 +3919,15 @@ var CHILD_SESSION_FALLBACK_MODEL = {
3918
3919
  providerID: "claudehub",
3919
3920
  modelID: "claude-opus-4-7"
3920
3921
  };
3922
+ var HIGH_CAPABILITY_FALLBACK_MODELS = [
3923
+ { providerID: "codexsale", modelID: "gpt-5.5" },
3924
+ { providerID: "codexsale", modelID: "gpt-5.4" }
3925
+ ];
3921
3926
  var AUTO_CYCLE_LOCK_TTL_MS = 30 * 60 * 1e3;
3922
3927
  var FALLBACK_MODEL_CANDIDATES = [
3923
3928
  CHILD_SESSION_FALLBACK_MODEL,
3924
- RATE_LIMIT_FALLBACK_MODEL,
3925
- { providerID: "codexsale", modelID: "gpt-5.5" }
3929
+ ...HIGH_CAPABILITY_FALLBACK_MODELS,
3930
+ RATE_LIMIT_FALLBACK_MODEL
3926
3931
  ];
3927
3932
  var FALLBACK_AGENT_SUFFIX = "-provider-fallback";
3928
3933
  function isManagedUltraworkSession(state, sessionID) {
@@ -4346,6 +4351,46 @@ function getRetryableErrorType(error) {
4346
4351
  if (action === "retry") return `transient provider error${statusSuffix}`;
4347
4352
  return "non-retryable provider error";
4348
4353
  }
4354
+ function compactUnknown(value, maxLength = 8e3) {
4355
+ try {
4356
+ const serialized = typeof value === "string" ? value : JSON.stringify(value);
4357
+ return (serialized ?? "").slice(0, maxLength).toLowerCase();
4358
+ } catch {
4359
+ return String(value).slice(0, maxLength).toLowerCase();
4360
+ }
4361
+ }
4362
+ function isAbortedMessageEvent(eventType, properties) {
4363
+ if (eventType !== "message.updated" && eventType !== "message.part.updated") return false;
4364
+ const text = compactUnknown(properties ?? {});
4365
+ return text.includes("messageabortederror") || text.includes("tool execution aborted") || text.includes('"interrupted":true') || text.includes('"status":"error"') && text.includes('"aborted"');
4366
+ }
4367
+ function getMessageIDFromEvent(properties) {
4368
+ const info = isRecord(properties?.info) ? properties.info : void 0;
4369
+ const part = isRecord(properties?.part) ? properties.part : void 0;
4370
+ return stringifyErrorField(properties?.messageID) ?? stringifyErrorField(info?.id) ?? stringifyErrorField(part?.messageID) ?? stringifyErrorField(properties?.id);
4371
+ }
4372
+ async function recoverUntrackedRootSessionForActiveTask(state, sessionID, reason) {
4373
+ const markerActive = await isUltraworkMarkerActive(state);
4374
+ if (!markerActive) return void 0;
4375
+ const recovery = await parseTasksFile(state.input.directory);
4376
+ if (!recovery || recovery.phase === "ARCHIVE: DONE") return void 0;
4377
+ state.recoveryContext = recovery;
4378
+ await addManagedUltraworkSession(state, sessionID);
4379
+ const recovered = getManagedSession(state, sessionID);
4380
+ await writeDiagnosticLog(state, "session-retry:recovered-untracked-root", {
4381
+ sessionID,
4382
+ task: recovery.task,
4383
+ level: recovery.level,
4384
+ phase: recovery.phase,
4385
+ reason
4386
+ });
4387
+ writePluginLog(
4388
+ state,
4389
+ "warn",
4390
+ `[opencode-immune] Recovered untracked root session ${sessionID} for ${reason}.`
4391
+ );
4392
+ return recovered;
4393
+ }
4349
4394
  function recordChildFallbackRequest(state, managedSession, childSessionID, error) {
4350
4395
  const fallbackModel = selectFallbackModel(
4351
4396
  getFailedModelFromError(error) ?? managedSession.currentModel
@@ -4405,26 +4450,11 @@ async function setSessionFallbackModel(state, sessionID, model) {
4405
4450
  }
4406
4451
  async function recoverUntrackedRootSessionForRetry(state, sessionID, error) {
4407
4452
  if (!isRetryableApiError(error)) return void 0;
4408
- const markerActive = await isUltraworkMarkerActive(state);
4409
- if (!markerActive) return void 0;
4410
- const recovery = await parseTasksFile(state.input.directory);
4411
- if (!recovery || recovery.phase === "ARCHIVE: DONE") return void 0;
4412
- state.recoveryContext = recovery;
4413
- await addManagedUltraworkSession(state, sessionID);
4414
- const recovered = getManagedSession(state, sessionID);
4415
- await writeDiagnosticLog(state, "session-retry:recovered-untracked-root", {
4416
- sessionID,
4417
- task: recovery.task,
4418
- level: recovery.level,
4419
- phase: recovery.phase,
4420
- errorType: getRetryableErrorType(error)
4421
- });
4422
- writePluginLog(
4453
+ return recoverUntrackedRootSessionForActiveTask(
4423
4454
  state,
4424
- "warn",
4425
- `[opencode-immune] Recovered untracked root session ${sessionID} for retry after plugin restart or state loss.`
4455
+ sessionID,
4456
+ `retry after ${getRetryableErrorType(error)}`
4426
4457
  );
4427
- return recovered;
4428
4458
  }
4429
4459
  function getManagedSessionRetryContext(state, sessionID) {
4430
4460
  const managedSession = state.managedUltraworkSessions.get(sessionID);
@@ -5412,9 +5442,50 @@ function createEventHandler(state) {
5412
5442
  await sessionRecovery(input);
5413
5443
  const event = input.event;
5414
5444
  const eventType = event.type ?? "unknown";
5415
- const info = event.properties?.info;
5416
- const sessionID = event.properties?.sessionID ?? info?.id;
5417
- const error = event.properties?.error;
5445
+ const properties = event.properties;
5446
+ const info = properties?.info;
5447
+ const sessionID = properties?.sessionID ?? info?.sessionID ?? info?.id;
5448
+ const error = properties?.error ?? info?.error;
5449
+ if (sessionID && isAbortedMessageEvent(eventType, properties)) {
5450
+ const messageID = getMessageIDFromEvent(properties) ?? `${sessionID}:unknown`;
5451
+ const retryKey = `${sessionID}:${messageID}`;
5452
+ if (state.abortedMessageRetries.has(retryKey)) {
5453
+ return;
5454
+ }
5455
+ const managedSession = getManagedSession(state, sessionID) ?? await recoverUntrackedRootSessionForActiveTask(
5456
+ state,
5457
+ sessionID,
5458
+ "aborted message recovery"
5459
+ );
5460
+ if (managedSession?.kind === "root") {
5461
+ state.abortedMessageRetries.add(retryKey);
5462
+ await writeDiagnosticLog(state, "session-retry:aborted-message-observed", {
5463
+ sessionID,
5464
+ messageID,
5465
+ eventType
5466
+ });
5467
+ const count = state.sessionErrorRetryCount.get(sessionID) ?? 0;
5468
+ if (count < MAX_RETRIES) {
5469
+ state.sessionErrorRetryCount.set(sessionID, count + 1);
5470
+ const scheduled = scheduleManagedSessionRetry(state, sessionID, {
5471
+ delayMs: Math.min(BASE_DELAY_MS * Math.pow(2, count), MAX_DELAY_MS),
5472
+ reason: "aborted message",
5473
+ attemptLabel: `aborted attempt ${count + 1}/${MAX_RETRIES}`,
5474
+ countAgainstBudget: true
5475
+ });
5476
+ if (!scheduled) {
5477
+ state.sessionErrorRetryCount.set(sessionID, count);
5478
+ }
5479
+ }
5480
+ } else if (managedSession?.kind === "child") {
5481
+ await writeDiagnosticLog(state, "session-retry:aborted-child-observed", {
5482
+ sessionID,
5483
+ messageID,
5484
+ rootSessionID: managedSession.rootSessionID,
5485
+ eventType
5486
+ });
5487
+ }
5488
+ }
5418
5489
  if (eventType === "session.error") {
5419
5490
  await writeDiagnosticLog(state, "session-error:observed", {
5420
5491
  sessionID,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-immune",
3
- "version": "1.0.70",
3
+ "version": "1.0.72",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin: session recovery, auto-retry, multi-cycle automation, context monitoring",
6
6
  "exports": {