opencode-immune 1.0.46 → 1.0.48

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 +85 -101
  2. package/package.json +3 -2
package/dist/plugin.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // Hybrid single-file architecture with factory functions, explicit state, error boundaries
4
4
  // See: memory-bank/creative/creative-plugin-architecture.md (Option C)
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const client_1 = require("@opencode-ai/sdk/v2/client");
6
7
  const promises_1 = require("fs/promises");
7
8
  const path_1 = require("path");
8
9
  const crypto_1 = require("crypto");
@@ -58,7 +59,9 @@ async function checkPluginUpdate(state) {
58
59
  if (latest && latest !== currentVersion) {
59
60
  state.pluginUpdateMessage =
60
61
  `[PLUGIN UPDATE] opencode-immune ${currentVersion} → ${latest} is available. ` +
61
- `Please inform the user: a plugin update is available. They should restart opencode to get the latest version.`;
62
+ `Please inform the user: run \`opencode plugin opencode-immune@latest --force\`, ` +
63
+ `then restart opencode. If opencode still loads the old package, clear its package cache with ` +
64
+ `\`rm -rf ~/.cache/opencode/packages/opencode-immune@latest\` and run the update command again.`;
62
65
  writePluginLog(state, "warn", `[opencode-immune] Plugin update available: ${currentVersion} → ${latest}.`);
63
66
  }
64
67
  else if (latest) {
@@ -71,8 +74,13 @@ async function checkPluginUpdate(state) {
71
74
  }
72
75
  function createState(input) {
73
76
  activeLogDirectory = input.directory;
77
+ const { client: _client, ...runtimeInput } = input;
74
78
  return {
75
- input,
79
+ input: runtimeInput,
80
+ client: (0, client_1.createOpencodeClient)({
81
+ baseUrl: input.serverUrl.toString(),
82
+ directory: input.directory,
83
+ }),
76
84
  recoveryContext: null,
77
85
  managedUltraworkSessions: new Map(),
78
86
  sessionRetryTimers: new Map(),
@@ -93,6 +101,23 @@ function createState(input) {
93
101
  };
94
102
  }
95
103
  const ULTRAWORK_AGENT = "0-ultrawork";
104
+ const ULTRAWORK_SESSION_PERMISSION = [
105
+ { permission: "read", pattern: "*", action: "allow" },
106
+ { permission: "edit", pattern: "*", action: "allow" },
107
+ { permission: "glob", pattern: "*", action: "allow" },
108
+ { permission: "grep", pattern: "*", action: "allow" },
109
+ { permission: "list", pattern: "*", action: "allow" },
110
+ { permission: "bash", pattern: "*", action: "allow" },
111
+ { permission: "task", pattern: "*", action: "allow" },
112
+ { permission: "external_directory", pattern: "*", action: "allow" },
113
+ { permission: "todowrite", pattern: "*", action: "allow" },
114
+ { permission: "question", pattern: "*", action: "allow" },
115
+ { permission: "webfetch", pattern: "*", action: "allow" },
116
+ { permission: "websearch", pattern: "*", action: "allow" },
117
+ { permission: "codesearch", pattern: "*", action: "allow" },
118
+ { permission: "lsp", pattern: "*", action: "allow" },
119
+ { permission: "skill", pattern: "*", action: "allow" },
120
+ ];
96
121
  const DIAGNOSTIC_LOG_MAX_BYTES = 5 * 1024 * 1024;
97
122
  let activeLogDirectory = null;
98
123
  const MANAGED_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
@@ -116,6 +141,38 @@ function isManagedRootUltraworkSession(state, sessionID) {
116
141
  const record = getManagedSession(state, sessionID);
117
142
  return !!record && record.kind === "root";
118
143
  }
144
+ async function createManagedUltraworkSession(state, title) {
145
+ const result = await state.client.session.create({
146
+ directory: state.input.directory,
147
+ title,
148
+ permission: ULTRAWORK_SESSION_PERMISSION,
149
+ });
150
+ const sessionID = result?.data?.id;
151
+ if (!sessionID)
152
+ return null;
153
+ await addManagedUltraworkSession(state, sessionID);
154
+ return sessionID;
155
+ }
156
+ async function promptManagedSession(state, sessionID, text, options = {}) {
157
+ await state.client.session.promptAsync({
158
+ directory: state.input.directory,
159
+ sessionID,
160
+ ...(options.model ? { model: options.model } : {}),
161
+ agent: options.agent ?? ULTRAWORK_AGENT,
162
+ parts: [
163
+ {
164
+ type: "text",
165
+ text,
166
+ },
167
+ ],
168
+ });
169
+ }
170
+ async function abortManagedSession(state, sessionID) {
171
+ await state.client.session.abort({
172
+ directory: state.input.directory,
173
+ sessionID,
174
+ });
175
+ }
119
176
  function pruneExpiredManagedSessions(state, now = Date.now()) {
120
177
  let removed = 0;
121
178
  for (const [sessionID, record] of state.managedUltraworkSessions.entries()) {
@@ -534,9 +591,7 @@ async function sendManagedSessionRetryPrompt(state, sessionID, reason, options =
534
591
  });
535
592
  if (options.abortBeforePrompt) {
536
593
  try {
537
- await state.input.client.session.abort({
538
- path: { id: sessionID },
539
- });
594
+ await abortManagedSession(state, sessionID);
540
595
  await writeDiagnosticLog(state, "session-retry:abort-success", { sessionID });
541
596
  }
542
597
  catch (err) {
@@ -546,18 +601,9 @@ async function sendManagedSessionRetryPrompt(state, sessionID, reason, options =
546
601
  });
547
602
  }
548
603
  }
549
- await state.input.client.session.promptAsync({
550
- body: {
551
- ...(fallbackModel ? { model: fallbackModel } : {}),
552
- agent: retryAgent,
553
- parts: [
554
- {
555
- type: "text",
556
- text: retryText,
557
- },
558
- ],
559
- },
560
- path: { id: sessionID },
604
+ await promptManagedSession(state, sessionID, retryText, {
605
+ agent: retryAgent,
606
+ model: fallbackModel,
561
607
  });
562
608
  writePluginLog(state, "info", `[opencode-immune] Retry prompt sent to session ${sessionID} (${reason})` +
563
609
  (fallbackModel
@@ -1096,18 +1142,7 @@ function createSessionRecoveryEvent(state) {
1096
1142
  }
1097
1143
  setTimeout(async () => {
1098
1144
  try {
1099
- await state.input.client.session.promptAsync({
1100
- body: {
1101
- agent: ULTRAWORK_AGENT,
1102
- parts: [
1103
- {
1104
- type: "text",
1105
- text: `[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.`,
1106
- },
1107
- ],
1108
- },
1109
- path: { id: sessionID },
1110
- });
1145
+ 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.`);
1111
1146
  pluginLog.info(`[opencode-immune] Auto-resume prompt sent to managed ultrawork session ${sessionID}`);
1112
1147
  }
1113
1148
  catch (err) {
@@ -1350,18 +1385,7 @@ function createFallbackModels(state) {
1350
1385
  const sid = input.sessionID;
1351
1386
  setTimeout(async () => {
1352
1387
  try {
1353
- await state.input.client.session.promptAsync({
1354
- body: {
1355
- agent: ULTRAWORK_AGENT,
1356
- parts: [
1357
- {
1358
- type: "text",
1359
- text: `[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.`,
1360
- },
1361
- ],
1362
- },
1363
- path: { id: sid },
1364
- });
1388
+ 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.`);
1365
1389
  pluginLog.info(`[opencode-immune] Auto-resume prompt sent to session ${sid}`);
1366
1390
  }
1367
1391
  catch (err) {
@@ -1714,31 +1738,13 @@ function createTextCompleteHandler(state) {
1714
1738
  const nextTask = taskMatch?.[1]?.trim() ?? "Continue processing task backlog";
1715
1739
  pluginLog.info(`[opencode-immune] Multi-Cycle: Creating new session (cycle ${state.cycleCount}/${MAX_CYCLES}) for: "${nextTask}"`);
1716
1740
  try {
1717
- const createResult = await state.input.client.session.create({
1718
- body: {
1719
- title: `Ultrawork Cycle ${state.cycleCount + 1}`,
1720
- },
1721
- });
1722
- const newSessionData = createResult?.data;
1723
- const newSessionID = newSessionData?.id;
1741
+ const newSessionID = await createManagedUltraworkSession(state, `Ultrawork Cycle ${state.cycleCount + 1}`);
1724
1742
  if (!newSessionID) {
1725
1743
  pluginLog.error("[opencode-immune] Multi-Cycle: Failed to create new session — no ID returned.");
1726
1744
  return;
1727
1745
  }
1728
1746
  pluginLog.info(`[opencode-immune] Multi-Cycle: New session created: ${newSessionID}`);
1729
- await addManagedUltraworkSession(state, newSessionID);
1730
- await state.input.client.session.promptAsync({
1731
- body: {
1732
- agent: ULTRAWORK_AGENT,
1733
- parts: [
1734
- {
1735
- type: "text",
1736
- text: `[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.`,
1737
- },
1738
- ],
1739
- },
1740
- path: { id: newSessionID },
1741
- });
1747
+ 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.`);
1742
1748
  pluginLog.info(`[opencode-immune] Multi-Cycle: Bootstrap prompt sent to ${newSessionID}`);
1743
1749
  }
1744
1750
  catch (err) {
@@ -1789,6 +1795,19 @@ function createMultiCycleHandler(state) {
1789
1795
  }
1790
1796
  };
1791
1797
  }
1798
+ function createPermissionAskHandler(state) {
1799
+ return async (input, output) => {
1800
+ const sessionID = input.sessionID;
1801
+ if (!isManagedUltraworkSession(state, sessionID))
1802
+ return;
1803
+ output.status = "allow";
1804
+ await writeDiagnosticLog(state, "permission:auto-allow", {
1805
+ sessionID,
1806
+ permission: input.permission,
1807
+ patterns: input.patterns,
1808
+ });
1809
+ };
1810
+ }
1792
1811
  // ═══════════════════════════════════════════════════════════════════════════════
1793
1812
  // PLUGIN MODULE EXPORT
1794
1813
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -1818,31 +1837,13 @@ async function server(input) {
1818
1837
  // Delay to let opencode fully initialize.
1819
1838
  setTimeout(async () => {
1820
1839
  try {
1821
- const createResult = await state.input.client.session.create({
1822
- body: {
1823
- title: `AUTO-RESUME: ${recovery.task}`,
1824
- },
1825
- });
1826
- const newSessionData = createResult?.data;
1827
- const newSessionID = newSessionData?.id;
1840
+ const newSessionID = await createManagedUltraworkSession(state, `AUTO-RESUME: ${recovery.task}`);
1828
1841
  if (!newSessionID) {
1829
1842
  pluginLog.error("[opencode-immune] Auto-resume: Failed to create session — no session ID returned.");
1830
1843
  return;
1831
1844
  }
1832
1845
  pluginLog.info(`[opencode-immune] Auto-resume: New session created: ${newSessionID}`);
1833
- await addManagedUltraworkSession(state, newSessionID);
1834
- await state.input.client.session.promptAsync({
1835
- body: {
1836
- agent: ULTRAWORK_AGENT,
1837
- parts: [
1838
- {
1839
- type: "text",
1840
- text: `[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.`,
1841
- },
1842
- ],
1843
- },
1844
- path: { id: newSessionID },
1845
- });
1846
+ 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.`);
1846
1847
  pluginLog.info(`[opencode-immune] Auto-resume prompt sent to new session ${newSessionID}`);
1847
1848
  }
1848
1849
  catch (err) {
@@ -1862,31 +1863,13 @@ async function server(input) {
1862
1863
  `Will create new session to start next cycle.`);
1863
1864
  setTimeout(async () => {
1864
1865
  try {
1865
- const createResult = await state.input.client.session.create({
1866
- body: {
1867
- title: `AUTO-CYCLE: next backlog task`,
1868
- },
1869
- });
1870
- const newSessionData = createResult?.data;
1871
- const newSessionID = newSessionData?.id;
1866
+ const newSessionID = await createManagedUltraworkSession(state, `AUTO-CYCLE: next backlog task`);
1872
1867
  if (!newSessionID) {
1873
1868
  pluginLog.error("[opencode-immune] Auto-cycle: Failed to create session — no session ID returned.");
1874
1869
  return;
1875
1870
  }
1876
1871
  pluginLog.info(`[opencode-immune] Auto-cycle: New session created: ${newSessionID}`);
1877
- await addManagedUltraworkSession(state, newSessionID);
1878
- await state.input.client.session.promptAsync({
1879
- body: {
1880
- agent: ULTRAWORK_AGENT,
1881
- parts: [
1882
- {
1883
- type: "text",
1884
- text: `[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.`,
1885
- },
1886
- ],
1887
- },
1888
- path: { id: newSessionID },
1889
- });
1872
+ 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.`);
1890
1873
  pluginLog.info(`[opencode-immune] Auto-cycle prompt sent to new session ${newSessionID}`);
1891
1874
  }
1892
1875
  catch (err) {
@@ -1931,6 +1914,7 @@ async function server(input) {
1931
1914
  "experimental.chat.system.transform": withErrorBoundary(state, "experimental.chat.system.transform", createSystemTransform(state)),
1932
1915
  "experimental.session.compacting": withErrorBoundary(state, "experimental.session.compacting", createCompactionHandler(state)),
1933
1916
  "experimental.text.complete": withErrorBoundary(state, "experimental.text.complete", createTextCompleteHandler(state)),
1917
+ "permission.ask": withErrorBoundary(state, "permission.ask", createPermissionAskHandler(state)),
1934
1918
  };
1935
1919
  }
1936
1920
  exports.default = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-immune",
3
- "version": "1.0.46",
3
+ "version": "1.0.48",
4
4
  "description": "OpenCode plugin: session recovery, auto-retry, multi-cycle automation, context monitoring",
5
5
  "exports": {
6
6
  "./server": "./dist/plugin.js"
@@ -14,7 +14,8 @@
14
14
  "prepublishOnly": "npm run build"
15
15
  },
16
16
  "dependencies": {
17
- "@opencode-ai/plugin": "1.4.7"
17
+ "@opencode-ai/plugin": "1.14.25",
18
+ "@opencode-ai/sdk": "1.14.25"
18
19
  },
19
20
  "devDependencies": {
20
21
  "@types/node": "^25.5.2",