Files
Elydora-Open-Source/sdks/node/src/plugins/opencode.ts
T

135 lines
4.0 KiB
TypeScript

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 { 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();
},
};
};
`;
}
export const opencodePlugin: AgentPlugin = {
async install(config: InstallConfig): Promise<void> {
const configDir = resolveConfigDir();
await fsp.mkdir(configDir, { recursive: true });
const configPath = resolveConfigPath();
const script = generatePluginScript(config.hookScriptPath, config.guardScriptPath);
await fsp.writeFile(configPath, script, 'utf-8');
},
async uninstall(_agentId?: string): Promise<void> {
const configPath = resolveConfigPath();
try {
await fsp.unlink(configPath);
} catch {
// Already removed
}
},
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
}
}
return {
installed: hookConfigured && hookScriptExists,
agentName: AGENT_KEY,
displayName: entry.name,
hookConfigured,
hookScriptExists,
configPath,
};
},
};