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.
@@ -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.66";
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
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-immune",
3
- "version": "1.0.66",
3
+ "version": "1.0.67",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin: session recovery, auto-retry, multi-cycle automation, context monitoring",
6
6
  "exports": {