opencode-immune 1.0.64 → 1.0.66

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.64";
3780
+ var PLUGIN_VERSION = "1.0.66";
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,8 @@ function createState(input) {
3873
3873
  sessionActive: false,
3874
3874
  autoResumeAttempted: false,
3875
3875
  autoResumeInFlight: false,
3876
+ autoCycleInFlight: false,
3877
+ autoCycleSourceSessions: /* @__PURE__ */ new Set(),
3876
3878
  cycleCount: 0,
3877
3879
  commitPending: false,
3878
3880
  pluginUpdateMessage: null
@@ -3966,6 +3968,7 @@ async function applyUltraworkSessionPermissions(state, sessionID) {
3966
3968
  }
3967
3969
  }
3968
3970
  async function promptManagedSession(state, sessionID, text, options = {}) {
3971
+ await applyUltraworkSessionPermissions(state, sessionID);
3969
3972
  const result = await state.client.session.promptAsync({
3970
3973
  directory: state.input.directory,
3971
3974
  sessionID,
@@ -4726,6 +4729,15 @@ async function parseTasksFile(directory) {
4726
4729
  return null;
4727
4730
  }
4728
4731
  }
4732
+ async function hasPendingBacklogTasks(directory) {
4733
+ try {
4734
+ const backlogPath = join(directory, "memory-bank", "backlog.md");
4735
+ const backlogContent = await readFile(backlogPath, "utf-8");
4736
+ return /- \[ \]/.test(backlogContent);
4737
+ } catch {
4738
+ return false;
4739
+ }
4740
+ }
4729
4741
  var HARNESS_VERSION_FILE = ".harness-version";
4730
4742
  var HARNESS_TOKEN_ENV = "OPENCODE_IMMUNE_TOKEN";
4731
4743
  var DEFAULT_HARNESS_REPO = "gendoor/opencode-immune-harness";
@@ -4826,6 +4838,24 @@ async function readLocalHarnessVersion(directory) {
4826
4838
  return null;
4827
4839
  }
4828
4840
  }
4841
+ function parseHarnessVersion(tag) {
4842
+ const match = tag.match(/^v(\d{4}\.\d{2}\.\d{2})(?:-(.+))?$/);
4843
+ if (!match?.[1]) return null;
4844
+ return {
4845
+ dateKey: match[1],
4846
+ suffix: match[2] ?? ""
4847
+ };
4848
+ }
4849
+ function compareHarnessVersions(localVersion, releaseVersion) {
4850
+ if (localVersion === releaseVersion) return 0;
4851
+ const local = parseHarnessVersion(localVersion);
4852
+ const release = parseHarnessVersion(releaseVersion);
4853
+ if (!local || !release) return null;
4854
+ if (local.dateKey < release.dateKey) return -1;
4855
+ if (local.dateKey > release.dateKey) return 1;
4856
+ if (local.suffix !== release.suffix) return null;
4857
+ return 0;
4858
+ }
4829
4859
  async function downloadHarnessAsset(assetUrl, token) {
4830
4860
  const resp = await fetch(assetUrl, {
4831
4861
  headers: {
@@ -4894,6 +4924,21 @@ async function syncHarness(state) {
4894
4924
  );
4895
4925
  return;
4896
4926
  }
4927
+ if (localVersion) {
4928
+ const versionOrder = compareHarnessVersions(localVersion, release.tagName);
4929
+ if (versionOrder === null) {
4930
+ pluginLog.warn(
4931
+ `[opencode-immune] Harness sync: local ${localVersion} and release ${release.tagName} have ambiguous ordering. Skipping sync to avoid overwriting project harness.`
4932
+ );
4933
+ return;
4934
+ }
4935
+ if (versionOrder > 0) {
4936
+ pluginLog.info(
4937
+ `[opencode-immune] Harness sync: local ${localVersion} is newer than release ${release.tagName}. Skipping sync.`
4938
+ );
4939
+ return;
4940
+ }
4941
+ }
4897
4942
  pluginLog.info(
4898
4943
  `[opencode-immune] Harness sync: updating ${localVersion ?? "(none)"} \u2192 ${release.tagName}`
4899
4944
  );
@@ -5479,8 +5524,9 @@ function createTextCompleteHandler(state) {
5479
5524
  const sessionID = input.sessionID;
5480
5525
  const text = output.text ?? "";
5481
5526
  if (!text) return;
5527
+ const isManagedRootSession = isManagedRootUltraworkSession(state, sessionID);
5482
5528
  cancelProviderRetryWatchdog(state, sessionID, "assistant text completed");
5483
- if (isProviderRetryBanner(text) && isManagedRootUltraworkSession(state, sessionID)) {
5529
+ if (isProviderRetryBanner(text) && isManagedRootSession) {
5484
5530
  const fallbackModel = getSessionFallbackModel(state, sessionID);
5485
5531
  await setSessionFallbackModel(state, sessionID, fallbackModel);
5486
5532
  scheduleManagedSessionRetry(state, sessionID, {
@@ -5562,6 +5608,40 @@ function createTextCompleteHandler(state) {
5562
5608
  } catch (err) {
5563
5609
  pluginLog.error("[opencode-immune] Multi-Cycle: Failed to create session or send prompt:", err);
5564
5610
  }
5611
+ return;
5612
+ }
5613
+ if (!isManagedRootSession || state.autoCycleInFlight) return;
5614
+ const recovery = await parseTasksFile(state.input.directory);
5615
+ if (recovery) return;
5616
+ const hasPendingTasks = await hasPendingBacklogTasks(state.input.directory);
5617
+ if (!hasPendingTasks) return;
5618
+ if (state.autoCycleSourceSessions.has(sessionID)) return;
5619
+ state.autoCycleSourceSessions.add(sessionID);
5620
+ state.autoCycleInFlight = true;
5621
+ pluginLog.warn(
5622
+ `[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.`
5623
+ );
5624
+ try {
5625
+ const newSessionID = await createManagedUltraworkSession(
5626
+ state,
5627
+ `AUTO-CYCLE: next backlog task`
5628
+ );
5629
+ if (!newSessionID) {
5630
+ pluginLog.error("[opencode-immune] Multi-Cycle fallback: Failed to create new session \u2014 no ID returned.");
5631
+ return;
5632
+ }
5633
+ pluginLog.info(`[opencode-immune] Multi-Cycle fallback: New session created: ${newSessionID}`);
5634
+ await promptManagedSession(
5635
+ state,
5636
+ newSessionID,
5637
+ `[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.`
5638
+ );
5639
+ pluginLog.info(`[opencode-immune] Multi-Cycle fallback: Bootstrap prompt sent to ${newSessionID}`);
5640
+ } catch (err) {
5641
+ state.autoCycleSourceSessions.delete(sessionID);
5642
+ pluginLog.error("[opencode-immune] Multi-Cycle fallback: Failed to create session or send prompt:", err);
5643
+ } finally {
5644
+ state.autoCycleInFlight = false;
5565
5645
  }
5566
5646
  };
5567
5647
  }
@@ -5675,9 +5755,7 @@ async function server(input) {
5675
5755
  }, 5e3);
5676
5756
  } else {
5677
5757
  try {
5678
- const backlogPath = join(state.input.directory, "memory-bank", "backlog.md");
5679
- const backlogContent = await readFile(backlogPath, "utf-8");
5680
- const hasPendingTasks = /- \[ \]/.test(backlogContent);
5758
+ const hasPendingTasks = await hasPendingBacklogTasks(state.input.directory);
5681
5759
  if (hasPendingTasks) {
5682
5760
  pluginLog.info(
5683
5761
  `[opencode-immune] Plugin init: no active task but backlog has pending items. Will create new session to start next cycle.`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-immune",
3
- "version": "1.0.64",
3
+ "version": "1.0.66",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin: session recovery, auto-retry, multi-cycle automation, context monitoring",
6
6
  "exports": {