opencode-immune 1.0.79 → 1.0.80

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.79";
3780
+ var PLUGIN_VERSION = "1.0.80";
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,7 @@ 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;
3898
3900
  var MODEL_NAME_CAPABILITY_SCORE = {
3899
3901
  "claude-opus-4-7": 100,
3900
3902
  "gpt-5.5": 100,
@@ -3914,6 +3916,13 @@ function isManagedRootUltraworkSession(state, sessionID) {
3914
3916
  const record = getManagedSession(state, sessionID);
3915
3917
  return !!record && record.kind === "root";
3916
3918
  }
3919
+ function hasRecentManagedChildSessionForRoot(state, rootSessionID, sinceMs) {
3920
+ const cutoff = Date.now() - sinceMs;
3921
+ for (const record of state.managedUltraworkSessions.values()) {
3922
+ if (record.kind === "child" && record.rootSessionID === rootSessionID && record.updatedAt >= cutoff) return true;
3923
+ }
3924
+ return false;
3925
+ }
3917
3926
  async function createManagedUltraworkSession(state, title) {
3918
3927
  const result = await state.client.session.create({
3919
3928
  directory: state.input.directory,
@@ -4110,6 +4119,11 @@ async function addManagedUltraworkSession(state, sessionID, timestamp = Date.now
4110
4119
  async function addManagedChildSession(state, sessionID, parentSessionID, timestamp = Date.now()) {
4111
4120
  const parent = state.managedUltraworkSessions.get(parentSessionID);
4112
4121
  if (!parent) return;
4122
+ cancelFallbackAutoCycle(
4123
+ state,
4124
+ parent.rootSessionID,
4125
+ `child session ${sessionID} created`
4126
+ );
4113
4127
  cancelProviderRetryWatchdog(
4114
4128
  state,
4115
4129
  parent.rootSessionID,
@@ -4155,8 +4169,13 @@ function cancelProviderRetryWatchdog(state, sessionID, reason) {
4155
4169
  );
4156
4170
  }
4157
4171
  async function removeManagedUltraworkSession(state, sessionID, reason) {
4172
+ const existing = state.managedUltraworkSessions.get(sessionID);
4158
4173
  cancelPendingSessionRetry(state, sessionID, reason);
4159
4174
  cancelProviderRetryWatchdog(state, sessionID, reason);
4175
+ cancelFallbackAutoCycle(state, sessionID, reason);
4176
+ if (existing?.kind === "child") {
4177
+ cancelFallbackAutoCycle(state, existing.rootSessionID, `child session ${sessionID} ${reason}`);
4178
+ }
4160
4179
  state.sessionErrorRetryCount.delete(sessionID);
4161
4180
  const existed = state.managedUltraworkSessions.delete(sessionID);
4162
4181
  if (!existed) return;
@@ -5760,7 +5779,15 @@ function createEventHandler(state) {
5760
5779
  state.sessionErrorRetryCount.delete(sessionID);
5761
5780
  if (markUltraworkSessionActive(state, sessionID)) {
5762
5781
  }
5782
+ if (managedSession?.kind === "root") {
5783
+ cancelFallbackAutoCycle(state, sessionID, "root session updated");
5784
+ }
5763
5785
  if (managedSession?.kind === "child") {
5786
+ cancelFallbackAutoCycle(
5787
+ state,
5788
+ managedSession.rootSessionID,
5789
+ `child session ${sessionID} updated`
5790
+ );
5764
5791
  cancelProviderRetryWatchdog(
5765
5792
  state,
5766
5793
  managedSession.rootSessionID,
@@ -5802,6 +5829,64 @@ async function commitCycleChanges(state, reason) {
5802
5829
  state.commitPending = false;
5803
5830
  }
5804
5831
  }
5832
+ function cancelFallbackAutoCycle(state, sessionID, reason) {
5833
+ const timer = state.autoCycleFallbackTimers.get(sessionID);
5834
+ if (!timer) return;
5835
+ clearTimeout(timer);
5836
+ state.autoCycleFallbackTimers.delete(sessionID);
5837
+ pluginLog.info(
5838
+ `[opencode-immune] Cancelled fallback AUTO-CYCLE for session ${sessionID}: ${reason}`
5839
+ );
5840
+ }
5841
+ function scheduleFallbackAutoCycle(state, sessionID) {
5842
+ if (state.autoCycleFallbackTimers.has(sessionID)) return;
5843
+ const timer = setTimeout(async () => {
5844
+ state.autoCycleFallbackTimers.delete(sessionID);
5845
+ if (!isManagedRootUltraworkSession(state, sessionID)) return;
5846
+ if (state.autoCycleInFlight || state.autoCycleSourceSessions.has(sessionID)) return;
5847
+ if (hasRecentManagedChildSessionForRoot(state, sessionID, FALLBACK_AUTO_CYCLE_DELAY_MS)) {
5848
+ scheduleFallbackAutoCycle(state, sessionID);
5849
+ return;
5850
+ }
5851
+ const recovery = await parseTasksFile(state.input.directory);
5852
+ if (recovery) return;
5853
+ const hasPendingTasks = await hasPendingBacklogTasks(state.input.directory);
5854
+ if (!hasPendingTasks) return;
5855
+ const lockAcquired = await acquireAutoCycleLock(
5856
+ state,
5857
+ "multi-cycle-fallback",
5858
+ sessionID
5859
+ );
5860
+ if (!lockAcquired) return;
5861
+ state.autoCycleSourceSessions.add(sessionID);
5862
+ state.autoCycleInFlight = true;
5863
+ pluginLog.warn(
5864
+ `[opencode-immune] Multi-Cycle fallback: no CYCLE_COMPLETE marker detected for ${sessionID} after ${FALLBACK_AUTO_CYCLE_DELAY_MS}ms grace period. Starting AUTO-CYCLE.`
5865
+ );
5866
+ try {
5867
+ await commitCycleChanges(state, "fallback AUTO-CYCLE before new session");
5868
+ await refreshAutoCycleLock(state, sessionID);
5869
+ await startAutoCycleInNewSession(
5870
+ state,
5871
+ {
5872
+ sourceSessionID: sessionID,
5873
+ logContext: "Multi-Cycle fallback",
5874
+ clearLockOnFailureReason: "fallback bootstrap failed",
5875
+ retireSourceSession: true
5876
+ }
5877
+ );
5878
+ } catch (err) {
5879
+ state.autoCycleSourceSessions.delete(sessionID);
5880
+ pluginLog.error("[opencode-immune] Multi-Cycle fallback: Failed to send prompt:", err);
5881
+ } finally {
5882
+ state.autoCycleInFlight = false;
5883
+ }
5884
+ }, FALLBACK_AUTO_CYCLE_DELAY_MS);
5885
+ state.autoCycleFallbackTimers.set(sessionID, timer);
5886
+ pluginLog.info(
5887
+ `[opencode-immune] Multi-Cycle fallback: scheduled delayed AUTO-CYCLE check for ${sessionID}.`
5888
+ );
5889
+ }
5805
5890
  async function archiveProgress(directory) {
5806
5891
  const progressPath = join(directory, "memory-bank", "progress.md");
5807
5892
  try {
@@ -5903,23 +5988,26 @@ function createTextCompleteHandler(state) {
5903
5988
  );
5904
5989
  }
5905
5990
  if (text.includes(ALL_CYCLES_COMPLETE_MARKER)) {
5991
+ cancelFallbackAutoCycle(state, sessionID, "ALL_CYCLES_COMPLETE detected");
5906
5992
  await clearUltraworkMarker(state);
5907
5993
  await clearAutoCycleLock(state, "all cycles complete");
5908
5994
  pluginLog.info("[opencode-immune] Multi-Cycle: ALL_CYCLES_COMPLETE detected, marker cleared.");
5909
5995
  return;
5910
5996
  }
5911
5997
  if (text.includes(PRE_COMMIT_MARKER) && !text.includes(CYCLE_COMPLETE_MARKER)) {
5998
+ cancelFallbackAutoCycle(state, sessionID, "PRE_COMMIT detected");
5912
5999
  await commitCycleChanges(state, "PRE_COMMIT detected (standalone)");
5913
6000
  return;
5914
6001
  }
5915
6002
  if (text.includes(CYCLE_COMPLETE_MARKER)) {
6003
+ cancelFallbackAutoCycle(state, sessionID, "CYCLE_COMPLETE detected");
5916
6004
  if (state.autoCycleInFlight || state.autoCycleSourceSessions.has(sessionID)) return;
5917
- const lockAcquired2 = await acquireAutoCycleLock(
6005
+ const lockAcquired = await acquireAutoCycleLock(
5918
6006
  state,
5919
6007
  "cycle-complete",
5920
6008
  sessionID
5921
6009
  );
5922
- if (!lockAcquired2) return;
6010
+ if (!lockAcquired) return;
5923
6011
  state.autoCycleSourceSessions.add(sessionID);
5924
6012
  state.autoCycleInFlight = true;
5925
6013
  try {
@@ -5966,36 +6054,7 @@ function createTextCompleteHandler(state) {
5966
6054
  if (recovery) return;
5967
6055
  const hasPendingTasks = await hasPendingBacklogTasks(state.input.directory);
5968
6056
  if (!hasPendingTasks) return;
5969
- if (state.autoCycleSourceSessions.has(sessionID)) return;
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
- }
6057
+ scheduleFallbackAutoCycle(state, sessionID);
5999
6058
  };
6000
6059
  }
6001
6060
  function createMultiCycleHandler(state) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-immune",
3
- "version": "1.0.79",
3
+ "version": "1.0.80",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin: session recovery, auto-retry, multi-cycle automation, context monitoring",
6
6
  "exports": {