fix(opencode): install discoverable atomic plugin

This commit is contained in:
ZacharyZhang-NY committed 2026-07-20 17:46:37 -04:00
1 parent 3f43f4aad5
commit 69a75153f5
15 files changed
+1554 -348

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
import assert from 'node:assert/strict';
import path from 'node:path';
import test from 'node:test';
import {
createFixture,
environment,
runProcess,
} from '../test-support/opencode-test-helpers.mjs';
const binary = process.env.ELYDORA_OPENCODE_BINARY;
test(
'official OpenCode 1.18 discovers the generated global JavaScript plugin',
{ skip: binary ? false : 'set ELYDORA_OPENCODE_BINARY to the official OpenCode executable' },
async () => {
const fixture = await createFixture();
try {
assert.equal((await fixture.install()).code, 0);
const env = {
...environment(fixture),
OPENCODE_DISABLE_AUTOUPDATE: 'true',
OPENCODE_DISABLE_MODELS_FETCH: 'true',
};
const version = await runProcess(binary, ['--version'], env, fixture.projectDir);
assert.equal(version.code, 0, version.stderr);
assert.match(version.stdout, /^1\.18\./);
const paths = await runProcess(binary, ['debug', 'paths'], env, fixture.projectDir);
assert.equal(paths.code, 0, paths.stderr);
assert.equal(paths.stdout.includes(path.join(fixture.configRoot, 'opencode')), true);
const info = await runProcess(binary, ['debug', 'info'], env, fixture.projectDir);
assert.equal(info.code, 0, info.stderr);
assert.match(info.stdout, /elydora-audit\.js/);
assert.doesNotMatch(info.stdout, /elydora-audit\.mjs/);
} finally {
await fixture.close();
}
},
);
+421 -136
View File
@@ -1,169 +1,454 @@
import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import os from 'node:os';
import {
lstat,
mkdir,
readFile,
readdir,
rm,
stat,
symlink,
writeFile,
} from 'node:fs/promises';
import path from 'node:path';
import { setTimeout as delay } from 'node:timers/promises';
import { pathToFileURL } from 'node:url';
import test from 'node:test';
import { pathToFileURL } from 'node:url';
import {
VALID_PRIVATE_KEY,
cliPath,
contractModuleUrl,
createFixture,
environment,
registryModuleUrl,
runNode,
runPlugin,
startApiServer,
} from '../test-support/opencode-test-helpers.mjs';
function runNode(args, env) {
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, args, {
env: { ...process.env, ...env },
stdio: ['ignore', 'pipe', 'pipe'],
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (chunk) => { stdout += chunk; });
child.stderr.on('data', (chunk) => { stderr += chunk; });
child.once('error', reject);
child.once('close', (code) => resolve({ code, stdout, stderr }));
});
async function assertMissing(filePath) {
await assert.rejects(lstat(filePath), { code: 'ENOENT' });
}
async function createFixture({ guardSource, hookSource }) {
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-opencode-'));
const agentDir = path.join(homeDir, '.elydora', 'agent-1');
await mkdir(agentDir, { recursive: true });
const guardScriptPath = path.join(agentDir, 'guard.cjs');
const hookScriptPath = path.join(agentDir, 'hook.cjs');
if (guardSource !== undefined) await writeFile(guardScriptPath, guardSource);
await writeFile(hookScriptPath, hookSource ?? 'process.exit(0);\n');
async function writeSource(filePath, source) {
await mkdir(path.dirname(filePath), { recursive: true });
await writeFile(filePath, source, { mode: 0o600 });
}
const pluginModuleUrl = pathToFileURL(path.resolve('dist/plugins/opencode.js')).href;
const installScript = `
import { opencodePlugin } from ${JSON.stringify(pluginModuleUrl)};
await opencodePlugin.install(JSON.parse(process.env.ELYDORA_TEST_CONFIG));
async function legacySource(fixture) {
const { buildLegacyOpenCodePlugin } = await import(contractModuleUrl);
return buildLegacyOpenCodePlugin(fixture.hookScriptPath, fixture.guardScriptPath);
}
async function runGeneratedHook(fixture, name, input, output) {
const source = `
const module = await import(${JSON.stringify(pathToFileURL(fixture.pluginPath).href)});
const hooks = await module.ElydoraAuditPlugin({});
await hooks[${JSON.stringify(name)}](
JSON.parse(process.env.ELYDORA_TEST_INPUT),
JSON.parse(process.env.ELYDORA_TEST_OUTPUT),
);
`;
const installResult = await runNode(
['--input-type=module', '--eval', installScript],
return runNode(
['--input-type=module', '--eval', source],
{
HOME: homeDir,
USERPROFILE: homeDir,
ELYDORA_TEST_CONFIG: JSON.stringify({ guardScriptPath, hookScriptPath }),
...environment(fixture),
ELYDORA_TEST_INPUT: JSON.stringify(input),
ELYDORA_TEST_OUTPUT: JSON.stringify(output),
},
fixture.projectDir,
);
assert.equal(installResult.code, 0, installResult.stderr);
const generatedPath = path.join(
homeDir,
'.config',
'opencode',
'plugins',
'elydora-audit.mjs',
);
const generatedPlugin = await import(`${pathToFileURL(generatedPath).href}?test=${Date.now()}`);
const hooks = await generatedPlugin.ElydoraAuditPlugin({ project: { name: 'project-fallback' } });
return {
guardScriptPath,
homeDir,
hooks,
async close() {
await rm(homeDir, { recursive: true, force: true });
},
};
}
async function readPluginStatus(homeDir) {
const pluginModuleUrl = pathToFileURL(path.resolve('dist/plugins/opencode.js')).href;
const statusScript = `
import { opencodePlugin } from ${JSON.stringify(pluginModuleUrl)};
console.log(JSON.stringify(await opencodePlugin.status()));
`;
const result = await runNode(
['--input-type=module', '--eval', statusScript],
{ HOME: homeDir, USERPROFILE: homeDir },
);
assert.equal(result.code, 0, result.stderr);
return JSON.parse(result.stdout);
}
test('OpenCode rejects tool execution when the Elydora guard blocks', async () => {
const fixture = await createFixture({
guardSource: "process.stderr.write('Agent is frozen by Elydora.'); process.exit(2);\n",
test('OpenCode installs the discoverable global JavaScript plugin and exact runtime', async () => {
const { SUPPORTED_AGENTS } = await import(registryModuleUrl);
assert.deepEqual(SUPPORTED_AGENTS.get('opencode'), {
name: 'OpenCode',
configDir: '.config/opencode/plugins',
configFile: 'elydora-audit.js',
});
const fixture = await createFixture();
try {
await assert.rejects(
fixture.hooks['tool.execute.before'](
{ tool: 'bash', sessionID: 'session-1', callID: 'call-1' },
{ args: { command: 'echo test' } },
),
/Agent is frozen by Elydora/,
);
} finally {
await fixture.close();
}
});
test('OpenCode blocks when the guard process cannot start', async () => {
const fixture = await createFixture({});
try {
await assert.rejects(
fixture.hooks['tool.execute.before'](
{ tool: 'bash', sessionID: 'session-1', callID: 'call-1' },
{ args: { command: 'echo test' } },
),
/Elydora guard failed/,
);
} finally {
await fixture.close();
}
});
test('OpenCode forwards the current tool event contract to the audit hook', async () => {
const capturePath = path.join(os.tmpdir(), `elydora-event-${process.pid}-${Date.now()}.json`);
const hookSource = `
const fs = require('node:fs');
const chunks = [];
process.stdin.on('data', (chunk) => chunks.push(chunk));
process.stdin.on('end', () => fs.writeFileSync(${JSON.stringify(capturePath)}, Buffer.concat(chunks)));
`;
const fixture = await createFixture({
guardSource: 'process.exit(0);\n',
hookSource,
});
try {
await fixture.hooks['tool.execute.after'](
const first = await fixture.install();
assert.equal(first.code, 0, first.stderr);
assert.match(first.stdout, /elydora-audit\.js/);
assert.match(first.stdout, /restart active OpenCode sessions/);
await assertMissing(fixture.legacyPluginPath);
const firstSource = await readFile(fixture.pluginPath, 'utf-8');
assert.match(firstSource, /@elydora-opencode-plugin/);
assert.match(firstSource, /tool\.execute\.before/);
assert.match(firstSource, /tool\.execute\.after/);
assert.doesNotMatch(firstSource, /spawn\('node'/);
const hooks = await fixture.loadPlugin();
assert.deepEqual(Object.keys(hooks).sort(), ['tool.execute.after', 'tool.execute.before']);
assert.deepEqual(
JSON.parse(await readFile(path.join(fixture.agentDir, 'config.json'), 'utf-8')),
{
tool: 'bash',
sessionID: 'session-1',
callID: 'call-1',
args: { command: 'echo test' },
org_id: 'org-1',
agent_id: fixture.agentId,
kid: 'kid-1',
base_url: fixture.baseUrl,
token: 'token-1',
agent_name: 'opencode',
},
{ title: 'Shell', output: 'test' },
);
let payload;
const deadline = Date.now() + 3000;
while (Date.now() < deadline) {
try {
payload = await readFile(capturePath, 'utf-8');
break;
} catch (error) {
if (error.code !== 'ENOENT') throw error;
await delay(20);
}
assert.equal(await readFile(path.join(fixture.agentDir, 'private.key'), 'utf-8'), VALID_PRIVATE_KEY);
const status = await runPlugin(fixture, 'status', null);
assert.equal(status.code, 0, status.stderr);
assert.deepEqual(JSON.parse(status.stdout), {
installed: true,
agentName: 'opencode',
displayName: 'OpenCode',
hookConfigured: true,
hookScriptExists: true,
configPath: fixture.pluginPath,
});
const second = await fixture.install();
assert.equal(second.code, 0, second.stderr);
assert.equal(await readFile(fixture.pluginPath, 'utf-8'), firstSource);
const names = [
...await readdir(fixture.pluginsDir),
...await readdir(fixture.agentDir),
];
assert.equal(names.some((name) => /\.(tmp|rollback)$/.test(name)), false);
if (process.platform !== 'win32') {
assert.equal((await stat(fixture.pluginPath)).mode & 0o777, 0o600);
assert.equal((await stat(fixture.guardScriptPath)).mode & 0o777, 0o700);
}
assert(payload, 'audit hook did not receive the tool event');
assert.deepEqual(JSON.parse(payload), {
} finally {
await fixture.close();
}
});
test('OpenCode guard enforces state and audit preserves the complete native event', async () => {
const api = await startApiServer();
const fixture = await createFixture({ baseUrl: api.baseUrl });
try {
assert.equal((await fixture.install()).code, 0);
const beforeInput = { tool: 'bash', sessionID: 'session-1', callID: 'call-1' };
const beforeOutput = { args: { command: 'echo test' } };
const guard = await runGeneratedHook(
fixture,
'tool.execute.before',
beforeInput,
beforeOutput,
);
assert.equal(guard.code, 0, guard.stderr);
assert.equal(api.requests.some(({ method }) => method === 'GET'), true);
const afterInput = { ...beforeInput, args: beforeOutput.args };
const afterOutput = { title: 'Shell', output: 'test', metadata: { exit: 0 } };
const audit = await runGeneratedHook(
fixture,
'tool.execute.after',
afterInput,
afterOutput,
);
assert.equal(audit.code, 0, audit.stderr);
const request = api.requests.find(({ method }) => method === 'POST');
assert.ok(request);
const operation = JSON.parse(request.raw);
assert.deepEqual(operation.action, { tool: 'bash' });
assert.deepEqual(operation.subject, { session_id: 'session-1' });
assert.deepEqual(operation.payload, {
hook_event_name: 'tool.execute.after',
tool_name: 'bash',
tool_input: { command: 'echo test' },
session_id: 'session-1',
call_id: 'call-1',
input: afterInput,
output: afterOutput,
});
api.setOperationStatus(503);
const failedAudit = await runGeneratedHook(
fixture,
'tool.execute.after',
afterInput,
afterOutput,
);
assert.equal(failedAudit.code, 0, failedAudit.stderr);
assert.match(failedAudit.stderr, /OpenCode audit runtime failed: \[Elydora audit\]/);
assert.match(
await readFile(path.join(fixture.agentDir, 'error.log'), 'utf-8'),
/Audit API returned HTTP 503/,
);
await rm(path.join(fixture.agentDir, 'status-cache.json'));
api.setStatus('frozen');
const frozen = await runGeneratedHook(
fixture,
'tool.execute.before',
beforeInput,
beforeOutput,
);
assert.equal(frozen.code, 1);
assert.match(frozen.stderr, /Agent "opencode" is frozen.*Tool execution blocked/s);
} finally {
await api.close();
await fixture.close();
await rm(capturePath, { force: true });
}
});
test('OpenCode status requires both generated runtime scripts', async () => {
const fixture = await createFixture({ guardSource: 'process.exit(0);\n' });
test('OpenCode bridge forwards both native arguments and surfaces runtime failures', async () => {
const fixture = await createFixture();
const guardCapture = path.join(fixture.projectDir, 'guard-input.json');
const auditCapture = path.join(fixture.projectDir, 'audit-input.json');
try {
assert.equal((await readPluginStatus(fixture.homeDir)).installed, true);
assert.equal((await fixture.install()).code, 0);
await writeFile(fixture.guardScriptPath, `
const fs = require('node:fs');
const chunks = [];
process.stdin.on('data', (chunk) => chunks.push(chunk));
process.stdin.on('end', () => {
fs.writeFileSync(${JSON.stringify(guardCapture)}, Buffer.concat(chunks));
process.stderr.write('[guard warning] degraded status API\\n');
});
`);
const beforeInput = { tool: 'write', sessionID: 'session-2', callID: 'call-2' };
const beforeOutput = { args: { filePath: 'a.txt', content: 'value' } };
const guard = await runGeneratedHook(
fixture,
'tool.execute.before',
beforeInput,
beforeOutput,
);
assert.equal(guard.code, 0, guard.stderr);
assert.match(guard.stderr, /guard warning/);
assert.deepEqual(JSON.parse(await readFile(guardCapture, 'utf-8')), {
hook_event_name: 'tool.execute.before',
tool_name: 'write',
tool_input: beforeOutput.args,
session_id: 'session-2',
call_id: 'call-2',
input: beforeInput,
output: beforeOutput,
});
await writeFile(fixture.hookScriptPath, `
const fs = require('node:fs');
const chunks = [];
process.stdin.on('data', (chunk) => chunks.push(chunk));
process.stdin.on('end', () => {
fs.writeFileSync(${JSON.stringify(auditCapture)}, Buffer.concat(chunks));
process.stderr.write('audit transport failed\\n');
process.exitCode = 3;
});
`);
const afterInput = { ...beforeInput, args: beforeOutput.args };
const afterOutput = { title: 'Write', output: 'saved', metadata: { bytes: 5 } };
const audit = await runGeneratedHook(
fixture,
'tool.execute.after',
afterInput,
afterOutput,
);
assert.equal(audit.code, 0, audit.stderr);
assert.match(audit.stderr, /OpenCode audit runtime failed: audit transport failed/);
assert.deepEqual(JSON.parse(await readFile(auditCapture, 'utf-8')).input, afterInput);
await rm(fixture.guardScriptPath);
assert.equal((await readPluginStatus(fixture.homeDir)).installed, false);
const missing = await runGeneratedHook(
fixture,
'tool.execute.before',
beforeInput,
beforeOutput,
);
assert.equal(missing.code, 1);
assert.match(missing.stderr, /Elydora guard failed/);
} finally {
await fixture.close();
}
});
test('OpenCode status requires an exact plugin, runtime identity, and clean migration state', async (t) => {
await t.test('tampered managed plugin', async () => {
const fixture = await createFixture();
try {
assert.equal((await fixture.install()).code, 0);
await writeFile(fixture.pluginPath, `${await readFile(fixture.pluginPath, 'utf-8')}\n// tampered\n`);
const status = await runPlugin(fixture, 'status', null);
assert.equal(status.code, 1);
assert.match(status.stderr, /does not match the managed template/);
} finally {
await fixture.close();
}
});
for (const [label, target, contents, expected] of [
['guard runtime', 'guard', 'tampered\n', /"installed":false/],
['runtime config', 'config', '{ malformed', /parse Elydora runtime config/i],
['runtime identity', 'config', JSON.stringify({
org_id: 'org-1',
agent_id: 'different-agent',
kid: 'kid-1',
base_url: 'http://127.0.0.1:9',
token: 'token-1',
agent_name: 'opencode',
}), /runtime identity/i],
['private key', 'key', 'invalid', /private key is invalid/i],
]) {
await t.test(label, async () => {
const fixture = await createFixture();
try {
assert.equal((await fixture.install()).code, 0);
const targets = {
guard: fixture.guardScriptPath,
config: path.join(fixture.agentDir, 'config.json'),
key: path.join(fixture.agentDir, 'private.key'),
};
await writeFile(targets[target], contents);
const status = await runPlugin(fixture, 'status', null);
assert.match(`${status.stdout}\n${status.stderr}`, expected);
} finally {
await fixture.close();
}
});
}
await t.test('stale owned mjs source', async () => {
const fixture = await createFixture();
try {
assert.equal((await fixture.install()).code, 0);
await writeSource(fixture.legacyPluginPath, await legacySource(fixture));
let status = await runPlugin(fixture, 'status', null);
assert.equal(JSON.parse(status.stdout).hookConfigured, false);
assert.equal((await fixture.install()).code, 0);
await assertMissing(fixture.legacyPluginPath);
status = await runPlugin(fixture, 'status', null);
assert.equal(JSON.parse(status.stdout).installed, true);
} finally {
await fixture.close();
}
});
});
test('OpenCode rejects collisions, invalid inputs, and linked plugin paths before writes', async (t) => {
await t.test('user plugin collision', async () => {
const source = 'export const UserPlugin = async () => ({});\n';
const fixture = await createFixture({ existingPlugin: source });
try {
const result = await fixture.install();
assert.equal(result.code, 1);
assert.match(result.stderr, /owned by another integration/);
assert.equal(await readFile(fixture.pluginPath, 'utf-8'), source);
await assertMissing(fixture.guardScriptPath);
} finally {
await fixture.close();
}
});
await t.test('non-canonical private key', async () => {
const fixture = await createFixture();
try {
const result = await fixture.install({ privateKey: 'invalid' });
assert.equal(result.code, 1);
assert.match(result.stderr, /canonical 32-byte/);
await assertMissing(fixture.pluginPath);
} finally {
await fixture.close();
}
});
await t.test('relative XDG_CONFIG_HOME', async () => {
const fixture = await createFixture();
try {
const source = `
import { opencodePlugin } from ${JSON.stringify(
pathToFileURL(path.resolve('dist/plugins/opencode.js')).href,
)};
await opencodePlugin.status();
`;
const result = await runNode(
['--input-type=module', '--eval', source],
{ HOME: fixture.homeDir, USERPROFILE: fixture.homeDir, XDG_CONFIG_HOME: 'relative' },
fixture.projectDir,
);
assert.equal(result.code, 1);
assert.match(result.stderr, /XDG_CONFIG_HOME must be an absolute path/);
} finally {
await fixture.close();
}
});
await t.test('linked plugins directory', async () => {
const fixture = await createFixture();
const external = path.join(fixture.rootDir, 'external-plugins');
try {
await mkdir(external);
await mkdir(path.dirname(fixture.pluginsDir), { recursive: true });
await symlink(external, fixture.pluginsDir, process.platform === 'win32' ? 'junction' : 'dir');
const result = await fixture.install();
assert.equal(result.code, 1);
assert.match(result.stderr, /plugins directory is not a physical directory/i);
await assertMissing(fixture.guardScriptPath);
} finally {
await fixture.close();
}
});
});
test('OpenCode migrates exact legacy ownership and preserves unrelated mjs bytes', async (t) => {
await t.test('install and uninstall exact ownership', async () => {
const fixture = await createFixture();
try {
await writeSource(fixture.legacyPluginPath, await legacySource(fixture));
assert.equal((await fixture.install()).code, 0);
await assertMissing(fixture.legacyPluginPath);
const uninstall = await runPlugin(fixture, 'uninstall', fixture.agentId);
assert.equal(uninstall.code, 0, uninstall.stderr);
await assertMissing(fixture.pluginPath);
} finally {
await fixture.close();
}
});
await t.test('unrelated legacy file', async () => {
const source = 'export const UserPlugin = async () => ({});\n';
const fixture = await createFixture({ existingLegacy: source });
try {
assert.equal((await fixture.install()).code, 0);
assert.equal(await readFile(fixture.legacyPluginPath, 'utf-8'), source);
assert.equal((await runPlugin(fixture, 'uninstall', fixture.agentId)).code, 0);
assert.equal(await readFile(fixture.legacyPluginPath, 'utf-8'), source);
} finally {
await fixture.close();
}
});
await t.test('legacy-only uninstall', async () => {
const fixture = await createFixture();
try {
await writeSource(fixture.legacyPluginPath, await legacySource(fixture));
assert.equal((await runPlugin(fixture, 'uninstall', fixture.agentId)).code, 0);
await assertMissing(fixture.legacyPluginPath);
} finally {
await fixture.close();
}
});
});
test('OpenCode CLI completes install, status, and uninstall end to end', async () => {
const fixture = await createFixture();
const privateKeyFile = path.join(fixture.projectDir, 'private-key.txt');
const tokenFile = path.join(fixture.projectDir, 'token.txt');
try {
await writeFile(privateKeyFile, `${VALID_PRIVATE_KEY}\n`, { mode: 0o600 });
await writeFile(tokenFile, 'token-1\n', { mode: 0o600 });
const install = await runNode([
cliPath, 'install', '--agent', 'opencode', '--org_id', 'org-1',
'--agent_id', fixture.agentId, '--kid', 'kid-1',
'--private_key_file', privateKeyFile, '--token_file', tokenFile,
'--base_url', fixture.baseUrl,
], environment(fixture), fixture.projectDir);
assert.equal(install.code, 0, install.stderr);
const status = await runNode([cliPath, 'status'], environment(fixture), fixture.projectDir);
assert.equal(status.code, 0, status.stderr);
assert.match(status.stdout, /OpenCode \(opencode\) \[installed\]/);
const uninstall = await runNode([
cliPath, 'uninstall', '--agent', 'opencode', '--agent_id', fixture.agentId,
], environment(fixture), fixture.projectDir);
assert.equal(uninstall.code, 0, uninstall.stderr);
await assertMissing(fixture.pluginPath);
await assertMissing(fixture.agentDir);
} finally {
await fixture.close();
}
+110
View File
@@ -0,0 +1,110 @@
import assert from 'node:assert/strict';
import { lstat, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
import {
contractModuleUrl,
createFixture,
environment,
installConfig,
installationModuleUrl,
ioModuleUrl,
runNode,
} from '../test-support/opencode-test-helpers.mjs';
async function assertMissing(filePath) {
await assert.rejects(lstat(filePath), { code: 'ENOENT' });
}
async function runPreparedInstallation(fixture, { failLegacy = false, racePlugin } = {}) {
const source = `
import fsp from 'node:fs/promises';
import path from 'node:path';
import {
commitOpenCodeInstallation,
prepareOpenCodeInstallation,
} from ${JSON.stringify(installationModuleUrl)};
import { readOpenCodeSources } from ${JSON.stringify(ioModuleUrl)};
const config = JSON.parse(process.env.ELYDORA_TEST_CONFIG);
const sources = await readOpenCodeSources();
const prepared = await prepareOpenCodeInstallation(config, sources);
if (process.env.ELYDORA_TEST_RACE_PLUGIN) {
await fsp.mkdir(path.dirname(sources.paths.pluginPath), { recursive: true });
await fsp.writeFile(sources.paths.pluginPath, process.env.ELYDORA_TEST_RACE_PLUGIN);
}
let failed = false;
await commitOpenCodeInstallation(prepared, async (from, to) => {
if (!failed
&& process.env.ELYDORA_TEST_FAIL_LEGACY === 'true'
&& from === sources.paths.legacyPluginPath
&& to.endsWith('.rollback')) {
failed = true;
throw new Error('injected OpenCode legacy migration failure');
}
await fsp.rename(from, to);
});
`;
return runNode(
['--input-type=module', '--eval', source],
{
...environment(fixture),
ELYDORA_TEST_CONFIG: JSON.stringify(installConfig(fixture)),
ELYDORA_TEST_FAIL_LEGACY: String(failLegacy),
ELYDORA_TEST_RACE_PLUGIN: racePlugin,
},
fixture.projectDir,
);
}
async function assertNoTransactionFiles(...directories) {
const names = [];
for (const directory of directories) {
try {
names.push(...await readdir(directory));
} catch (error) {
if (error.code !== 'ENOENT') throw error;
}
}
assert.equal(names.some((name) => /\.(tmp|rollback)$/.test(name)), false, names.join(', '));
}
test('OpenCode rolls back plugin, legacy migration, and runtime after a late failure', async () => {
const fixture = await createFixture();
const { buildLegacyOpenCodePlugin } = await import(contractModuleUrl);
const legacy = buildLegacyOpenCodePlugin(fixture.hookScriptPath, fixture.guardScriptPath);
try {
await mkdir(path.dirname(fixture.legacyPluginPath), { recursive: true });
await writeFile(fixture.legacyPluginPath, legacy);
const result = await runPreparedInstallation(fixture, { failLegacy: true });
assert.equal(result.code, 1);
assert.match(result.stderr, /injected OpenCode legacy migration failure/);
assert.equal(await readFile(fixture.legacyPluginPath, 'utf-8'), legacy);
await assertMissing(fixture.pluginPath);
for (const filePath of [
fixture.guardScriptPath,
fixture.hookScriptPath,
path.join(fixture.agentDir, 'config.json'),
path.join(fixture.agentDir, 'private.key'),
]) await assertMissing(filePath);
await assertNoTransactionFiles(fixture.pluginsDir, fixture.agentDir);
} finally {
await fixture.close();
}
});
test('OpenCode detects concurrent plugin creation before committing runtime files', async () => {
const fixture = await createFixture();
const racePlugin = 'export const RacePlugin = async () => ({});\n';
try {
const result = await runPreparedInstallation(fixture, { racePlugin });
assert.equal(result.code, 1);
assert.match(result.stderr, /changed during OpenCode installation/i);
assert.equal(await readFile(fixture.pluginPath, 'utf-8'), racePlugin);
await assertMissing(fixture.guardScriptPath);
await assertMissing(fixture.hookScriptPath);
await assertNoTransactionFiles(fixture.pluginsDir, fixture.agentDir);
} finally {
await fixture.close();
}
});