opencode-immune 1.0.79 → 1.0.81
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 +133 -34
- 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.81";
|
|
3781
3781
|
var PLUGIN_PACKAGE_NAME = "opencode-immune";
|
|
3782
3782
|
var PLUGIN_DIRNAME = dirname(fileURLToPath(import.meta.url));
|
|
3783
3783
|
function getServerAuthHeaders() {
|
|
@@ -3876,6 +3876,7 @@ function createState(input) {
|
|
|
3876
3876
|
autoResumeInFlight: false,
|
|
3877
3877
|
autoCycleInFlight: false,
|
|
3878
3878
|
autoCycleSourceSessions: /* @__PURE__ */ new Set(),
|
|
3879
|
+
autoCycleFallbackTimers: /* @__PURE__ */ new Map(),
|
|
3879
3880
|
autoCycleLockPath: join(
|
|
3880
3881
|
input.directory,
|
|
3881
3882
|
".opencode",
|
|
@@ -3895,6 +3896,8 @@ var PROVIDER_RETRY_WATCHDOG_MS = 3e4;
|
|
|
3895
3896
|
var RETRY_PROMPT_DELIVERY_ATTEMPTS = 3;
|
|
3896
3897
|
var CHILD_FALLBACK_REQUEST_TTL_MS = 10 * 60 * 1e3;
|
|
3897
3898
|
var AUTO_CYCLE_LOCK_TTL_MS = 30 * 60 * 1e3;
|
|
3899
|
+
var FALLBACK_AUTO_CYCLE_DELAY_MS = 6e4;
|
|
3900
|
+
var ROOT_RETRY_CHILD_ACTIVITY_SUPPRESSION_MS = 2 * 6e4;
|
|
3898
3901
|
var MODEL_NAME_CAPABILITY_SCORE = {
|
|
3899
3902
|
"claude-opus-4-7": 100,
|
|
3900
3903
|
"gpt-5.5": 100,
|
|
@@ -3914,6 +3917,26 @@ function isManagedRootUltraworkSession(state, sessionID) {
|
|
|
3914
3917
|
const record = getManagedSession(state, sessionID);
|
|
3915
3918
|
return !!record && record.kind === "root";
|
|
3916
3919
|
}
|
|
3920
|
+
function hasRecentManagedChildSessionForRoot(state, rootSessionID, sinceMs) {
|
|
3921
|
+
const cutoff = Date.now() - sinceMs;
|
|
3922
|
+
for (const record of state.managedUltraworkSessions.values()) {
|
|
3923
|
+
if (record.kind === "child" && record.rootSessionID === rootSessionID && record.updatedAt >= cutoff) return true;
|
|
3924
|
+
}
|
|
3925
|
+
return false;
|
|
3926
|
+
}
|
|
3927
|
+
function hasPendingChildFallbackRequestForRoot(state, rootSessionID, now = Date.now()) {
|
|
3928
|
+
for (const request of state.childFallbackRequests.values()) {
|
|
3929
|
+
if (request.rootSessionID === rootSessionID && now - request.createdAt <= CHILD_FALLBACK_REQUEST_TTL_MS) return true;
|
|
3930
|
+
}
|
|
3931
|
+
return false;
|
|
3932
|
+
}
|
|
3933
|
+
function shouldSuppressRootRetryForChildActivity(state, rootSessionID) {
|
|
3934
|
+
return hasPendingChildFallbackRequestForRoot(state, rootSessionID) || hasRecentManagedChildSessionForRoot(
|
|
3935
|
+
state,
|
|
3936
|
+
rootSessionID,
|
|
3937
|
+
ROOT_RETRY_CHILD_ACTIVITY_SUPPRESSION_MS
|
|
3938
|
+
);
|
|
3939
|
+
}
|
|
3917
3940
|
async function createManagedUltraworkSession(state, title) {
|
|
3918
3941
|
const result = await state.client.session.create({
|
|
3919
3942
|
directory: state.input.directory,
|
|
@@ -4092,8 +4115,15 @@ async function isUltraworkMarkerActive(state) {
|
|
|
4092
4115
|
}
|
|
4093
4116
|
async function addManagedUltraworkSession(state, sessionID, timestamp = Date.now()) {
|
|
4094
4117
|
const existing = state.managedUltraworkSessions.get(sessionID);
|
|
4118
|
+
if (existing?.kind === "child") {
|
|
4119
|
+
state.managedUltraworkSessions.set(sessionID, {
|
|
4120
|
+
...existing,
|
|
4121
|
+
updatedAt: timestamp
|
|
4122
|
+
});
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4095
4125
|
const nextRecord = {
|
|
4096
|
-
kind:
|
|
4126
|
+
kind: "root",
|
|
4097
4127
|
agent: ULTRAWORK_AGENT,
|
|
4098
4128
|
rootSessionID: existing?.rootSessionID ?? sessionID,
|
|
4099
4129
|
parentSessionID: existing?.parentSessionID,
|
|
@@ -4110,6 +4140,11 @@ async function addManagedUltraworkSession(state, sessionID, timestamp = Date.now
|
|
|
4110
4140
|
async function addManagedChildSession(state, sessionID, parentSessionID, timestamp = Date.now()) {
|
|
4111
4141
|
const parent = state.managedUltraworkSessions.get(parentSessionID);
|
|
4112
4142
|
if (!parent) return;
|
|
4143
|
+
cancelFallbackAutoCycle(
|
|
4144
|
+
state,
|
|
4145
|
+
parent.rootSessionID,
|
|
4146
|
+
`child session ${sessionID} created`
|
|
4147
|
+
);
|
|
4113
4148
|
cancelProviderRetryWatchdog(
|
|
4114
4149
|
state,
|
|
4115
4150
|
parent.rootSessionID,
|
|
@@ -4155,8 +4190,13 @@ function cancelProviderRetryWatchdog(state, sessionID, reason) {
|
|
|
4155
4190
|
);
|
|
4156
4191
|
}
|
|
4157
4192
|
async function removeManagedUltraworkSession(state, sessionID, reason) {
|
|
4193
|
+
const existing = state.managedUltraworkSessions.get(sessionID);
|
|
4158
4194
|
cancelPendingSessionRetry(state, sessionID, reason);
|
|
4159
4195
|
cancelProviderRetryWatchdog(state, sessionID, reason);
|
|
4196
|
+
cancelFallbackAutoCycle(state, sessionID, reason);
|
|
4197
|
+
if (existing?.kind === "child") {
|
|
4198
|
+
cancelFallbackAutoCycle(state, existing.rootSessionID, `child session ${sessionID} ${reason}`);
|
|
4199
|
+
}
|
|
4160
4200
|
state.sessionErrorRetryCount.delete(sessionID);
|
|
4161
4201
|
const existed = state.managedUltraworkSessions.delete(sessionID);
|
|
4162
4202
|
if (!existed) return;
|
|
@@ -5622,6 +5662,14 @@ function createEventHandler(state) {
|
|
|
5622
5662
|
"aborted message recovery"
|
|
5623
5663
|
);
|
|
5624
5664
|
if (managedSession?.kind === "root") {
|
|
5665
|
+
if (shouldSuppressRootRetryForChildActivity(state, sessionID)) {
|
|
5666
|
+
await writeDiagnosticLog(state, "session-retry:root-suppressed-child-active", {
|
|
5667
|
+
sessionID,
|
|
5668
|
+
messageID,
|
|
5669
|
+
eventType
|
|
5670
|
+
});
|
|
5671
|
+
return;
|
|
5672
|
+
}
|
|
5625
5673
|
state.abortedMessageRetries.add(retryKey);
|
|
5626
5674
|
await writeDiagnosticLog(state, "session-retry:aborted-message-observed", {
|
|
5627
5675
|
sessionID,
|
|
@@ -5715,6 +5763,17 @@ function createEventHandler(state) {
|
|
|
5715
5763
|
);
|
|
5716
5764
|
return;
|
|
5717
5765
|
}
|
|
5766
|
+
if (isRoot && shouldSuppressRootRetryForChildActivity(state, sessionID)) {
|
|
5767
|
+
await writeDiagnosticLog(state, "session-retry:root-suppressed-child-active", {
|
|
5768
|
+
sessionID,
|
|
5769
|
+
eventType,
|
|
5770
|
+
errorType: getRetryableErrorType(error)
|
|
5771
|
+
});
|
|
5772
|
+
pluginLog.info(
|
|
5773
|
+
`[opencode-immune] Suppressed root retry for ${sessionID}: child session activity owns this failure.`
|
|
5774
|
+
);
|
|
5775
|
+
return;
|
|
5776
|
+
}
|
|
5718
5777
|
const count = state.sessionErrorRetryCount.get(sessionID) ?? 0;
|
|
5719
5778
|
if (count < MAX_RETRIES) {
|
|
5720
5779
|
const delay = Math.min(BASE_DELAY_MS * Math.pow(2, count), MAX_DELAY_MS);
|
|
@@ -5760,7 +5819,15 @@ function createEventHandler(state) {
|
|
|
5760
5819
|
state.sessionErrorRetryCount.delete(sessionID);
|
|
5761
5820
|
if (markUltraworkSessionActive(state, sessionID)) {
|
|
5762
5821
|
}
|
|
5822
|
+
if (managedSession?.kind === "root") {
|
|
5823
|
+
cancelFallbackAutoCycle(state, sessionID, "root session updated");
|
|
5824
|
+
}
|
|
5763
5825
|
if (managedSession?.kind === "child") {
|
|
5826
|
+
cancelFallbackAutoCycle(
|
|
5827
|
+
state,
|
|
5828
|
+
managedSession.rootSessionID,
|
|
5829
|
+
`child session ${sessionID} updated`
|
|
5830
|
+
);
|
|
5764
5831
|
cancelProviderRetryWatchdog(
|
|
5765
5832
|
state,
|
|
5766
5833
|
managedSession.rootSessionID,
|
|
@@ -5802,6 +5869,64 @@ async function commitCycleChanges(state, reason) {
|
|
|
5802
5869
|
state.commitPending = false;
|
|
5803
5870
|
}
|
|
5804
5871
|
}
|
|
5872
|
+
function cancelFallbackAutoCycle(state, sessionID, reason) {
|
|
5873
|
+
const timer = state.autoCycleFallbackTimers.get(sessionID);
|
|
5874
|
+
if (!timer) return;
|
|
5875
|
+
clearTimeout(timer);
|
|
5876
|
+
state.autoCycleFallbackTimers.delete(sessionID);
|
|
5877
|
+
pluginLog.info(
|
|
5878
|
+
`[opencode-immune] Cancelled fallback AUTO-CYCLE for session ${sessionID}: ${reason}`
|
|
5879
|
+
);
|
|
5880
|
+
}
|
|
5881
|
+
function scheduleFallbackAutoCycle(state, sessionID) {
|
|
5882
|
+
if (state.autoCycleFallbackTimers.has(sessionID)) return;
|
|
5883
|
+
const timer = setTimeout(async () => {
|
|
5884
|
+
state.autoCycleFallbackTimers.delete(sessionID);
|
|
5885
|
+
if (!isManagedRootUltraworkSession(state, sessionID)) return;
|
|
5886
|
+
if (state.autoCycleInFlight || state.autoCycleSourceSessions.has(sessionID)) return;
|
|
5887
|
+
if (hasRecentManagedChildSessionForRoot(state, sessionID, FALLBACK_AUTO_CYCLE_DELAY_MS)) {
|
|
5888
|
+
scheduleFallbackAutoCycle(state, sessionID);
|
|
5889
|
+
return;
|
|
5890
|
+
}
|
|
5891
|
+
const recovery = await parseTasksFile(state.input.directory);
|
|
5892
|
+
if (recovery) return;
|
|
5893
|
+
const hasPendingTasks = await hasPendingBacklogTasks(state.input.directory);
|
|
5894
|
+
if (!hasPendingTasks) return;
|
|
5895
|
+
const lockAcquired = await acquireAutoCycleLock(
|
|
5896
|
+
state,
|
|
5897
|
+
"multi-cycle-fallback",
|
|
5898
|
+
sessionID
|
|
5899
|
+
);
|
|
5900
|
+
if (!lockAcquired) return;
|
|
5901
|
+
state.autoCycleSourceSessions.add(sessionID);
|
|
5902
|
+
state.autoCycleInFlight = true;
|
|
5903
|
+
pluginLog.warn(
|
|
5904
|
+
`[opencode-immune] Multi-Cycle fallback: no CYCLE_COMPLETE marker detected for ${sessionID} after ${FALLBACK_AUTO_CYCLE_DELAY_MS}ms grace period. Starting AUTO-CYCLE.`
|
|
5905
|
+
);
|
|
5906
|
+
try {
|
|
5907
|
+
await commitCycleChanges(state, "fallback AUTO-CYCLE before new session");
|
|
5908
|
+
await refreshAutoCycleLock(state, sessionID);
|
|
5909
|
+
await startAutoCycleInNewSession(
|
|
5910
|
+
state,
|
|
5911
|
+
{
|
|
5912
|
+
sourceSessionID: sessionID,
|
|
5913
|
+
logContext: "Multi-Cycle fallback",
|
|
5914
|
+
clearLockOnFailureReason: "fallback bootstrap failed",
|
|
5915
|
+
retireSourceSession: true
|
|
5916
|
+
}
|
|
5917
|
+
);
|
|
5918
|
+
} catch (err) {
|
|
5919
|
+
state.autoCycleSourceSessions.delete(sessionID);
|
|
5920
|
+
pluginLog.error("[opencode-immune] Multi-Cycle fallback: Failed to send prompt:", err);
|
|
5921
|
+
} finally {
|
|
5922
|
+
state.autoCycleInFlight = false;
|
|
5923
|
+
}
|
|
5924
|
+
}, FALLBACK_AUTO_CYCLE_DELAY_MS);
|
|
5925
|
+
state.autoCycleFallbackTimers.set(sessionID, timer);
|
|
5926
|
+
pluginLog.info(
|
|
5927
|
+
`[opencode-immune] Multi-Cycle fallback: scheduled delayed AUTO-CYCLE check for ${sessionID}.`
|
|
5928
|
+
);
|
|
5929
|
+
}
|
|
5805
5930
|
async function archiveProgress(directory) {
|
|
5806
5931
|
const progressPath = join(directory, "memory-bank", "progress.md");
|
|
5807
5932
|
try {
|
|
@@ -5903,23 +6028,26 @@ function createTextCompleteHandler(state) {
|
|
|
5903
6028
|
);
|
|
5904
6029
|
}
|
|
5905
6030
|
if (text.includes(ALL_CYCLES_COMPLETE_MARKER)) {
|
|
6031
|
+
cancelFallbackAutoCycle(state, sessionID, "ALL_CYCLES_COMPLETE detected");
|
|
5906
6032
|
await clearUltraworkMarker(state);
|
|
5907
6033
|
await clearAutoCycleLock(state, "all cycles complete");
|
|
5908
6034
|
pluginLog.info("[opencode-immune] Multi-Cycle: ALL_CYCLES_COMPLETE detected, marker cleared.");
|
|
5909
6035
|
return;
|
|
5910
6036
|
}
|
|
5911
6037
|
if (text.includes(PRE_COMMIT_MARKER) && !text.includes(CYCLE_COMPLETE_MARKER)) {
|
|
6038
|
+
cancelFallbackAutoCycle(state, sessionID, "PRE_COMMIT detected");
|
|
5912
6039
|
await commitCycleChanges(state, "PRE_COMMIT detected (standalone)");
|
|
5913
6040
|
return;
|
|
5914
6041
|
}
|
|
5915
6042
|
if (text.includes(CYCLE_COMPLETE_MARKER)) {
|
|
6043
|
+
cancelFallbackAutoCycle(state, sessionID, "CYCLE_COMPLETE detected");
|
|
5916
6044
|
if (state.autoCycleInFlight || state.autoCycleSourceSessions.has(sessionID)) return;
|
|
5917
|
-
const
|
|
6045
|
+
const lockAcquired = await acquireAutoCycleLock(
|
|
5918
6046
|
state,
|
|
5919
6047
|
"cycle-complete",
|
|
5920
6048
|
sessionID
|
|
5921
6049
|
);
|
|
5922
|
-
if (!
|
|
6050
|
+
if (!lockAcquired) return;
|
|
5923
6051
|
state.autoCycleSourceSessions.add(sessionID);
|
|
5924
6052
|
state.autoCycleInFlight = true;
|
|
5925
6053
|
try {
|
|
@@ -5966,36 +6094,7 @@ function createTextCompleteHandler(state) {
|
|
|
5966
6094
|
if (recovery) return;
|
|
5967
6095
|
const hasPendingTasks = await hasPendingBacklogTasks(state.input.directory);
|
|
5968
6096
|
if (!hasPendingTasks) return;
|
|
5969
|
-
|
|
5970
|
-
const lockAcquired = await acquireAutoCycleLock(
|
|
5971
|
-
state,
|
|
5972
|
-
"multi-cycle-fallback",
|
|
5973
|
-
sessionID
|
|
5974
|
-
);
|
|
5975
|
-
if (!lockAcquired) return;
|
|
5976
|
-
state.autoCycleSourceSessions.add(sessionID);
|
|
5977
|
-
state.autoCycleInFlight = true;
|
|
5978
|
-
pluginLog.warn(
|
|
5979
|
-
`[opencode-immune] Multi-Cycle fallback: no CYCLE_COMPLETE marker detected for ${sessionID}, but tasks.md has no active task and backlog has pending items. Starting AUTO-CYCLE.`
|
|
5980
|
-
);
|
|
5981
|
-
try {
|
|
5982
|
-
await commitCycleChanges(state, "fallback AUTO-CYCLE before new session");
|
|
5983
|
-
await refreshAutoCycleLock(state, sessionID);
|
|
5984
|
-
await startAutoCycleInNewSession(
|
|
5985
|
-
state,
|
|
5986
|
-
{
|
|
5987
|
-
sourceSessionID: sessionID,
|
|
5988
|
-
logContext: "Multi-Cycle fallback",
|
|
5989
|
-
clearLockOnFailureReason: "fallback bootstrap failed",
|
|
5990
|
-
retireSourceSession: true
|
|
5991
|
-
}
|
|
5992
|
-
);
|
|
5993
|
-
} catch (err) {
|
|
5994
|
-
state.autoCycleSourceSessions.delete(sessionID);
|
|
5995
|
-
pluginLog.error("[opencode-immune] Multi-Cycle fallback: Failed to send prompt:", err);
|
|
5996
|
-
} finally {
|
|
5997
|
-
state.autoCycleInFlight = false;
|
|
5998
|
-
}
|
|
6097
|
+
scheduleFallbackAutoCycle(state, sessionID);
|
|
5999
6098
|
};
|
|
6000
6099
|
}
|
|
6001
6100
|
function createMultiCycleHandler(state) {
|