-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.js
More file actions
153 lines (139 loc) · 4.21 KB
/
Copy pathserver.js
File metadata and controls
153 lines (139 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 EvoMap
//
// OpenCode server plugin entrypoint.
//
// OpenCode loads npm server plugins from package `main` / `exports["./server"]`
// and invokes exported plugin functions with a context object. This module
// bridges OpenCode events to the same clean-room Evolver hook scripts used by
// the Claude Code and Cursor plugins.
'use strict';
const { spawnSync } = require('node:child_process');
const fs = require('node:fs');
const path = require('node:path');
const DEFAULT_HOOKS_DIR = path.join(__dirname, 'hooks');
const WRITE_TOOLS = new Set(['write', 'edit', 'multiedit', 'multi_edit']);
function timeoutMs(envName, fallback) {
const raw = Number.parseInt(process.env[envName] || '', 10);
return Number.isFinite(raw) && raw > 0 ? raw : fallback;
}
function resolveHooksDir() {
const override = process.env.EVOLVER_OPENCODE_HOOKS_DIR;
return override && path.isAbsolute(override) ? override : DEFAULT_HOOKS_DIR;
}
function resolveWorkingDir(ctx) {
const candidates = [
ctx && ctx.worktree,
ctx && ctx.directory,
ctx && ctx.project && ctx.project.path,
ctx && ctx.project && ctx.project.directory,
process.cwd(),
];
for (const candidate of candidates) {
if (typeof candidate !== 'string' || candidate.length === 0) continue;
try {
if (fs.statSync(candidate).isDirectory()) return candidate;
} catch (_err) {
// try the next candidate
}
}
return process.cwd();
}
function runHook(scriptName, payload, timeoutMs, ctx) {
const cwd = resolveWorkingDir(ctx);
try {
const result = spawnSync('node', [path.join(resolveHooksDir(), scriptName)], {
cwd,
input: JSON.stringify(payload || {}),
encoding: 'utf8',
timeout: timeoutMs,
env: {
...process.env,
OPENCODE_PROJECT_DIR: cwd,
},
});
if (!result || !result.stdout) return {};
try {
return JSON.parse(result.stdout);
} catch (_err) {
return {};
}
} catch (_err) {
return {};
}
}
function sessionIdFromEvent(event) {
if (!event || typeof event !== 'object') return undefined;
const props = event.properties || {};
return (
props.sessionID ||
props.sessionId ||
props.session_id ||
(props.info && props.info.id)
);
}
function normalizeToolName(input) {
if (!input || typeof input !== 'object') return '';
const raw = input.tool || input.name || input.toolName || '';
return String(raw).toLowerCase();
}
function toolArgs(input, output) {
if (output && typeof output.args === 'object') return output.args;
if (input && typeof input.args === 'object') return input.args;
if (input && typeof input.input === 'object') return input.input;
if (input && typeof input.parameters === 'object') return input.parameters;
return {};
}
function toolResult(output) {
if (!output || typeof output !== 'object') return {};
return output.output || output.result || output.response || {};
}
async function Evolver(ctx = {}) {
return {
event: async ({ event } = {}) => {
if (!event || typeof event.type !== 'string') return;
if (event.type === 'session.created') {
runHook(
'session-start.js',
{ session_id: sessionIdFromEvent(event), source: 'opencode' },
timeoutMs('EVOLVER_OPENCODE_SESSION_START_TIMEOUT_MS', 3000),
ctx
);
return;
}
if (event.type === 'session.idle') {
runHook(
'session-end.js',
{ session_id: sessionIdFromEvent(event), source: 'opencode' },
timeoutMs('EVOLVER_OPENCODE_SESSION_END_TIMEOUT_MS', 8000),
ctx
);
}
},
'tool.execute.after': async (input, output) => {
if (!WRITE_TOOLS.has(normalizeToolName(input))) return;
runHook(
'signal-detect.js',
{
source: 'opencode',
tool_input: toolArgs(input, output),
tool_response: toolResult(output),
},
timeoutMs('EVOLVER_OPENCODE_SIGNAL_TIMEOUT_MS', 2000),
ctx
);
},
};
}
module.exports = { Evolver };
module.exports.default = Evolver;
module.exports._private = {
resolveHooksDir,
resolveWorkingDir,
runHook,
timeoutMs,
normalizeToolName,
toolArgs,
toolResult,
WRITE_TOOLS,
};