opencode-immune 1.0.75 → 1.0.76

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.
@@ -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.75";
3780
+ var PLUGIN_VERSION = "1.0.76";
3781
3781
  var PLUGIN_PACKAGE_NAME = "opencode-immune";
3782
3782
  var PLUGIN_DIRNAME = dirname(fileURLToPath(import.meta.url));
3783
3783
  function getServerAuthHeaders() {
@@ -5110,6 +5110,104 @@ async function fileHash(filePath) {
5110
5110
  return "";
5111
5111
  }
5112
5112
  }
5113
+ function parseImmunePluginSpec(value) {
5114
+ const trimmed = value.trim();
5115
+ const match = trimmed.match(/^opencode-immune(?:@(.+))?$/);
5116
+ if (!match) return null;
5117
+ return {
5118
+ spec: trimmed,
5119
+ version: match[1] ?? null
5120
+ };
5121
+ }
5122
+ async function readProjectImmunePluginSpec(directory) {
5123
+ try {
5124
+ const raw = await readFile(join(directory, "opencode.json"), "utf-8");
5125
+ const config = JSON.parse(raw);
5126
+ const plugins = Array.isArray(config.plugin) ? config.plugin : [];
5127
+ for (let index = plugins.length - 1; index >= 0; index--) {
5128
+ const value = plugins[index];
5129
+ if (typeof value !== "string") continue;
5130
+ const parsed = parseImmunePluginSpec(value);
5131
+ if (parsed) return parsed;
5132
+ }
5133
+ } catch {
5134
+ }
5135
+ return null;
5136
+ }
5137
+ function execFileAsync(command, args, options) {
5138
+ return new Promise((resolve, reject) => {
5139
+ execFile(command, args, options ?? {}, (err, stdout, stderr) => {
5140
+ if (err) {
5141
+ reject(err);
5142
+ return;
5143
+ }
5144
+ resolve({ stdout, stderr });
5145
+ });
5146
+ });
5147
+ }
5148
+ async function clearProjectPluginOverride(directory) {
5149
+ const overridePath = join(directory, ".opencode", "opencode.json");
5150
+ try {
5151
+ const raw = await readFile(overridePath, "utf-8");
5152
+ const config = JSON.parse(raw);
5153
+ const keys = Object.keys(config ?? {});
5154
+ const plugins = Array.isArray(config.plugin) ? config.plugin : [];
5155
+ const isGeneratedVersionPin = plugins.length === 1 && typeof plugins[0] === "string" && /^opencode-immune@.+$/.test(plugins[0]) && keys.every((key) => key === "plugin" || key === "$schema");
5156
+ if (!isGeneratedVersionPin) {
5157
+ return false;
5158
+ }
5159
+ await unlink(overridePath);
5160
+ return true;
5161
+ } catch {
5162
+ return false;
5163
+ }
5164
+ }
5165
+ async function installPinnedProjectPlugin(state, pluginSpec) {
5166
+ if (!pluginSpec.version) return false;
5167
+ const currentVersion = await getPluginVersion();
5168
+ if (pluginSpec.version === currentVersion) {
5169
+ const overrideRemoved = await clearProjectPluginOverride(state.input.directory);
5170
+ if (overrideRemoved) {
5171
+ pluginLog.info(
5172
+ `[opencode-immune] Harness sync: removed stale local plugin override after confirming ${pluginSpec.spec}.`
5173
+ );
5174
+ }
5175
+ return false;
5176
+ }
5177
+ try {
5178
+ pluginLog.info(
5179
+ `[opencode-immune] Harness sync: installing ${pluginSpec.spec} in ${state.input.directory}`
5180
+ );
5181
+ await execFileAsync("opencode", ["plugin", pluginSpec.spec, "--force"], {
5182
+ cwd: state.input.directory,
5183
+ env: process.env
5184
+ });
5185
+ const overrideRemoved = await clearProjectPluginOverride(state.input.directory);
5186
+ if (overrideRemoved) {
5187
+ pluginLog.info(
5188
+ `[opencode-immune] Harness sync: removed local plugin override after installing ${pluginSpec.spec}.`
5189
+ );
5190
+ }
5191
+ state.pluginUpdateMessage = `[PLUGIN UPDATE] Harness synced this project to ${pluginSpec.spec}. Restart opencode to load the new plugin version.`;
5192
+ await writeDiagnosticLog(state, "harness-sync:plugin-install", {
5193
+ pluginSpec: pluginSpec.spec,
5194
+ status: "installed"
5195
+ });
5196
+ return true;
5197
+ } catch (err) {
5198
+ pluginLog.warn(
5199
+ `[opencode-immune] Harness sync: automatic install failed for ${pluginSpec.spec}.`,
5200
+ err instanceof Error ? err.message : String(err)
5201
+ );
5202
+ 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.`;
5203
+ await writeDiagnosticLog(state, "harness-sync:plugin-install", {
5204
+ pluginSpec: pluginSpec.spec,
5205
+ status: "failed",
5206
+ error: err instanceof Error ? err.message : String(err)
5207
+ });
5208
+ return false;
5209
+ }
5210
+ }
5113
5211
  async function syncHarness(state) {
5114
5212
  const token = await resolveEnvValue(state.input.directory, HARNESS_TOKEN_ENV);
5115
5213
  if (!token) {
@@ -5160,19 +5258,36 @@ async function syncHarness(state) {
5160
5258
  release.tagName + "\n",
5161
5259
  "utf-8"
5162
5260
  );
5261
+ const projectPluginSpec = await readProjectImmunePluginSpec(state.input.directory);
5262
+ const pluginInstalled = projectPluginSpec ? await installPinnedProjectPlugin(state, projectPluginSpec) : false;
5263
+ if (!projectPluginSpec) {
5264
+ const overrideRemoved = await clearProjectPluginOverride(state.input.directory);
5265
+ if (overrideRemoved) {
5266
+ pluginLog.info(
5267
+ `[opencode-immune] Harness sync: removed local plugin override to keep project config authoritative.`
5268
+ );
5269
+ }
5270
+ }
5163
5271
  const hashAfter = await fileHash(configPath);
5164
5272
  if (hashBefore && hashAfter && hashBefore !== hashAfter) {
5165
5273
  pluginLog.warn(
5166
5274
  `[opencode-immune] \u26A0 Harness sync: opencode.json was updated. Please restart opencode for the new agent configuration to take effect.`
5167
5275
  );
5168
5276
  }
5277
+ if (pluginInstalled) {
5278
+ pluginLog.warn(
5279
+ `[opencode-immune] \u26A0 Harness sync: installed updated plugin from project config. Restart opencode to load the new plugin code.`
5280
+ );
5281
+ }
5169
5282
  pluginLog.info(
5170
5283
  `[opencode-immune] Harness sync: successfully updated to ${release.tagName}`
5171
5284
  );
5172
5285
  await writeDiagnosticLog(state, "harness-sync:success", {
5173
5286
  from: localVersion,
5174
5287
  to: release.tagName,
5175
- configChanged: hashBefore !== hashAfter
5288
+ configChanged: hashBefore !== hashAfter,
5289
+ pluginSpec: projectPluginSpec?.spec ?? null,
5290
+ pluginInstalled
5176
5291
  });
5177
5292
  } finally {
5178
5293
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-immune",
3
- "version": "1.0.75",
3
+ "version": "1.0.76",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin: session recovery, auto-retry, multi-cycle automation, context monitoring",
6
6
  "exports": {