fix(opencode): install discoverable atomic plugin
This commit is contained in:
1 parent
3f43f4aad5
commit
69a75153f5
15 files changed
+1554
-348
No files matched your search
+40
-111
@@ -1,134 +1,63 @@
|
||||
import fsp from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import type { AgentPlugin, InstallConfig, PluginStatus } from './base.js';
|
||||
import { AGENT_KEY } from './opencode-contract.js';
|
||||
import {
|
||||
commitOpenCodeInstallation,
|
||||
commitOpenCodeUninstall,
|
||||
preflightOpenCodeInstallation,
|
||||
prepareOpenCodeInstallation,
|
||||
prepareOpenCodeUninstall,
|
||||
} from './opencode-installation.js';
|
||||
import {
|
||||
openCodeRuntimeFilesExist,
|
||||
readOpenCodeSources,
|
||||
requireAvailableOpenCodePlugin,
|
||||
} from './opencode-io.js';
|
||||
import { SUPPORTED_AGENTS } from './registry.js';
|
||||
|
||||
const AGENT_KEY = 'opencode';
|
||||
const entry = SUPPORTED_AGENTS.get(AGENT_KEY)!;
|
||||
|
||||
function resolveConfigDir(): string {
|
||||
return path.join(os.homedir(), entry.configDir);
|
||||
}
|
||||
|
||||
function resolveConfigPath(): string {
|
||||
return path.join(resolveConfigDir(), entry.configFile);
|
||||
}
|
||||
|
||||
function generatePluginScript(hookScriptPath: string, guardScriptPath: string): string {
|
||||
return `// Elydora Audit Plugin for OpenCode
|
||||
// Generated by @elydora/sdk — DO NOT EDIT
|
||||
|
||||
import { spawn, spawnSync } from 'node:child_process';
|
||||
|
||||
const HOOK_SCRIPT_PATH = ${JSON.stringify(hookScriptPath)};
|
||||
const GUARD_SCRIPT_PATH = ${JSON.stringify(guardScriptPath)};
|
||||
|
||||
export const ElydoraAuditPlugin = async (ctx) => {
|
||||
return {
|
||||
"tool.execute.before": async () => {
|
||||
// Guard — blocks tool if agent is frozen
|
||||
let result;
|
||||
try {
|
||||
result = spawnSync('node', [GUARD_SCRIPT_PATH], {
|
||||
timeout: 5000,
|
||||
stdio: ['pipe', 'ignore', 'pipe'],
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error('Elydora guard failed: ' + error.message);
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
throw new Error('Elydora guard failed: ' + result.error.message);
|
||||
}
|
||||
if (result.status !== 0) {
|
||||
const message = result.stderr?.toString().trim() || 'Guard exited with status ' + result.status;
|
||||
const prefix = result.status === 2 ? '' : 'Elydora guard failed: ';
|
||||
throw new Error(prefix + message);
|
||||
}
|
||||
},
|
||||
|
||||
"tool.execute.after": async (input, output) => {
|
||||
const data = JSON.stringify({
|
||||
tool_name: input?.tool || 'unknown',
|
||||
tool_input: input?.args || output?.args || {},
|
||||
session_id: input?.sessionID || ctx?.project?.name || 'unknown',
|
||||
});
|
||||
const child = spawn('node', [HOOK_SCRIPT_PATH], {
|
||||
detached: true,
|
||||
stdio: ['pipe', 'ignore', 'ignore'],
|
||||
});
|
||||
child.on('error', (error) => {
|
||||
console.error('[elydora] audit hook failed:', error.message);
|
||||
});
|
||||
child.stdin.on('error', (error) => {
|
||||
console.error('[elydora] audit input failed:', error.message);
|
||||
});
|
||||
child.stdin.end(data);
|
||||
child.unref();
|
||||
},
|
||||
};
|
||||
};
|
||||
`;
|
||||
function validateSources(sources: Awaited<ReturnType<typeof readOpenCodeSources>>): void {
|
||||
requireAvailableOpenCodePlugin(sources.current);
|
||||
}
|
||||
|
||||
export const opencodePlugin: AgentPlugin = {
|
||||
async install(config: InstallConfig): Promise<void> {
|
||||
const configDir = resolveConfigDir();
|
||||
await fsp.mkdir(configDir, { recursive: true });
|
||||
managesRuntime: true,
|
||||
|
||||
const configPath = resolveConfigPath();
|
||||
const script = generatePluginScript(config.hookScriptPath, config.guardScriptPath);
|
||||
|
||||
await fsp.writeFile(configPath, script, 'utf-8');
|
||||
async preflightInstall(config: InstallConfig): Promise<void> {
|
||||
const sources = await readOpenCodeSources();
|
||||
validateSources(sources);
|
||||
await preflightOpenCodeInstallation(config, sources);
|
||||
},
|
||||
|
||||
async uninstall(_agentId?: string): Promise<void> {
|
||||
const configPath = resolveConfigPath();
|
||||
try {
|
||||
await fsp.unlink(configPath);
|
||||
} catch {
|
||||
// Already removed
|
||||
}
|
||||
async install(config: InstallConfig): Promise<void> {
|
||||
const sources = await readOpenCodeSources();
|
||||
validateSources(sources);
|
||||
const prepared = await prepareOpenCodeInstallation(config, sources);
|
||||
await commitOpenCodeInstallation(prepared);
|
||||
console.log(` OpenCode global plugin: ${sources.paths.pluginPath}`);
|
||||
console.log(' OpenCode activation: restart active OpenCode sessions.');
|
||||
},
|
||||
|
||||
async uninstall(agentId?: string): Promise<void> {
|
||||
const sources = await readOpenCodeSources();
|
||||
const prepared = await prepareOpenCodeUninstall(sources, agentId);
|
||||
await commitOpenCodeUninstall(prepared);
|
||||
},
|
||||
|
||||
async status(): Promise<PluginStatus> {
|
||||
const configPath = resolveConfigPath();
|
||||
|
||||
let hookConfigured = false;
|
||||
let hookScriptPath = '';
|
||||
let guardScriptPath = '';
|
||||
try {
|
||||
const raw = await fsp.readFile(configPath, 'utf-8');
|
||||
const hookMatch = raw.match(/const HOOK_SCRIPT_PATH = ("(?:\\.|[^"\\])*");/);
|
||||
const guardMatch = raw.match(/const GUARD_SCRIPT_PATH = ("(?:\\.|[^"\\])*");/);
|
||||
if (hookMatch && guardMatch) {
|
||||
hookScriptPath = JSON.parse(hookMatch[1]);
|
||||
guardScriptPath = JSON.parse(guardMatch[1]);
|
||||
hookConfigured = true;
|
||||
}
|
||||
} catch {
|
||||
// File doesn't exist
|
||||
}
|
||||
|
||||
let hookScriptExists = false;
|
||||
if (hookScriptPath && guardScriptPath) {
|
||||
try {
|
||||
await fsp.access(hookScriptPath);
|
||||
await fsp.access(guardScriptPath);
|
||||
hookScriptExists = true;
|
||||
} catch {
|
||||
// File doesn't exist
|
||||
}
|
||||
}
|
||||
|
||||
const sources = await readOpenCodeSources();
|
||||
validateSources(sources);
|
||||
const contract = sources.legacy.contract ? undefined : sources.current.contract;
|
||||
const hookConfigured = contract !== undefined;
|
||||
const hookScriptExists = contract !== undefined
|
||||
&& await openCodeRuntimeFilesExist(contract);
|
||||
return {
|
||||
installed: hookConfigured && hookScriptExists,
|
||||
agentName: AGENT_KEY,
|
||||
displayName: entry.name,
|
||||
hookConfigured,
|
||||
hookScriptExists,
|
||||
configPath,
|
||||
configPath: sources.paths.pluginPath,
|
||||
};
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user