opencode-immune 1.0.66 → 1.0.67
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 +88 -1
- 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.67";
|
|
3781
3781
|
var PLUGIN_PACKAGE_NAME = "opencode-immune";
|
|
3782
3782
|
var PLUGIN_DIRNAME = dirname(fileURLToPath(import.meta.url));
|
|
3783
3783
|
function getServerAuthHeaders() {
|
|
@@ -3875,6 +3875,12 @@ function createState(input) {
|
|
|
3875
3875
|
autoResumeInFlight: false,
|
|
3876
3876
|
autoCycleInFlight: false,
|
|
3877
3877
|
autoCycleSourceSessions: /* @__PURE__ */ new Set(),
|
|
3878
|
+
autoCycleLockPath: join(
|
|
3879
|
+
input.directory,
|
|
3880
|
+
".opencode",
|
|
3881
|
+
"state",
|
|
3882
|
+
"ultrawork-auto-cycle-lock.json"
|
|
3883
|
+
),
|
|
3878
3884
|
cycleCount: 0,
|
|
3879
3885
|
commitPending: false,
|
|
3880
3886
|
pluginUpdateMessage: null
|
|
@@ -3912,6 +3918,7 @@ var CHILD_SESSION_FALLBACK_MODEL = {
|
|
|
3912
3918
|
providerID: "claudehub",
|
|
3913
3919
|
modelID: "claude-opus-4-7"
|
|
3914
3920
|
};
|
|
3921
|
+
var AUTO_CYCLE_LOCK_TTL_MS = 30 * 60 * 1e3;
|
|
3915
3922
|
var FALLBACK_MODEL_CANDIDATES = [
|
|
3916
3923
|
CHILD_SESSION_FALLBACK_MODEL,
|
|
3917
3924
|
RATE_LIMIT_FALLBACK_MODEL,
|
|
@@ -4738,6 +4745,65 @@ async function hasPendingBacklogTasks(directory) {
|
|
|
4738
4745
|
return false;
|
|
4739
4746
|
}
|
|
4740
4747
|
}
|
|
4748
|
+
async function acquireAutoCycleLock(state, source, sourceSessionID) {
|
|
4749
|
+
try {
|
|
4750
|
+
const now = Date.now();
|
|
4751
|
+
try {
|
|
4752
|
+
const raw = await readFile(state.autoCycleLockPath, "utf-8");
|
|
4753
|
+
const existing = JSON.parse(raw);
|
|
4754
|
+
const lockTime = Date.parse(existing.updatedAt ?? existing.createdAt ?? "");
|
|
4755
|
+
if (Number.isFinite(lockTime) && now - lockTime < AUTO_CYCLE_LOCK_TTL_MS) {
|
|
4756
|
+
pluginLog.info(
|
|
4757
|
+
`[opencode-immune] Auto-cycle lock active for ${existing.sessionID ?? "unknown session"}; skipping ${source}.`
|
|
4758
|
+
);
|
|
4759
|
+
return false;
|
|
4760
|
+
}
|
|
4761
|
+
} catch {
|
|
4762
|
+
}
|
|
4763
|
+
await mkdir(dirname(state.autoCycleLockPath), { recursive: true });
|
|
4764
|
+
const payload = JSON.stringify(
|
|
4765
|
+
{
|
|
4766
|
+
active: true,
|
|
4767
|
+
source,
|
|
4768
|
+
sourceSessionID,
|
|
4769
|
+
createdAt: new Date(now).toISOString(),
|
|
4770
|
+
updatedAt: new Date(now).toISOString()
|
|
4771
|
+
},
|
|
4772
|
+
null,
|
|
4773
|
+
2
|
|
4774
|
+
);
|
|
4775
|
+
await writeFile(state.autoCycleLockPath, payload, { flag: "wx" });
|
|
4776
|
+
return true;
|
|
4777
|
+
} catch (err) {
|
|
4778
|
+
const code = err.code;
|
|
4779
|
+
if (code === "EEXIST") {
|
|
4780
|
+
pluginLog.info(`[opencode-immune] Auto-cycle lock already exists; skipping ${source}.`);
|
|
4781
|
+
return false;
|
|
4782
|
+
}
|
|
4783
|
+
pluginLog.warn(
|
|
4784
|
+
`[opencode-immune] Auto-cycle lock failed for ${source}; refusing to start duplicate-prone AUTO-CYCLE:`,
|
|
4785
|
+
err
|
|
4786
|
+
);
|
|
4787
|
+
return false;
|
|
4788
|
+
}
|
|
4789
|
+
}
|
|
4790
|
+
async function refreshAutoCycleLock(state, sessionID) {
|
|
4791
|
+
try {
|
|
4792
|
+
const raw = await readFile(state.autoCycleLockPath, "utf-8");
|
|
4793
|
+
const existing = JSON.parse(raw);
|
|
4794
|
+
existing.sessionID = sessionID;
|
|
4795
|
+
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4796
|
+
await writeFile(state.autoCycleLockPath, JSON.stringify(existing, null, 2));
|
|
4797
|
+
} catch {
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
async function clearAutoCycleLock(state, reason) {
|
|
4801
|
+
try {
|
|
4802
|
+
await unlink(state.autoCycleLockPath);
|
|
4803
|
+
pluginLog.info(`[opencode-immune] Auto-cycle lock cleared: ${reason}`);
|
|
4804
|
+
} catch {
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4741
4807
|
var HARNESS_VERSION_FILE = ".harness-version";
|
|
4742
4808
|
var HARNESS_TOKEN_ENV = "OPENCODE_IMMUNE_TOKEN";
|
|
4743
4809
|
var DEFAULT_HARNESS_REPO = "gendoor/opencode-immune-harness";
|
|
@@ -5052,6 +5118,7 @@ function createSessionRecoveryEvent(state) {
|
|
|
5052
5118
|
const recovery = await parseTasksFile(state.input.directory);
|
|
5053
5119
|
if (recovery) {
|
|
5054
5120
|
state.recoveryContext = recovery;
|
|
5121
|
+
await clearAutoCycleLock(state, `active task observed in ${sessionID}`);
|
|
5055
5122
|
pluginLog.info(
|
|
5056
5123
|
`[opencode-immune] Active task found: "${recovery.task}" (Level ${recovery.level}, Phase: ${recovery.phase})`
|
|
5057
5124
|
);
|
|
@@ -5541,6 +5608,7 @@ function createTextCompleteHandler(state) {
|
|
|
5541
5608
|
}
|
|
5542
5609
|
if (text.includes(ALL_CYCLES_COMPLETE_MARKER)) {
|
|
5543
5610
|
await clearUltraworkMarker(state);
|
|
5611
|
+
await clearAutoCycleLock(state, "all cycles complete");
|
|
5544
5612
|
pluginLog.info("[opencode-immune] Multi-Cycle: ALL_CYCLES_COMPLETE detected, marker cleared.");
|
|
5545
5613
|
return;
|
|
5546
5614
|
}
|
|
@@ -5616,6 +5684,12 @@ function createTextCompleteHandler(state) {
|
|
|
5616
5684
|
const hasPendingTasks = await hasPendingBacklogTasks(state.input.directory);
|
|
5617
5685
|
if (!hasPendingTasks) return;
|
|
5618
5686
|
if (state.autoCycleSourceSessions.has(sessionID)) return;
|
|
5687
|
+
const lockAcquired = await acquireAutoCycleLock(
|
|
5688
|
+
state,
|
|
5689
|
+
"multi-cycle-fallback",
|
|
5690
|
+
sessionID
|
|
5691
|
+
);
|
|
5692
|
+
if (!lockAcquired) return;
|
|
5619
5693
|
state.autoCycleSourceSessions.add(sessionID);
|
|
5620
5694
|
state.autoCycleInFlight = true;
|
|
5621
5695
|
pluginLog.warn(
|
|
@@ -5627,9 +5701,11 @@ function createTextCompleteHandler(state) {
|
|
|
5627
5701
|
`AUTO-CYCLE: next backlog task`
|
|
5628
5702
|
);
|
|
5629
5703
|
if (!newSessionID) {
|
|
5704
|
+
await clearAutoCycleLock(state, "fallback create returned no session ID");
|
|
5630
5705
|
pluginLog.error("[opencode-immune] Multi-Cycle fallback: Failed to create new session \u2014 no ID returned.");
|
|
5631
5706
|
return;
|
|
5632
5707
|
}
|
|
5708
|
+
await refreshAutoCycleLock(state, newSessionID);
|
|
5633
5709
|
pluginLog.info(`[opencode-immune] Multi-Cycle fallback: New session created: ${newSessionID}`);
|
|
5634
5710
|
await promptManagedSession(
|
|
5635
5711
|
state,
|
|
@@ -5639,6 +5715,7 @@ function createTextCompleteHandler(state) {
|
|
|
5639
5715
|
pluginLog.info(`[opencode-immune] Multi-Cycle fallback: Bootstrap prompt sent to ${newSessionID}`);
|
|
5640
5716
|
} catch (err) {
|
|
5641
5717
|
state.autoCycleSourceSessions.delete(sessionID);
|
|
5718
|
+
await clearAutoCycleLock(state, "fallback bootstrap failed");
|
|
5642
5719
|
pluginLog.error("[opencode-immune] Multi-Cycle fallback: Failed to create session or send prompt:", err);
|
|
5643
5720
|
} finally {
|
|
5644
5721
|
state.autoCycleInFlight = false;
|
|
@@ -5757,6 +5834,11 @@ async function server(input) {
|
|
|
5757
5834
|
try {
|
|
5758
5835
|
const hasPendingTasks = await hasPendingBacklogTasks(state.input.directory);
|
|
5759
5836
|
if (hasPendingTasks) {
|
|
5837
|
+
const lockAcquired = await acquireAutoCycleLock(
|
|
5838
|
+
state,
|
|
5839
|
+
"plugin-init-auto-cycle"
|
|
5840
|
+
);
|
|
5841
|
+
if (!lockAcquired) return;
|
|
5760
5842
|
pluginLog.info(
|
|
5761
5843
|
`[opencode-immune] Plugin init: no active task but backlog has pending items. Will create new session to start next cycle.`
|
|
5762
5844
|
);
|
|
@@ -5768,11 +5850,13 @@ async function server(input) {
|
|
|
5768
5850
|
`AUTO-CYCLE: next backlog task`
|
|
5769
5851
|
);
|
|
5770
5852
|
if (!newSessionID) {
|
|
5853
|
+
await clearAutoCycleLock(state, "init auto-cycle create returned no session ID");
|
|
5771
5854
|
pluginLog.error(
|
|
5772
5855
|
"[opencode-immune] Auto-cycle: Failed to create session \u2014 no session ID returned."
|
|
5773
5856
|
);
|
|
5774
5857
|
return;
|
|
5775
5858
|
}
|
|
5859
|
+
await refreshAutoCycleLock(state, newSessionID);
|
|
5776
5860
|
pluginLog.info(
|
|
5777
5861
|
`[opencode-immune] Auto-cycle: New session created: ${newSessionID}`
|
|
5778
5862
|
);
|
|
@@ -5786,6 +5870,7 @@ async function server(input) {
|
|
|
5786
5870
|
`[opencode-immune] Auto-cycle prompt sent to new session ${newSessionID}`
|
|
5787
5871
|
);
|
|
5788
5872
|
} catch (err) {
|
|
5873
|
+
await clearAutoCycleLock(state, "init auto-cycle bootstrap failed");
|
|
5789
5874
|
pluginLog.error(
|
|
5790
5875
|
"[opencode-immune] Auto-cycle: Failed to create session or send prompt:",
|
|
5791
5876
|
err
|
|
@@ -5796,12 +5881,14 @@ async function server(input) {
|
|
|
5796
5881
|
}, 5e3);
|
|
5797
5882
|
} else {
|
|
5798
5883
|
await clearUltraworkMarker(state);
|
|
5884
|
+
await clearAutoCycleLock(state, "no pending backlog tasks");
|
|
5799
5885
|
pluginLog.info(
|
|
5800
5886
|
`[opencode-immune] Plugin init: no active task and no pending backlog. Marker cleared.`
|
|
5801
5887
|
);
|
|
5802
5888
|
}
|
|
5803
5889
|
} catch {
|
|
5804
5890
|
await clearUltraworkMarker(state);
|
|
5891
|
+
await clearAutoCycleLock(state, "backlog unreadable");
|
|
5805
5892
|
pluginLog.info(
|
|
5806
5893
|
`[opencode-immune] Plugin init: no active task, backlog unreadable. Marker cleared.`
|
|
5807
5894
|
);
|