opencode-immune 1.0.70 → 1.0.71
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/server.js +89 -22
- package/package.json +1 -1
package/dist/plugin/server.js
CHANGED
|
@@ -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.
|
|
3780
|
+
var PLUGIN_VERSION = "1.0.71";
|
|
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(),
|
|
@@ -4346,6 +4347,46 @@ function getRetryableErrorType(error) {
|
|
|
4346
4347
|
if (action === "retry") return `transient provider error${statusSuffix}`;
|
|
4347
4348
|
return "non-retryable provider error";
|
|
4348
4349
|
}
|
|
4350
|
+
function compactUnknown(value, maxLength = 8e3) {
|
|
4351
|
+
try {
|
|
4352
|
+
const serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
4353
|
+
return (serialized ?? "").slice(0, maxLength).toLowerCase();
|
|
4354
|
+
} catch {
|
|
4355
|
+
return String(value).slice(0, maxLength).toLowerCase();
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
function isAbortedMessageEvent(eventType, properties) {
|
|
4359
|
+
if (eventType !== "message.updated" && eventType !== "message.part.updated") return false;
|
|
4360
|
+
const text = compactUnknown(properties ?? {});
|
|
4361
|
+
return text.includes("messageabortederror") || text.includes("tool execution aborted") || text.includes('"interrupted":true') || text.includes('"status":"error"') && text.includes('"aborted"');
|
|
4362
|
+
}
|
|
4363
|
+
function getMessageIDFromEvent(properties) {
|
|
4364
|
+
const info = isRecord(properties?.info) ? properties.info : void 0;
|
|
4365
|
+
const part = isRecord(properties?.part) ? properties.part : void 0;
|
|
4366
|
+
return stringifyErrorField(properties?.messageID) ?? stringifyErrorField(info?.id) ?? stringifyErrorField(part?.messageID) ?? stringifyErrorField(properties?.id);
|
|
4367
|
+
}
|
|
4368
|
+
async function recoverUntrackedRootSessionForActiveTask(state, sessionID, reason) {
|
|
4369
|
+
const markerActive = await isUltraworkMarkerActive(state);
|
|
4370
|
+
if (!markerActive) return void 0;
|
|
4371
|
+
const recovery = await parseTasksFile(state.input.directory);
|
|
4372
|
+
if (!recovery || recovery.phase === "ARCHIVE: DONE") return void 0;
|
|
4373
|
+
state.recoveryContext = recovery;
|
|
4374
|
+
await addManagedUltraworkSession(state, sessionID);
|
|
4375
|
+
const recovered = getManagedSession(state, sessionID);
|
|
4376
|
+
await writeDiagnosticLog(state, "session-retry:recovered-untracked-root", {
|
|
4377
|
+
sessionID,
|
|
4378
|
+
task: recovery.task,
|
|
4379
|
+
level: recovery.level,
|
|
4380
|
+
phase: recovery.phase,
|
|
4381
|
+
reason
|
|
4382
|
+
});
|
|
4383
|
+
writePluginLog(
|
|
4384
|
+
state,
|
|
4385
|
+
"warn",
|
|
4386
|
+
`[opencode-immune] Recovered untracked root session ${sessionID} for ${reason}.`
|
|
4387
|
+
);
|
|
4388
|
+
return recovered;
|
|
4389
|
+
}
|
|
4349
4390
|
function recordChildFallbackRequest(state, managedSession, childSessionID, error) {
|
|
4350
4391
|
const fallbackModel = selectFallbackModel(
|
|
4351
4392
|
getFailedModelFromError(error) ?? managedSession.currentModel
|
|
@@ -4405,26 +4446,11 @@ async function setSessionFallbackModel(state, sessionID, model) {
|
|
|
4405
4446
|
}
|
|
4406
4447
|
async function recoverUntrackedRootSessionForRetry(state, sessionID, error) {
|
|
4407
4448
|
if (!isRetryableApiError(error)) return void 0;
|
|
4408
|
-
|
|
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(
|
|
4449
|
+
return recoverUntrackedRootSessionForActiveTask(
|
|
4423
4450
|
state,
|
|
4424
|
-
|
|
4425
|
-
`
|
|
4451
|
+
sessionID,
|
|
4452
|
+
`retry after ${getRetryableErrorType(error)}`
|
|
4426
4453
|
);
|
|
4427
|
-
return recovered;
|
|
4428
4454
|
}
|
|
4429
4455
|
function getManagedSessionRetryContext(state, sessionID) {
|
|
4430
4456
|
const managedSession = state.managedUltraworkSessions.get(sessionID);
|
|
@@ -5412,9 +5438,50 @@ function createEventHandler(state) {
|
|
|
5412
5438
|
await sessionRecovery(input);
|
|
5413
5439
|
const event = input.event;
|
|
5414
5440
|
const eventType = event.type ?? "unknown";
|
|
5415
|
-
const
|
|
5416
|
-
const
|
|
5417
|
-
const
|
|
5441
|
+
const properties = event.properties;
|
|
5442
|
+
const info = properties?.info;
|
|
5443
|
+
const sessionID = properties?.sessionID ?? info?.sessionID ?? info?.id;
|
|
5444
|
+
const error = properties?.error ?? info?.error;
|
|
5445
|
+
if (sessionID && isAbortedMessageEvent(eventType, properties)) {
|
|
5446
|
+
const messageID = getMessageIDFromEvent(properties) ?? `${sessionID}:unknown`;
|
|
5447
|
+
const retryKey = `${sessionID}:${messageID}`;
|
|
5448
|
+
if (state.abortedMessageRetries.has(retryKey)) {
|
|
5449
|
+
return;
|
|
5450
|
+
}
|
|
5451
|
+
const managedSession = getManagedSession(state, sessionID) ?? await recoverUntrackedRootSessionForActiveTask(
|
|
5452
|
+
state,
|
|
5453
|
+
sessionID,
|
|
5454
|
+
"aborted message recovery"
|
|
5455
|
+
);
|
|
5456
|
+
if (managedSession?.kind === "root") {
|
|
5457
|
+
state.abortedMessageRetries.add(retryKey);
|
|
5458
|
+
await writeDiagnosticLog(state, "session-retry:aborted-message-observed", {
|
|
5459
|
+
sessionID,
|
|
5460
|
+
messageID,
|
|
5461
|
+
eventType
|
|
5462
|
+
});
|
|
5463
|
+
const count = state.sessionErrorRetryCount.get(sessionID) ?? 0;
|
|
5464
|
+
if (count < MAX_RETRIES) {
|
|
5465
|
+
state.sessionErrorRetryCount.set(sessionID, count + 1);
|
|
5466
|
+
const scheduled = scheduleManagedSessionRetry(state, sessionID, {
|
|
5467
|
+
delayMs: Math.min(BASE_DELAY_MS * Math.pow(2, count), MAX_DELAY_MS),
|
|
5468
|
+
reason: "aborted message",
|
|
5469
|
+
attemptLabel: `aborted attempt ${count + 1}/${MAX_RETRIES}`,
|
|
5470
|
+
countAgainstBudget: true
|
|
5471
|
+
});
|
|
5472
|
+
if (!scheduled) {
|
|
5473
|
+
state.sessionErrorRetryCount.set(sessionID, count);
|
|
5474
|
+
}
|
|
5475
|
+
}
|
|
5476
|
+
} else if (managedSession?.kind === "child") {
|
|
5477
|
+
await writeDiagnosticLog(state, "session-retry:aborted-child-observed", {
|
|
5478
|
+
sessionID,
|
|
5479
|
+
messageID,
|
|
5480
|
+
rootSessionID: managedSession.rootSessionID,
|
|
5481
|
+
eventType
|
|
5482
|
+
});
|
|
5483
|
+
}
|
|
5484
|
+
}
|
|
5418
5485
|
if (eventType === "session.error") {
|
|
5419
5486
|
await writeDiagnosticLog(state, "session-error:observed", {
|
|
5420
5487
|
sessionID,
|