opencode-immune 1.0.65 → 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 +137 -5
- 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() {
|
|
@@ -3873,6 +3873,14 @@ function createState(input) {
|
|
|
3873
3873
|
sessionActive: false,
|
|
3874
3874
|
autoResumeAttempted: false,
|
|
3875
3875
|
autoResumeInFlight: false,
|
|
3876
|
+
autoCycleInFlight: false,
|
|
3877
|
+
autoCycleSourceSessions: /* @__PURE__ */ new Set(),
|
|
3878
|
+
autoCycleLockPath: join(
|
|
3879
|
+
input.directory,
|
|
3880
|
+
".opencode",
|
|
3881
|
+
"state",
|
|
3882
|
+
"ultrawork-auto-cycle-lock.json"
|
|
3883
|
+
),
|
|
3876
3884
|
cycleCount: 0,
|
|
3877
3885
|
commitPending: false,
|
|
3878
3886
|
pluginUpdateMessage: null
|
|
@@ -3910,6 +3918,7 @@ var CHILD_SESSION_FALLBACK_MODEL = {
|
|
|
3910
3918
|
providerID: "claudehub",
|
|
3911
3919
|
modelID: "claude-opus-4-7"
|
|
3912
3920
|
};
|
|
3921
|
+
var AUTO_CYCLE_LOCK_TTL_MS = 30 * 60 * 1e3;
|
|
3913
3922
|
var FALLBACK_MODEL_CANDIDATES = [
|
|
3914
3923
|
CHILD_SESSION_FALLBACK_MODEL,
|
|
3915
3924
|
RATE_LIMIT_FALLBACK_MODEL,
|
|
@@ -3966,6 +3975,7 @@ async function applyUltraworkSessionPermissions(state, sessionID) {
|
|
|
3966
3975
|
}
|
|
3967
3976
|
}
|
|
3968
3977
|
async function promptManagedSession(state, sessionID, text, options = {}) {
|
|
3978
|
+
await applyUltraworkSessionPermissions(state, sessionID);
|
|
3969
3979
|
const result = await state.client.session.promptAsync({
|
|
3970
3980
|
directory: state.input.directory,
|
|
3971
3981
|
sessionID,
|
|
@@ -4726,6 +4736,74 @@ async function parseTasksFile(directory) {
|
|
|
4726
4736
|
return null;
|
|
4727
4737
|
}
|
|
4728
4738
|
}
|
|
4739
|
+
async function hasPendingBacklogTasks(directory) {
|
|
4740
|
+
try {
|
|
4741
|
+
const backlogPath = join(directory, "memory-bank", "backlog.md");
|
|
4742
|
+
const backlogContent = await readFile(backlogPath, "utf-8");
|
|
4743
|
+
return /- \[ \]/.test(backlogContent);
|
|
4744
|
+
} catch {
|
|
4745
|
+
return false;
|
|
4746
|
+
}
|
|
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
|
+
}
|
|
4729
4807
|
var HARNESS_VERSION_FILE = ".harness-version";
|
|
4730
4808
|
var HARNESS_TOKEN_ENV = "OPENCODE_IMMUNE_TOKEN";
|
|
4731
4809
|
var DEFAULT_HARNESS_REPO = "gendoor/opencode-immune-harness";
|
|
@@ -5040,6 +5118,7 @@ function createSessionRecoveryEvent(state) {
|
|
|
5040
5118
|
const recovery = await parseTasksFile(state.input.directory);
|
|
5041
5119
|
if (recovery) {
|
|
5042
5120
|
state.recoveryContext = recovery;
|
|
5121
|
+
await clearAutoCycleLock(state, `active task observed in ${sessionID}`);
|
|
5043
5122
|
pluginLog.info(
|
|
5044
5123
|
`[opencode-immune] Active task found: "${recovery.task}" (Level ${recovery.level}, Phase: ${recovery.phase})`
|
|
5045
5124
|
);
|
|
@@ -5512,8 +5591,9 @@ function createTextCompleteHandler(state) {
|
|
|
5512
5591
|
const sessionID = input.sessionID;
|
|
5513
5592
|
const text = output.text ?? "";
|
|
5514
5593
|
if (!text) return;
|
|
5594
|
+
const isManagedRootSession = isManagedRootUltraworkSession(state, sessionID);
|
|
5515
5595
|
cancelProviderRetryWatchdog(state, sessionID, "assistant text completed");
|
|
5516
|
-
if (isProviderRetryBanner(text) &&
|
|
5596
|
+
if (isProviderRetryBanner(text) && isManagedRootSession) {
|
|
5517
5597
|
const fallbackModel = getSessionFallbackModel(state, sessionID);
|
|
5518
5598
|
await setSessionFallbackModel(state, sessionID, fallbackModel);
|
|
5519
5599
|
scheduleManagedSessionRetry(state, sessionID, {
|
|
@@ -5528,6 +5608,7 @@ function createTextCompleteHandler(state) {
|
|
|
5528
5608
|
}
|
|
5529
5609
|
if (text.includes(ALL_CYCLES_COMPLETE_MARKER)) {
|
|
5530
5610
|
await clearUltraworkMarker(state);
|
|
5611
|
+
await clearAutoCycleLock(state, "all cycles complete");
|
|
5531
5612
|
pluginLog.info("[opencode-immune] Multi-Cycle: ALL_CYCLES_COMPLETE detected, marker cleared.");
|
|
5532
5613
|
return;
|
|
5533
5614
|
}
|
|
@@ -5595,6 +5676,49 @@ function createTextCompleteHandler(state) {
|
|
|
5595
5676
|
} catch (err) {
|
|
5596
5677
|
pluginLog.error("[opencode-immune] Multi-Cycle: Failed to create session or send prompt:", err);
|
|
5597
5678
|
}
|
|
5679
|
+
return;
|
|
5680
|
+
}
|
|
5681
|
+
if (!isManagedRootSession || state.autoCycleInFlight) return;
|
|
5682
|
+
const recovery = await parseTasksFile(state.input.directory);
|
|
5683
|
+
if (recovery) return;
|
|
5684
|
+
const hasPendingTasks = await hasPendingBacklogTasks(state.input.directory);
|
|
5685
|
+
if (!hasPendingTasks) return;
|
|
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;
|
|
5693
|
+
state.autoCycleSourceSessions.add(sessionID);
|
|
5694
|
+
state.autoCycleInFlight = true;
|
|
5695
|
+
pluginLog.warn(
|
|
5696
|
+
`[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.`
|
|
5697
|
+
);
|
|
5698
|
+
try {
|
|
5699
|
+
const newSessionID = await createManagedUltraworkSession(
|
|
5700
|
+
state,
|
|
5701
|
+
`AUTO-CYCLE: next backlog task`
|
|
5702
|
+
);
|
|
5703
|
+
if (!newSessionID) {
|
|
5704
|
+
await clearAutoCycleLock(state, "fallback create returned no session ID");
|
|
5705
|
+
pluginLog.error("[opencode-immune] Multi-Cycle fallback: Failed to create new session \u2014 no ID returned.");
|
|
5706
|
+
return;
|
|
5707
|
+
}
|
|
5708
|
+
await refreshAutoCycleLock(state, newSessionID);
|
|
5709
|
+
pluginLog.info(`[opencode-immune] Multi-Cycle fallback: New session created: ${newSessionID}`);
|
|
5710
|
+
await promptManagedSession(
|
|
5711
|
+
state,
|
|
5712
|
+
newSessionID,
|
|
5713
|
+
`[AUTO-CYCLE] Continue processing task backlog. Read memory-bank/tasks.md and memory-bank/backlog.md, pick the next pending task, and run the full pipeline.`
|
|
5714
|
+
);
|
|
5715
|
+
pluginLog.info(`[opencode-immune] Multi-Cycle fallback: Bootstrap prompt sent to ${newSessionID}`);
|
|
5716
|
+
} catch (err) {
|
|
5717
|
+
state.autoCycleSourceSessions.delete(sessionID);
|
|
5718
|
+
await clearAutoCycleLock(state, "fallback bootstrap failed");
|
|
5719
|
+
pluginLog.error("[opencode-immune] Multi-Cycle fallback: Failed to create session or send prompt:", err);
|
|
5720
|
+
} finally {
|
|
5721
|
+
state.autoCycleInFlight = false;
|
|
5598
5722
|
}
|
|
5599
5723
|
};
|
|
5600
5724
|
}
|
|
@@ -5708,10 +5832,13 @@ async function server(input) {
|
|
|
5708
5832
|
}, 5e3);
|
|
5709
5833
|
} else {
|
|
5710
5834
|
try {
|
|
5711
|
-
const
|
|
5712
|
-
const backlogContent = await readFile(backlogPath, "utf-8");
|
|
5713
|
-
const hasPendingTasks = /- \[ \]/.test(backlogContent);
|
|
5835
|
+
const hasPendingTasks = await hasPendingBacklogTasks(state.input.directory);
|
|
5714
5836
|
if (hasPendingTasks) {
|
|
5837
|
+
const lockAcquired = await acquireAutoCycleLock(
|
|
5838
|
+
state,
|
|
5839
|
+
"plugin-init-auto-cycle"
|
|
5840
|
+
);
|
|
5841
|
+
if (!lockAcquired) return;
|
|
5715
5842
|
pluginLog.info(
|
|
5716
5843
|
`[opencode-immune] Plugin init: no active task but backlog has pending items. Will create new session to start next cycle.`
|
|
5717
5844
|
);
|
|
@@ -5723,11 +5850,13 @@ async function server(input) {
|
|
|
5723
5850
|
`AUTO-CYCLE: next backlog task`
|
|
5724
5851
|
);
|
|
5725
5852
|
if (!newSessionID) {
|
|
5853
|
+
await clearAutoCycleLock(state, "init auto-cycle create returned no session ID");
|
|
5726
5854
|
pluginLog.error(
|
|
5727
5855
|
"[opencode-immune] Auto-cycle: Failed to create session \u2014 no session ID returned."
|
|
5728
5856
|
);
|
|
5729
5857
|
return;
|
|
5730
5858
|
}
|
|
5859
|
+
await refreshAutoCycleLock(state, newSessionID);
|
|
5731
5860
|
pluginLog.info(
|
|
5732
5861
|
`[opencode-immune] Auto-cycle: New session created: ${newSessionID}`
|
|
5733
5862
|
);
|
|
@@ -5741,6 +5870,7 @@ async function server(input) {
|
|
|
5741
5870
|
`[opencode-immune] Auto-cycle prompt sent to new session ${newSessionID}`
|
|
5742
5871
|
);
|
|
5743
5872
|
} catch (err) {
|
|
5873
|
+
await clearAutoCycleLock(state, "init auto-cycle bootstrap failed");
|
|
5744
5874
|
pluginLog.error(
|
|
5745
5875
|
"[opencode-immune] Auto-cycle: Failed to create session or send prompt:",
|
|
5746
5876
|
err
|
|
@@ -5751,12 +5881,14 @@ async function server(input) {
|
|
|
5751
5881
|
}, 5e3);
|
|
5752
5882
|
} else {
|
|
5753
5883
|
await clearUltraworkMarker(state);
|
|
5884
|
+
await clearAutoCycleLock(state, "no pending backlog tasks");
|
|
5754
5885
|
pluginLog.info(
|
|
5755
5886
|
`[opencode-immune] Plugin init: no active task and no pending backlog. Marker cleared.`
|
|
5756
5887
|
);
|
|
5757
5888
|
}
|
|
5758
5889
|
} catch {
|
|
5759
5890
|
await clearUltraworkMarker(state);
|
|
5891
|
+
await clearAutoCycleLock(state, "backlog unreadable");
|
|
5760
5892
|
pluginLog.info(
|
|
5761
5893
|
`[opencode-immune] Plugin init: no active task, backlog unreadable. Marker cleared.`
|
|
5762
5894
|
);
|