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.
- package/dist/plugin.js +82 -100
- 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
|
|
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
|
|
550
|
-
|
|
551
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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",
|