opencode-immune 1.0.46 → 1.0.47

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 +82 -100
  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");
@@ -71,8 +72,13 @@ async function checkPluginUpdate(state) {
71
72
  }
72
73
  function createState(input) {
73
74
  activeLogDirectory = input.directory;
75
+ const { client: _client, ...runtimeInput } = input;
74
76
  return {
75
- input,
77
+ input: runtimeInput,
78
+ client: (0, client_1.createOpencodeClient)({
79
+ baseUrl: input.serverUrl.toString(),
80
+ directory: input.directory,
81
+ }),
76
82
  recoveryContext: null,
77
83
  managedUltraworkSessions: new Map(),
78
84
  sessionRetryTimers: new Map(),
@@ -93,6 +99,23 @@ function createState(input) {
93
99
  };
94
100
  }
95
101
  const ULTRAWORK_AGENT = "0-ultrawork";
102
+ const ULTRAWORK_SESSION_PERMISSION = [
103
+ { permission: "read", pattern: "*", action: "allow" },
104
+ { permission: "edit", pattern: "*", action: "allow" },
105
+ { permission: "glob", pattern: "*", action: "allow" },
106
+ { permission: "grep", pattern: "*", action: "allow" },
107
+ { permission: "list", pattern: "*", action: "allow" },
108
+ { permission: "bash", pattern: "*", action: "allow" },
109
+ { permission: "task", pattern: "*", action: "allow" },
110
+ { permission: "external_directory", pattern: "*", action: "allow" },
111
+ { permission: "todowrite", pattern: "*", action: "allow" },
112
+ { permission: "question", pattern: "*", action: "allow" },
113
+ { permission: "webfetch", pattern: "*", action: "allow" },
114
+ { permission: "websearch", pattern: "*", action: "allow" },
115
+ { permission: "codesearch", pattern: "*", action: "allow" },
116
+ { permission: "lsp", pattern: "*", action: "allow" },
117
+ { permission: "skill", pattern: "*", action: "allow" },
118
+ ];
96
119
  const DIAGNOSTIC_LOG_MAX_BYTES = 5 * 1024 * 1024;
97
120
  let activeLogDirectory = null;
98
121
  const MANAGED_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
@@ -116,6 +139,38 @@ function isManagedRootUltraworkSession(state, sessionID) {
116
139
  const record = getManagedSession(state, sessionID);
117
140
  return !!record && record.kind === "root";
118
141
  }
142
+ async function createManagedUltraworkSession(state, title) {
143
+ const result = await state.client.session.create({
144
+ directory: state.input.directory,
145
+ title,
146
+ permission: ULTRAWORK_SESSION_PERMISSION,
147
+ });
148
+ const sessionID = result?.data?.id;
149
+ if (!sessionID)
150
+ return null;
151
+ await addManagedUltraworkSession(state, sessionID);
152
+ return sessionID;
153
+ }
154
+ async function promptManagedSession(state, sessionID, text, options = {}) {
155
+ await state.client.session.promptAsync({
156
+ directory: state.input.directory,
157
+ sessionID,
158
+ ...(options.model ? { model: options.model } : {}),
159
+ agent: options.agent ?? ULTRAWORK_AGENT,
160
+ parts: [
161
+ {
162
+ type: "text",
163
+ text,
164
+ },
165
+ ],
166
+ });
167
+ }
168
+ async function abortManagedSession(state, sessionID) {
169
+ await state.client.session.abort({
170
+ directory: state.input.directory,
171
+ sessionID,
172
+ });
173
+ }
119
174
  function pruneExpiredManagedSessions(state, now = Date.now()) {
120
175
  let removed = 0;
121
176
  for (const [sessionID, record] of state.managedUltraworkSessions.entries()) {
@@ -534,9 +589,7 @@ async function sendManagedSessionRetryPrompt(state, sessionID, reason, options =
534
589
  });
535
590
  if (options.abortBeforePrompt) {
536
591
  try {
537
- await state.input.client.session.abort({
538
- path: { id: sessionID },
539
- });
592
+ await abortManagedSession(state, sessionID);
540
593
  await writeDiagnosticLog(state, "session-retry:abort-success", { sessionID });
541
594
  }
542
595
  catch (err) {
@@ -546,18 +599,9 @@ async function sendManagedSessionRetryPrompt(state, sessionID, reason, options =
546
599
  });
547
600
  }
548
601
  }
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 },
602
+ await promptManagedSession(state, sessionID, retryText, {
603
+ agent: retryAgent,
604
+ model: fallbackModel,
561
605
  });
562
606
  writePluginLog(state, "info", `[opencode-immune] Retry prompt sent to session ${sessionID} (${reason})` +
563
607
  (fallbackModel
@@ -1096,18 +1140,7 @@ function createSessionRecoveryEvent(state) {
1096
1140
  }
1097
1141
  setTimeout(async () => {
1098
1142
  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
- });
1143
+ 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
1144
  pluginLog.info(`[opencode-immune] Auto-resume prompt sent to managed ultrawork session ${sessionID}`);
1112
1145
  }
1113
1146
  catch (err) {
@@ -1350,18 +1383,7 @@ function createFallbackModels(state) {
1350
1383
  const sid = input.sessionID;
1351
1384
  setTimeout(async () => {
1352
1385
  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
- });
1386
+ 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
1387
  pluginLog.info(`[opencode-immune] Auto-resume prompt sent to session ${sid}`);
1366
1388
  }
1367
1389
  catch (err) {
@@ -1714,31 +1736,13 @@ function createTextCompleteHandler(state) {
1714
1736
  const nextTask = taskMatch?.[1]?.trim() ?? "Continue processing task backlog";
1715
1737
  pluginLog.info(`[opencode-immune] Multi-Cycle: Creating new session (cycle ${state.cycleCount}/${MAX_CYCLES}) for: "${nextTask}"`);
1716
1738
  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;
1739
+ const newSessionID = await createManagedUltraworkSession(state, `Ultrawork Cycle ${state.cycleCount + 1}`);
1724
1740
  if (!newSessionID) {
1725
1741
  pluginLog.error("[opencode-immune] Multi-Cycle: Failed to create new session — no ID returned.");
1726
1742
  return;
1727
1743
  }
1728
1744
  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
- });
1745
+ 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
1746
  pluginLog.info(`[opencode-immune] Multi-Cycle: Bootstrap prompt sent to ${newSessionID}`);
1743
1747
  }
1744
1748
  catch (err) {
@@ -1789,6 +1793,19 @@ function createMultiCycleHandler(state) {
1789
1793
  }
1790
1794
  };
1791
1795
  }
1796
+ function createPermissionAskHandler(state) {
1797
+ return async (input, output) => {
1798
+ const sessionID = input.sessionID;
1799
+ if (!isManagedUltraworkSession(state, sessionID))
1800
+ return;
1801
+ output.status = "allow";
1802
+ await writeDiagnosticLog(state, "permission:auto-allow", {
1803
+ sessionID,
1804
+ permission: input.permission,
1805
+ patterns: input.patterns,
1806
+ });
1807
+ };
1808
+ }
1792
1809
  // ═══════════════════════════════════════════════════════════════════════════════
1793
1810
  // PLUGIN MODULE EXPORT
1794
1811
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -1818,31 +1835,13 @@ async function server(input) {
1818
1835
  // Delay to let opencode fully initialize.
1819
1836
  setTimeout(async () => {
1820
1837
  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;
1838
+ const newSessionID = await createManagedUltraworkSession(state, `AUTO-RESUME: ${recovery.task}`);
1828
1839
  if (!newSessionID) {
1829
1840
  pluginLog.error("[opencode-immune] Auto-resume: Failed to create session — no session ID returned.");
1830
1841
  return;
1831
1842
  }
1832
1843
  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
- });
1844
+ 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
1845
  pluginLog.info(`[opencode-immune] Auto-resume prompt sent to new session ${newSessionID}`);
1847
1846
  }
1848
1847
  catch (err) {
@@ -1862,31 +1861,13 @@ async function server(input) {
1862
1861
  `Will create new session to start next cycle.`);
1863
1862
  setTimeout(async () => {
1864
1863
  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;
1864
+ const newSessionID = await createManagedUltraworkSession(state, `AUTO-CYCLE: next backlog task`);
1872
1865
  if (!newSessionID) {
1873
1866
  pluginLog.error("[opencode-immune] Auto-cycle: Failed to create session — no session ID returned.");
1874
1867
  return;
1875
1868
  }
1876
1869
  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
- });
1870
+ 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
1871
  pluginLog.info(`[opencode-immune] Auto-cycle prompt sent to new session ${newSessionID}`);
1891
1872
  }
1892
1873
  catch (err) {
@@ -1931,6 +1912,7 @@ async function server(input) {
1931
1912
  "experimental.chat.system.transform": withErrorBoundary(state, "experimental.chat.system.transform", createSystemTransform(state)),
1932
1913
  "experimental.session.compacting": withErrorBoundary(state, "experimental.session.compacting", createCompactionHandler(state)),
1933
1914
  "experimental.text.complete": withErrorBoundary(state, "experimental.text.complete", createTextCompleteHandler(state)),
1915
+ "permission.ask": withErrorBoundary(state, "permission.ask", createPermissionAskHandler(state)),
1934
1916
  };
1935
1917
  }
1936
1918
  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.47",
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",