opencode-immune 1.0.59 → 1.0.61

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.
Files changed (2) hide show
  1. package/dist/plugin.js +56 -7
  2. package/package.json +1 -1
package/dist/plugin.js CHANGED
@@ -96,6 +96,7 @@ function createState(input) {
96
96
  providerRetryWatchdogs: new Map(),
97
97
  childFallbackRequests: new Map(),
98
98
  sessionErrorRetryCount: new Map(),
99
+ ultraworkPermissionSessions: new Set(),
99
100
  fallbackAgentByAgent: new Map(),
100
101
  baseAgentByFallbackAgent: new Map(),
101
102
  ultraworkMarkerPath: join(input.directory, ".opencode", "state", "ultrawork-active.json"),
@@ -106,6 +107,7 @@ function createState(input) {
106
107
  approximateTokens: 0,
107
108
  sessionActive: false,
108
109
  autoResumeAttempted: false,
110
+ autoResumeInFlight: false,
109
111
  cycleCount: 0,
110
112
  commitPending: false,
111
113
  pluginUpdateMessage: null,
@@ -174,6 +176,25 @@ async function createManagedUltraworkSession(state, title) {
174
176
  await addManagedUltraworkSession(state, sessionID);
175
177
  return sessionID;
176
178
  }
179
+ async function applyUltraworkSessionPermissions(state, sessionID) {
180
+ if (state.ultraworkPermissionSessions.has(sessionID))
181
+ return;
182
+ try {
183
+ const result = await state.client.session.update({
184
+ directory: state.input.directory,
185
+ sessionID,
186
+ permission: ULTRAWORK_SESSION_PERMISSION,
187
+ });
188
+ if (result.error || !result.response.ok) {
189
+ pluginLog.warn(`[opencode-immune] Failed to apply ultrawork permissions to session ${sessionID}:`, result.error ?? result.response.status);
190
+ return;
191
+ }
192
+ state.ultraworkPermissionSessions.add(sessionID);
193
+ }
194
+ catch (err) {
195
+ pluginLog.warn(`[opencode-immune] Failed to apply ultrawork permissions to session ${sessionID}:`, err);
196
+ }
197
+ }
177
198
  async function promptManagedSession(state, sessionID, text, options = {}) {
178
199
  const result = await state.client.session.promptAsync({
179
200
  directory: state.input.directory,
@@ -336,6 +357,7 @@ async function addManagedUltraworkSession(state, sessionID, timestamp = Date.now
336
357
  return;
337
358
  }
338
359
  state.managedUltraworkSessions.set(sessionID, nextRecord);
360
+ await applyUltraworkSessionPermissions(state, sessionID);
339
361
  }
340
362
  async function addManagedChildSession(state, sessionID, parentSessionID, timestamp = Date.now()) {
341
363
  const parent = state.managedUltraworkSessions.get(parentSessionID);
@@ -379,6 +401,7 @@ async function removeManagedUltraworkSession(state, sessionID, reason) {
379
401
  cancelPendingSessionRetry(state, sessionID, reason);
380
402
  cancelProviderRetryWatchdog(state, sessionID, reason);
381
403
  state.sessionErrorRetryCount.delete(sessionID);
404
+ state.ultraworkPermissionSessions.delete(sessionID);
382
405
  const existed = state.managedUltraworkSessions.delete(sessionID);
383
406
  if (!existed)
384
407
  return;
@@ -1377,18 +1400,23 @@ function createSessionRecoveryEvent(state) {
1377
1400
  // Register this root session as managed so retry/recovery works
1378
1401
  await addManagedUltraworkSession(state, sessionID);
1379
1402
  // Skip sending AUTO-RESUME if already sent from plugin init
1380
- if (state.autoResumeAttempted) {
1381
- pluginLog.info(`[opencode-immune] Auto-resume already sent from plugin init, skipping duplicate for session ${sessionID}.`);
1403
+ if (state.autoResumeAttempted || state.autoResumeInFlight) {
1404
+ pluginLog.info(`[opencode-immune] Auto-resume already sent or in flight, skipping duplicate for session ${sessionID}.`);
1382
1405
  return;
1383
1406
  }
1407
+ state.autoResumeInFlight = true;
1384
1408
  setTimeout(async () => {
1385
1409
  try {
1386
1410
  await promptManagedSession(state, sessionID, `[AUTO-RESUME] Previous session was interrupted. Read memory-bank/tasks.md, check the Phase Status block, and continue the pipeline. Use ONLY the Phase Status block to determine the next phase. Do NOT analyze or evaluate the content of tasks.md. Call the appropriate router with the exact neutral prompt from your Step 5 table.`);
1411
+ state.autoResumeAttempted = true;
1387
1412
  pluginLog.info(`[opencode-immune] Auto-resume prompt sent to managed ultrawork session ${sessionID}`);
1388
1413
  }
1389
1414
  catch (err) {
1390
1415
  pluginLog.info(`[opencode-immune] Auto-resume failed (session may have been taken over):`, err);
1391
1416
  }
1417
+ finally {
1418
+ state.autoResumeInFlight = false;
1419
+ }
1392
1420
  }, 3_000);
1393
1421
  }
1394
1422
  }
@@ -1614,8 +1642,7 @@ function createFallbackModels(state) {
1614
1642
  scheduleProviderRetryWatchdog(state, input.sessionID);
1615
1643
  // First contact with 0-ultrawork after plugin restart:
1616
1644
  // if marker is active and tasks.md has incomplete work, send AUTO-RESUME prompt.
1617
- if (!wasAlreadyManaged && !state.autoResumeAttempted) {
1618
- state.autoResumeAttempted = true;
1645
+ if (!wasAlreadyManaged && !state.autoResumeAttempted && !state.autoResumeInFlight) {
1619
1646
  const markerActive = await isUltraworkMarkerActive(state);
1620
1647
  if (markerActive) {
1621
1648
  const recovery = await parseTasksFile(state.input.directory);
@@ -1625,14 +1652,19 @@ function createFallbackModels(state) {
1625
1652
  `task="${recovery.task}", level=${recovery.level}, phase=${recovery.phase}. ` +
1626
1653
  `Sending AUTO-RESUME prompt in 3s...`);
1627
1654
  const sid = input.sessionID;
1655
+ state.autoResumeInFlight = true;
1628
1656
  setTimeout(async () => {
1629
1657
  try {
1630
1658
  await promptManagedSession(state, sid, `[AUTO-RESUME] Previous session was interrupted. Read memory-bank/tasks.md, check the Phase Status block, and continue the pipeline. Use ONLY the Phase Status block to determine the next phase. Do NOT analyze or evaluate the content of tasks.md. Call the appropriate router with the exact neutral prompt from your Step 5 table.`);
1659
+ state.autoResumeAttempted = true;
1631
1660
  pluginLog.info(`[opencode-immune] Auto-resume prompt sent to session ${sid}`);
1632
1661
  }
1633
1662
  catch (err) {
1634
1663
  pluginLog.info(`[opencode-immune] Auto-resume prompt failed:`, err);
1635
1664
  }
1665
+ finally {
1666
+ state.autoResumeInFlight = false;
1667
+ }
1636
1668
  }, 3_000);
1637
1669
  }
1638
1670
  }
@@ -2066,8 +2098,17 @@ function createMultiCycleHandler(state) {
2066
2098
  function createPermissionAskHandler(state) {
2067
2099
  return async (input, output) => {
2068
2100
  const sessionID = input.sessionID;
2069
- if (!isManagedUltraworkSession(state, sessionID))
2101
+ if (!sessionID)
2070
2102
  return;
2103
+ if (!isManagedUltraworkSession(state, sessionID)) {
2104
+ const markerActive = await isUltraworkMarkerActive(state);
2105
+ const recovery = markerActive ? await parseTasksFile(state.input.directory) : null;
2106
+ if (!recovery || recovery.phase === "ARCHIVE: DONE")
2107
+ return;
2108
+ state.recoveryContext = recovery;
2109
+ await addManagedUltraworkSession(state, sessionID);
2110
+ pluginLog.info(`[opencode-immune] Permission request recovered AUTO-RESUME session ${sessionID}; applying managed ultrawork auto-allow.`);
2111
+ }
2071
2112
  output.status = "allow";
2072
2113
  await writeDiagnosticLog(state, "permission:auto-allow", {
2073
2114
  sessionID,
@@ -2097,12 +2138,12 @@ async function server(input) {
2097
2138
  if (recovery && recovery.phase !== "ARCHIVE: DONE") {
2098
2139
  // Active task exists with incomplete phases — resume it
2099
2140
  state.recoveryContext = recovery;
2100
- state.autoResumeAttempted = true;
2101
2141
  pluginLog.info(`[opencode-immune] Plugin init: ultrawork marker active, recovery context loaded: ` +
2102
2142
  `task="${recovery.task}", level=${recovery.level}, phase=${recovery.phase}. ` +
2103
2143
  `Will create new session and send AUTO-RESUME.`);
2104
2144
  // Create a new session and send AUTO-RESUME prompt (same pattern as CYCLE_COMPLETE).
2105
2145
  // Delay to let opencode fully initialize.
2146
+ state.autoResumeInFlight = true;
2106
2147
  setTimeout(async () => {
2107
2148
  try {
2108
2149
  const newSessionID = await createManagedUltraworkSession(state, `AUTO-RESUME: ${recovery.task}`);
@@ -2112,11 +2153,15 @@ async function server(input) {
2112
2153
  }
2113
2154
  pluginLog.info(`[opencode-immune] Auto-resume: New session created: ${newSessionID}`);
2114
2155
  await promptManagedSession(state, newSessionID, `[AUTO-RESUME] Previous session was interrupted. Read memory-bank/tasks.md, check the Phase Status block, and continue the pipeline. Use ONLY the Phase Status block to determine the next phase. Do NOT analyze or evaluate the content of tasks.md. Call the appropriate router with the exact neutral prompt from your Step 5 table.`);
2156
+ state.autoResumeAttempted = true;
2115
2157
  pluginLog.info(`[opencode-immune] Auto-resume prompt sent to new session ${newSessionID}`);
2116
2158
  }
2117
2159
  catch (err) {
2118
2160
  pluginLog.error("[opencode-immune] Auto-resume: Failed to create session or send prompt:", err);
2119
2161
  }
2162
+ finally {
2163
+ state.autoResumeInFlight = false;
2164
+ }
2120
2165
  }, 5_000);
2121
2166
  }
2122
2167
  else {
@@ -2126,9 +2171,9 @@ async function server(input) {
2126
2171
  const backlogContent = await readFile(backlogPath, "utf-8");
2127
2172
  const hasPendingTasks = /- \[ \]/.test(backlogContent);
2128
2173
  if (hasPendingTasks) {
2129
- state.autoResumeAttempted = true;
2130
2174
  pluginLog.info(`[opencode-immune] Plugin init: no active task but backlog has pending items. ` +
2131
2175
  `Will create new session to start next cycle.`);
2176
+ state.autoResumeInFlight = true;
2132
2177
  setTimeout(async () => {
2133
2178
  try {
2134
2179
  const newSessionID = await createManagedUltraworkSession(state, `AUTO-CYCLE: next backlog task`);
@@ -2138,11 +2183,15 @@ async function server(input) {
2138
2183
  }
2139
2184
  pluginLog.info(`[opencode-immune] Auto-cycle: New session created: ${newSessionID}`);
2140
2185
  await promptManagedSession(state, newSessionID, `[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.`);
2186
+ state.autoResumeAttempted = true;
2141
2187
  pluginLog.info(`[opencode-immune] Auto-cycle prompt sent to new session ${newSessionID}`);
2142
2188
  }
2143
2189
  catch (err) {
2144
2190
  pluginLog.error("[opencode-immune] Auto-cycle: Failed to create session or send prompt:", err);
2145
2191
  }
2192
+ finally {
2193
+ state.autoResumeInFlight = false;
2194
+ }
2146
2195
  }, 5_000);
2147
2196
  }
2148
2197
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-immune",
3
- "version": "1.0.59",
3
+ "version": "1.0.61",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin: session recovery, auto-retry, multi-cycle automation, context monitoring",
6
6
  "exports": {