opencode-immune 1.0.75 → 1.0.77
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/server.js +178 -53
- package/package.json +2 -2
package/dist/plugin/server.js
CHANGED
|
@@ -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.
|
|
3780
|
+
var PLUGIN_VERSION = "1.0.77";
|
|
3781
3781
|
var PLUGIN_PACKAGE_NAME = "opencode-immune";
|
|
3782
3782
|
var PLUGIN_DIRNAME = dirname(fileURLToPath(import.meta.url));
|
|
3783
3783
|
function getServerAuthHeaders() {
|
|
@@ -3852,7 +3852,6 @@ function createState(input) {
|
|
|
3852
3852
|
providerRetryWatchdogs: /* @__PURE__ */ new Map(),
|
|
3853
3853
|
childFallbackRequests: /* @__PURE__ */ new Map(),
|
|
3854
3854
|
sessionErrorRetryCount: /* @__PURE__ */ new Map(),
|
|
3855
|
-
ultraworkPermissionSessions: /* @__PURE__ */ new Set(),
|
|
3856
3855
|
fallbackAgentByAgent: /* @__PURE__ */ new Map(),
|
|
3857
3856
|
baseAgentByFallbackAgent: /* @__PURE__ */ new Map(),
|
|
3858
3857
|
fallbackModelCandidates: [],
|
|
@@ -3889,23 +3888,6 @@ function createState(input) {
|
|
|
3889
3888
|
};
|
|
3890
3889
|
}
|
|
3891
3890
|
var ULTRAWORK_AGENT = "0-ultrawork";
|
|
3892
|
-
var ULTRAWORK_SESSION_PERMISSION = [
|
|
3893
|
-
{ permission: "read", pattern: "*", action: "allow" },
|
|
3894
|
-
{ permission: "edit", pattern: "*", action: "allow" },
|
|
3895
|
-
{ permission: "glob", pattern: "*", action: "allow" },
|
|
3896
|
-
{ permission: "grep", pattern: "*", action: "allow" },
|
|
3897
|
-
{ permission: "list", pattern: "*", action: "allow" },
|
|
3898
|
-
{ permission: "bash", pattern: "*", action: "allow" },
|
|
3899
|
-
{ permission: "task", pattern: "*", action: "allow" },
|
|
3900
|
-
{ permission: "external_directory", pattern: "*", action: "allow" },
|
|
3901
|
-
{ permission: "todowrite", pattern: "*", action: "allow" },
|
|
3902
|
-
{ permission: "question", pattern: "*", action: "allow" },
|
|
3903
|
-
{ permission: "webfetch", pattern: "*", action: "allow" },
|
|
3904
|
-
{ permission: "websearch", pattern: "*", action: "allow" },
|
|
3905
|
-
{ permission: "codesearch", pattern: "*", action: "allow" },
|
|
3906
|
-
{ permission: "lsp", pattern: "*", action: "allow" },
|
|
3907
|
-
{ permission: "skill", pattern: "*", action: "allow" }
|
|
3908
|
-
];
|
|
3909
3891
|
var DIAGNOSTIC_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
3910
3892
|
var activeLogDirectory = null;
|
|
3911
3893
|
var MANAGED_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -3913,6 +3895,8 @@ var PROVIDER_RETRY_WATCHDOG_MS = 3e4;
|
|
|
3913
3895
|
var RETRY_PROMPT_DELIVERY_ATTEMPTS = 3;
|
|
3914
3896
|
var CHILD_FALLBACK_REQUEST_TTL_MS = 10 * 60 * 1e3;
|
|
3915
3897
|
var AUTO_CYCLE_LOCK_TTL_MS = 30 * 60 * 1e3;
|
|
3898
|
+
var PROJECT_TMP_RELATIVE_PATH = ".opencode/tmp";
|
|
3899
|
+
var PROJECT_LOG_RELATIVE_PATH = ".opencode/state/logs";
|
|
3916
3900
|
var MODEL_NAME_CAPABILITY_SCORE = {
|
|
3917
3901
|
"claude-opus-4-7": 100,
|
|
3918
3902
|
"gpt-5.5": 100,
|
|
@@ -3932,11 +3916,16 @@ function isManagedRootUltraworkSession(state, sessionID) {
|
|
|
3932
3916
|
const record = getManagedSession(state, sessionID);
|
|
3933
3917
|
return !!record && record.kind === "root";
|
|
3934
3918
|
}
|
|
3919
|
+
function getProjectTmpDir(state) {
|
|
3920
|
+
return join(state.input.directory, PROJECT_TMP_RELATIVE_PATH);
|
|
3921
|
+
}
|
|
3922
|
+
function getProjectLogDir(state) {
|
|
3923
|
+
return join(state.input.directory, PROJECT_LOG_RELATIVE_PATH);
|
|
3924
|
+
}
|
|
3935
3925
|
async function createManagedUltraworkSession(state, title) {
|
|
3936
3926
|
const result = await state.client.session.create({
|
|
3937
3927
|
directory: state.input.directory,
|
|
3938
|
-
title
|
|
3939
|
-
permission: ULTRAWORK_SESSION_PERMISSION
|
|
3928
|
+
title
|
|
3940
3929
|
});
|
|
3941
3930
|
if (result.error || !result.response.ok) {
|
|
3942
3931
|
throw new Error(
|
|
@@ -3984,31 +3973,7 @@ async function startAutoCycleInNewSession(state, options) {
|
|
|
3984
3973
|
state.autoResumeInFlight = false;
|
|
3985
3974
|
}
|
|
3986
3975
|
}
|
|
3987
|
-
async function applyUltraworkSessionPermissions(state, sessionID) {
|
|
3988
|
-
if (state.ultraworkPermissionSessions.has(sessionID)) return;
|
|
3989
|
-
try {
|
|
3990
|
-
const result = await state.client.session.update({
|
|
3991
|
-
directory: state.input.directory,
|
|
3992
|
-
sessionID,
|
|
3993
|
-
permission: ULTRAWORK_SESSION_PERMISSION
|
|
3994
|
-
});
|
|
3995
|
-
if (result.error || !result.response.ok) {
|
|
3996
|
-
pluginLog.warn(
|
|
3997
|
-
`[opencode-immune] Failed to apply ultrawork permissions to session ${sessionID}:`,
|
|
3998
|
-
result.error ?? result.response.status
|
|
3999
|
-
);
|
|
4000
|
-
return;
|
|
4001
|
-
}
|
|
4002
|
-
state.ultraworkPermissionSessions.add(sessionID);
|
|
4003
|
-
} catch (err) {
|
|
4004
|
-
pluginLog.warn(
|
|
4005
|
-
`[opencode-immune] Failed to apply ultrawork permissions to session ${sessionID}:`,
|
|
4006
|
-
err
|
|
4007
|
-
);
|
|
4008
|
-
}
|
|
4009
|
-
}
|
|
4010
3976
|
async function promptManagedSession(state, sessionID, text, options = {}) {
|
|
4011
|
-
await applyUltraworkSessionPermissions(state, sessionID);
|
|
4012
3977
|
const result = await state.client.session.promptAsync({
|
|
4013
3978
|
directory: state.input.directory,
|
|
4014
3979
|
sessionID,
|
|
@@ -4149,7 +4114,6 @@ async function addManagedUltraworkSession(state, sessionID, timestamp = Date.now
|
|
|
4149
4114
|
return;
|
|
4150
4115
|
}
|
|
4151
4116
|
state.managedUltraworkSessions.set(sessionID, nextRecord);
|
|
4152
|
-
await applyUltraworkSessionPermissions(state, sessionID);
|
|
4153
4117
|
}
|
|
4154
4118
|
async function addManagedChildSession(state, sessionID, parentSessionID, timestamp = Date.now()) {
|
|
4155
4119
|
const parent = state.managedUltraworkSessions.get(parentSessionID);
|
|
@@ -4202,7 +4166,6 @@ async function removeManagedUltraworkSession(state, sessionID, reason) {
|
|
|
4202
4166
|
cancelPendingSessionRetry(state, sessionID, reason);
|
|
4203
4167
|
cancelProviderRetryWatchdog(state, sessionID, reason);
|
|
4204
4168
|
state.sessionErrorRetryCount.delete(sessionID);
|
|
4205
|
-
state.ultraworkPermissionSessions.delete(sessionID);
|
|
4206
4169
|
const existed = state.managedUltraworkSessions.delete(sessionID);
|
|
4207
4170
|
if (!existed) return;
|
|
4208
4171
|
writePluginLog(
|
|
@@ -5110,6 +5073,104 @@ async function fileHash(filePath) {
|
|
|
5110
5073
|
return "";
|
|
5111
5074
|
}
|
|
5112
5075
|
}
|
|
5076
|
+
function parseImmunePluginSpec(value) {
|
|
5077
|
+
const trimmed = value.trim();
|
|
5078
|
+
const match = trimmed.match(/^opencode-immune(?:@(.+))?$/);
|
|
5079
|
+
if (!match) return null;
|
|
5080
|
+
return {
|
|
5081
|
+
spec: trimmed,
|
|
5082
|
+
version: match[1] ?? null
|
|
5083
|
+
};
|
|
5084
|
+
}
|
|
5085
|
+
async function readProjectImmunePluginSpec(directory) {
|
|
5086
|
+
try {
|
|
5087
|
+
const raw = await readFile(join(directory, "opencode.json"), "utf-8");
|
|
5088
|
+
const config = JSON.parse(raw);
|
|
5089
|
+
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
5090
|
+
for (let index = plugins.length - 1; index >= 0; index--) {
|
|
5091
|
+
const value = plugins[index];
|
|
5092
|
+
if (typeof value !== "string") continue;
|
|
5093
|
+
const parsed = parseImmunePluginSpec(value);
|
|
5094
|
+
if (parsed) return parsed;
|
|
5095
|
+
}
|
|
5096
|
+
} catch {
|
|
5097
|
+
}
|
|
5098
|
+
return null;
|
|
5099
|
+
}
|
|
5100
|
+
function execFileAsync(command, args, options) {
|
|
5101
|
+
return new Promise((resolve, reject) => {
|
|
5102
|
+
execFile(command, args, options ?? {}, (err, stdout, stderr) => {
|
|
5103
|
+
if (err) {
|
|
5104
|
+
reject(err);
|
|
5105
|
+
return;
|
|
5106
|
+
}
|
|
5107
|
+
resolve({ stdout, stderr });
|
|
5108
|
+
});
|
|
5109
|
+
});
|
|
5110
|
+
}
|
|
5111
|
+
async function clearProjectPluginOverride(directory) {
|
|
5112
|
+
const overridePath = join(directory, ".opencode", "opencode.json");
|
|
5113
|
+
try {
|
|
5114
|
+
const raw = await readFile(overridePath, "utf-8");
|
|
5115
|
+
const config = JSON.parse(raw);
|
|
5116
|
+
const keys = Object.keys(config ?? {});
|
|
5117
|
+
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
5118
|
+
const isGeneratedVersionPin = plugins.length === 1 && typeof plugins[0] === "string" && /^opencode-immune@.+$/.test(plugins[0]) && keys.every((key) => key === "plugin" || key === "$schema");
|
|
5119
|
+
if (!isGeneratedVersionPin) {
|
|
5120
|
+
return false;
|
|
5121
|
+
}
|
|
5122
|
+
await unlink(overridePath);
|
|
5123
|
+
return true;
|
|
5124
|
+
} catch {
|
|
5125
|
+
return false;
|
|
5126
|
+
}
|
|
5127
|
+
}
|
|
5128
|
+
async function installPinnedProjectPlugin(state, pluginSpec) {
|
|
5129
|
+
if (!pluginSpec.version) return false;
|
|
5130
|
+
const currentVersion = await getPluginVersion();
|
|
5131
|
+
if (pluginSpec.version === currentVersion) {
|
|
5132
|
+
const overrideRemoved = await clearProjectPluginOverride(state.input.directory);
|
|
5133
|
+
if (overrideRemoved) {
|
|
5134
|
+
pluginLog.info(
|
|
5135
|
+
`[opencode-immune] Harness sync: removed stale local plugin override after confirming ${pluginSpec.spec}.`
|
|
5136
|
+
);
|
|
5137
|
+
}
|
|
5138
|
+
return false;
|
|
5139
|
+
}
|
|
5140
|
+
try {
|
|
5141
|
+
pluginLog.info(
|
|
5142
|
+
`[opencode-immune] Harness sync: installing ${pluginSpec.spec} in ${state.input.directory}`
|
|
5143
|
+
);
|
|
5144
|
+
await execFileAsync("opencode", ["plugin", pluginSpec.spec, "--force"], {
|
|
5145
|
+
cwd: state.input.directory,
|
|
5146
|
+
env: process.env
|
|
5147
|
+
});
|
|
5148
|
+
const overrideRemoved = await clearProjectPluginOverride(state.input.directory);
|
|
5149
|
+
if (overrideRemoved) {
|
|
5150
|
+
pluginLog.info(
|
|
5151
|
+
`[opencode-immune] Harness sync: removed local plugin override after installing ${pluginSpec.spec}.`
|
|
5152
|
+
);
|
|
5153
|
+
}
|
|
5154
|
+
state.pluginUpdateMessage = `[PLUGIN UPDATE] Harness synced this project to ${pluginSpec.spec}. Restart opencode to load the new plugin version.`;
|
|
5155
|
+
await writeDiagnosticLog(state, "harness-sync:plugin-install", {
|
|
5156
|
+
pluginSpec: pluginSpec.spec,
|
|
5157
|
+
status: "installed"
|
|
5158
|
+
});
|
|
5159
|
+
return true;
|
|
5160
|
+
} catch (err) {
|
|
5161
|
+
pluginLog.warn(
|
|
5162
|
+
`[opencode-immune] Harness sync: automatic install failed for ${pluginSpec.spec}.`,
|
|
5163
|
+
err instanceof Error ? err.message : String(err)
|
|
5164
|
+
);
|
|
5165
|
+
state.pluginUpdateMessage = `[PLUGIN UPDATE] Harness synced this project to ${pluginSpec.spec}, but automatic install failed. Run \`opencode plugin ${pluginSpec.spec} --force\` in this project, then restart opencode.`;
|
|
5166
|
+
await writeDiagnosticLog(state, "harness-sync:plugin-install", {
|
|
5167
|
+
pluginSpec: pluginSpec.spec,
|
|
5168
|
+
status: "failed",
|
|
5169
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5170
|
+
});
|
|
5171
|
+
return false;
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5113
5174
|
async function syncHarness(state) {
|
|
5114
5175
|
const token = await resolveEnvValue(state.input.directory, HARNESS_TOKEN_ENV);
|
|
5115
5176
|
if (!token) {
|
|
@@ -5160,19 +5221,36 @@ async function syncHarness(state) {
|
|
|
5160
5221
|
release.tagName + "\n",
|
|
5161
5222
|
"utf-8"
|
|
5162
5223
|
);
|
|
5224
|
+
const projectPluginSpec = await readProjectImmunePluginSpec(state.input.directory);
|
|
5225
|
+
const pluginInstalled = projectPluginSpec ? await installPinnedProjectPlugin(state, projectPluginSpec) : false;
|
|
5226
|
+
if (!projectPluginSpec) {
|
|
5227
|
+
const overrideRemoved = await clearProjectPluginOverride(state.input.directory);
|
|
5228
|
+
if (overrideRemoved) {
|
|
5229
|
+
pluginLog.info(
|
|
5230
|
+
`[opencode-immune] Harness sync: removed local plugin override to keep project config authoritative.`
|
|
5231
|
+
);
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5163
5234
|
const hashAfter = await fileHash(configPath);
|
|
5164
5235
|
if (hashBefore && hashAfter && hashBefore !== hashAfter) {
|
|
5165
5236
|
pluginLog.warn(
|
|
5166
5237
|
`[opencode-immune] \u26A0 Harness sync: opencode.json was updated. Please restart opencode for the new agent configuration to take effect.`
|
|
5167
5238
|
);
|
|
5168
5239
|
}
|
|
5240
|
+
if (pluginInstalled) {
|
|
5241
|
+
pluginLog.warn(
|
|
5242
|
+
`[opencode-immune] \u26A0 Harness sync: installed updated plugin from project config. Restart opencode to load the new plugin code.`
|
|
5243
|
+
);
|
|
5244
|
+
}
|
|
5169
5245
|
pluginLog.info(
|
|
5170
5246
|
`[opencode-immune] Harness sync: successfully updated to ${release.tagName}`
|
|
5171
5247
|
);
|
|
5172
5248
|
await writeDiagnosticLog(state, "harness-sync:success", {
|
|
5173
5249
|
from: localVersion,
|
|
5174
5250
|
to: release.tagName,
|
|
5175
|
-
configChanged: hashBefore !== hashAfter
|
|
5251
|
+
configChanged: hashBefore !== hashAfter,
|
|
5252
|
+
pluginSpec: projectPluginSpec?.spec ?? null,
|
|
5253
|
+
pluginInstalled
|
|
5176
5254
|
});
|
|
5177
5255
|
} finally {
|
|
5178
5256
|
try {
|
|
@@ -5968,17 +6046,59 @@ function createPermissionAskHandler(state) {
|
|
|
5968
6046
|
state.recoveryContext = recovery;
|
|
5969
6047
|
await addManagedUltraworkSession(state, sessionID);
|
|
5970
6048
|
pluginLog.info(
|
|
5971
|
-
`[opencode-immune] Permission request recovered AUTO-RESUME session ${sessionID};
|
|
6049
|
+
`[opencode-immune] Permission request recovered AUTO-RESUME session ${sessionID}; tracking as managed ultrawork session.`
|
|
5972
6050
|
);
|
|
5973
6051
|
}
|
|
5974
|
-
|
|
5975
|
-
|
|
6052
|
+
const permissionType = getPermissionType(input);
|
|
6053
|
+
const patterns = getPermissionPatterns(input);
|
|
6054
|
+
if (isManagedUltraworkSession(state, sessionID) && permissionType === "external_directory" && patterns.some(isExternalTmpPattern)) {
|
|
6055
|
+
output.status = "deny";
|
|
6056
|
+
await writeDiagnosticLog(state, "permission:auto-deny-external-tmp", {
|
|
6057
|
+
sessionID,
|
|
6058
|
+
status: output.status,
|
|
6059
|
+
permission: permissionType,
|
|
6060
|
+
patterns,
|
|
6061
|
+
projectTmpDir: getProjectTmpDir(state),
|
|
6062
|
+
projectLogDir: getProjectLogDir(state)
|
|
6063
|
+
});
|
|
6064
|
+
return;
|
|
6065
|
+
}
|
|
6066
|
+
await writeDiagnosticLog(state, "permission:ask", {
|
|
5976
6067
|
sessionID,
|
|
5977
|
-
|
|
5978
|
-
|
|
6068
|
+
status: output.status,
|
|
6069
|
+
permission: permissionType,
|
|
6070
|
+
patterns
|
|
5979
6071
|
});
|
|
5980
6072
|
};
|
|
5981
6073
|
}
|
|
6074
|
+
function getPermissionType(input) {
|
|
6075
|
+
if (!input || typeof input !== "object") return void 0;
|
|
6076
|
+
const value = input;
|
|
6077
|
+
return typeof value.type === "string" ? value.type : typeof value.permission === "string" ? value.permission : void 0;
|
|
6078
|
+
}
|
|
6079
|
+
function getPermissionPatterns(input) {
|
|
6080
|
+
if (!input || typeof input !== "object") return [];
|
|
6081
|
+
const value = input;
|
|
6082
|
+
const source = value.pattern ?? value.patterns;
|
|
6083
|
+
if (Array.isArray(source)) return source.filter((item) => typeof item === "string");
|
|
6084
|
+
return typeof source === "string" ? [source] : [];
|
|
6085
|
+
}
|
|
6086
|
+
function isExternalTmpPattern(pattern) {
|
|
6087
|
+
return pattern === "/tmp" || pattern.startsWith("/tmp/") || pattern === "/tmp/*";
|
|
6088
|
+
}
|
|
6089
|
+
function createShellEnvHandler(state) {
|
|
6090
|
+
return async (_input, output) => {
|
|
6091
|
+
const projectTmpDir = getProjectTmpDir(state);
|
|
6092
|
+
const projectLogDir = getProjectLogDir(state);
|
|
6093
|
+
await mkdir(projectTmpDir, { recursive: true });
|
|
6094
|
+
await mkdir(projectLogDir, { recursive: true });
|
|
6095
|
+
output.env.TMPDIR = projectTmpDir;
|
|
6096
|
+
output.env.TMP = projectTmpDir;
|
|
6097
|
+
output.env.TEMP = projectTmpDir;
|
|
6098
|
+
output.env.OPENCODE_TMP_DIR = projectTmpDir;
|
|
6099
|
+
output.env.OPENCODE_LOG_DIR = projectLogDir;
|
|
6100
|
+
};
|
|
6101
|
+
}
|
|
5982
6102
|
async function server(input) {
|
|
5983
6103
|
const state = createState(input);
|
|
5984
6104
|
checkPluginUpdate(state).catch(() => {
|
|
@@ -6118,6 +6238,11 @@ async function server(input) {
|
|
|
6118
6238
|
state,
|
|
6119
6239
|
"permission.ask",
|
|
6120
6240
|
createPermissionAskHandler(state)
|
|
6241
|
+
),
|
|
6242
|
+
"shell.env": withErrorBoundary(
|
|
6243
|
+
state,
|
|
6244
|
+
"shell.env",
|
|
6245
|
+
createShellEnvHandler(state)
|
|
6121
6246
|
)
|
|
6122
6247
|
};
|
|
6123
6248
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-immune",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.77",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin: session recovery, auto-retry, multi-cycle automation, context monitoring",
|
|
6
6
|
"exports": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"dist/plugin/server.js"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
-
"build": "esbuild plugin.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/plugin/server.js",
|
|
15
|
+
"build": "esbuild plugin.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/plugin/server.js && esbuild plugin.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/plugin.js",
|
|
16
16
|
"dev": "node ./node_modules/typescript/bin/tsc --project tsconfig.json --watch",
|
|
17
17
|
"prepublishOnly": "npm run build"
|
|
18
18
|
},
|