@evomap/evolver-mcp 2.0.0-beta.0 → 2.0.0-beta.10
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.
- package/dist/antigravityInstaller.d.ts +32 -0
- package/dist/antigravityInstaller.js +271 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/injection.d.ts +4 -3
- package/dist/injection.js +21 -10
- package/dist/installer.d.ts +51 -6
- package/dist/installer.js +28 -6
- package/dist/jsonMcpInstaller.d.ts +75 -0
- package/dist/jsonMcpInstaller.js +824 -0
- package/dist/kiroInstaller.d.ts +10 -0
- package/dist/kiroInstaller.js +146 -0
- package/dist/opencodeInstaller.d.ts +18 -0
- package/dist/opencodeInstaller.js +531 -0
- package/dist/proxyClient.d.ts +17 -0
- package/dist/proxyClient.js +25 -0
- package/dist/tools.js +61 -0
- package/package.json +7 -2
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { homedir, userInfo } from 'node:os';
|
|
4
|
+
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from 'node:path';
|
|
5
|
+
import { installJsonMcpRuntime, McpConfigChangedError, McpConfigConflictError, McpConfigOwnershipError, McpConfigShapeError, uninstallJsonMcpRuntime, } from './jsonMcpInstaller.js';
|
|
6
|
+
function configuredPath(value) {
|
|
7
|
+
const trimmed = value?.trim();
|
|
8
|
+
return trimmed ? (isAbsolute(trimmed) ? trimmed : resolve(trimmed)) : undefined;
|
|
9
|
+
}
|
|
10
|
+
export function resolveOpenCodeManagedConfigDir(opts = {}) {
|
|
11
|
+
const platform = opts.opencodePlatform ?? process.platform;
|
|
12
|
+
if (platform === 'darwin')
|
|
13
|
+
return '/Library/Application Support/opencode';
|
|
14
|
+
if (platform === 'win32') {
|
|
15
|
+
return win32.join(opts.opencodeProgramData ?? process.env['ProgramData'] ?? 'C:\\ProgramData', 'opencode');
|
|
16
|
+
}
|
|
17
|
+
return '/etc/opencode';
|
|
18
|
+
}
|
|
19
|
+
export function resolveOpenCodeManagedPreferencePaths(opts = {}) {
|
|
20
|
+
if ((opts.opencodePlatform ?? process.platform) !== 'darwin')
|
|
21
|
+
return [];
|
|
22
|
+
let username = opts.opencodeUsername;
|
|
23
|
+
if (username === undefined) {
|
|
24
|
+
try {
|
|
25
|
+
username = userInfo().username || 'user';
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
username = 'user';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return [
|
|
32
|
+
posix.join('/Library/Managed Preferences', username, 'ai.opencode.managed.plist'),
|
|
33
|
+
posix.join('/Library/Managed Preferences', 'ai.opencode.managed.plist'),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
function isRecord(value) {
|
|
37
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
38
|
+
}
|
|
39
|
+
function valuesEqual(left, right) {
|
|
40
|
+
if (Object.is(left, right))
|
|
41
|
+
return true;
|
|
42
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
43
|
+
return Array.isArray(left) && Array.isArray(right)
|
|
44
|
+
&& left.length === right.length
|
|
45
|
+
&& left.every((value, index) => valuesEqual(value, right[index]));
|
|
46
|
+
}
|
|
47
|
+
if (!isRecord(left) || !isRecord(right))
|
|
48
|
+
return false;
|
|
49
|
+
const leftKeys = Object.keys(left).sort();
|
|
50
|
+
const rightKeys = Object.keys(right).sort();
|
|
51
|
+
return leftKeys.length === rightKeys.length
|
|
52
|
+
&& leftKeys.every((key, index) => key === rightKeys[index] && valuesEqual(left[key], right[key]));
|
|
53
|
+
}
|
|
54
|
+
function mergeOpenCodeValue(target, source) {
|
|
55
|
+
if (!isRecord(target) || !isRecord(source))
|
|
56
|
+
return source;
|
|
57
|
+
const merged = { ...target };
|
|
58
|
+
for (const [key, value] of Object.entries(source)) {
|
|
59
|
+
// Define user-controlled keys as data so `__proto__` cannot invoke the legacy object setter.
|
|
60
|
+
Object.defineProperty(merged, key, {
|
|
61
|
+
configurable: true,
|
|
62
|
+
enumerable: true,
|
|
63
|
+
value: mergeOpenCodeValue(merged[key], value),
|
|
64
|
+
writable: true,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return merged;
|
|
68
|
+
}
|
|
69
|
+
function mergeEffectiveEntry(current, entry, source) {
|
|
70
|
+
if (!entry.present)
|
|
71
|
+
return current;
|
|
72
|
+
return {
|
|
73
|
+
present: true,
|
|
74
|
+
value: current.present ? mergeOpenCodeValue(current.value, entry.value) : entry.value,
|
|
75
|
+
source,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function readReadOnlySource(path) {
|
|
79
|
+
if (!existsSync(path))
|
|
80
|
+
return { path, raw: null };
|
|
81
|
+
const stat = lstatSync(path);
|
|
82
|
+
if (stat.isSymbolicLink()) {
|
|
83
|
+
throw new McpConfigOwnershipError('opencode', 'a layered configuration is a symbolic link');
|
|
84
|
+
}
|
|
85
|
+
if (!stat.isFile()) {
|
|
86
|
+
throw new McpConfigOwnershipError('opencode', 'a layered configuration is not a regular file');
|
|
87
|
+
}
|
|
88
|
+
return { path, raw: readFileSync(path, 'utf8') };
|
|
89
|
+
}
|
|
90
|
+
function parseReadOnlyJsonc(raw, source) {
|
|
91
|
+
let withoutComments = '';
|
|
92
|
+
let inString = false;
|
|
93
|
+
let escaped = false;
|
|
94
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
95
|
+
const char = raw[index];
|
|
96
|
+
const next = raw[index + 1];
|
|
97
|
+
if (inString) {
|
|
98
|
+
withoutComments += char;
|
|
99
|
+
if (escaped)
|
|
100
|
+
escaped = false;
|
|
101
|
+
else if (char === '\\')
|
|
102
|
+
escaped = true;
|
|
103
|
+
else if (char === '"')
|
|
104
|
+
inString = false;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (char === '"') {
|
|
108
|
+
inString = true;
|
|
109
|
+
withoutComments += char;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (char === '/' && next === '/') {
|
|
113
|
+
withoutComments += ' ';
|
|
114
|
+
index += 1;
|
|
115
|
+
while (index + 1 < raw.length && raw[index + 1] !== '\n' && raw[index + 1] !== '\r') {
|
|
116
|
+
withoutComments += ' ';
|
|
117
|
+
index += 1;
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (char === '/' && next === '*') {
|
|
122
|
+
withoutComments += ' ';
|
|
123
|
+
index += 1;
|
|
124
|
+
let closed = false;
|
|
125
|
+
while (index + 1 < raw.length) {
|
|
126
|
+
const current = raw[index + 1];
|
|
127
|
+
const following = raw[index + 2];
|
|
128
|
+
if (current === '*' && following === '/') {
|
|
129
|
+
withoutComments += ' ';
|
|
130
|
+
index += 2;
|
|
131
|
+
closed = true;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
withoutComments += current === '\n' || current === '\r' ? current : ' ';
|
|
135
|
+
index += 1;
|
|
136
|
+
}
|
|
137
|
+
if (!closed)
|
|
138
|
+
throw new McpConfigShapeError('opencode', source, 'layered JSONC configuration has an unterminated comment');
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
withoutComments += char;
|
|
142
|
+
}
|
|
143
|
+
let normalized = '';
|
|
144
|
+
inString = false;
|
|
145
|
+
escaped = false;
|
|
146
|
+
for (let index = 0; index < withoutComments.length; index += 1) {
|
|
147
|
+
const char = withoutComments[index];
|
|
148
|
+
if (inString) {
|
|
149
|
+
normalized += char;
|
|
150
|
+
if (escaped)
|
|
151
|
+
escaped = false;
|
|
152
|
+
else if (char === '\\')
|
|
153
|
+
escaped = true;
|
|
154
|
+
else if (char === '"')
|
|
155
|
+
inString = false;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (char === '"') {
|
|
159
|
+
inString = true;
|
|
160
|
+
normalized += char;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (char === ',') {
|
|
164
|
+
let lookahead = index + 1;
|
|
165
|
+
while (/\s/.test(withoutComments[lookahead] ?? ''))
|
|
166
|
+
lookahead += 1;
|
|
167
|
+
if (withoutComments[lookahead] === '}' || withoutComments[lookahead] === ']')
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
normalized += char;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
return JSON.parse(normalized);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
throw new McpConfigShapeError('opencode', source, 'layered configuration is not valid JSON/JSONC');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function entryFromLayeredConfig(raw, source) {
|
|
180
|
+
const parsed = source.endsWith('.jsonc') ? parseReadOnlyJsonc(raw, source) : (() => {
|
|
181
|
+
try {
|
|
182
|
+
return JSON.parse(raw);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
throw new McpConfigShapeError('opencode', source, 'layered configuration is not valid JSON');
|
|
186
|
+
}
|
|
187
|
+
})();
|
|
188
|
+
if (!isRecord(parsed))
|
|
189
|
+
throw new McpConfigShapeError('opencode', source, 'layered configuration must be a JSON object');
|
|
190
|
+
const mcp = parsed['mcp'];
|
|
191
|
+
if (mcp === undefined)
|
|
192
|
+
return { present: false };
|
|
193
|
+
if (!isRecord(mcp))
|
|
194
|
+
throw new McpConfigShapeError('opencode', source, 'mcp must be a JSON object');
|
|
195
|
+
return Object.prototype.hasOwnProperty.call(mcp, 'evolver')
|
|
196
|
+
? { present: true, value: mcp['evolver'] }
|
|
197
|
+
: { present: false };
|
|
198
|
+
}
|
|
199
|
+
function uniquePaths(paths) {
|
|
200
|
+
return [...new Set(paths)];
|
|
201
|
+
}
|
|
202
|
+
function canonicalOpenCodeProjectRoot(configRoot) {
|
|
203
|
+
const directory = resolve(configRoot);
|
|
204
|
+
const parent = dirname(directory);
|
|
205
|
+
if (parent === directory)
|
|
206
|
+
return directory;
|
|
207
|
+
try {
|
|
208
|
+
// Resolve parent aliases such as macOS /tmp, but leave the project-root entry itself for the shared
|
|
209
|
+
// safe-parent guard to reject when configRoot is a caller-controlled symlink.
|
|
210
|
+
return join(realpathSync(parent), basename(directory));
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
if (error.code !== 'ENOENT')
|
|
214
|
+
throw error;
|
|
215
|
+
return join(canonicalOpenCodeProjectRoot(parent), basename(directory));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function nearestExistingCanonicalDirectory(path) {
|
|
219
|
+
let current = path;
|
|
220
|
+
while (true) {
|
|
221
|
+
try {
|
|
222
|
+
if (lstatSync(current).isDirectory())
|
|
223
|
+
return realpathSync(current);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
if (error.code !== 'ENOENT')
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
const parent = dirname(current);
|
|
230
|
+
if (parent === current)
|
|
231
|
+
return current;
|
|
232
|
+
current = parent;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function isLexicallyContained(root, path) {
|
|
236
|
+
const relativePath = relative(root, path);
|
|
237
|
+
return relativePath === ''
|
|
238
|
+
|| (!isAbsolute(relativePath) && relativePath !== '..' && !relativePath.startsWith(`..${sep}`));
|
|
239
|
+
}
|
|
240
|
+
function filesystemRoot(path) {
|
|
241
|
+
let current = path;
|
|
242
|
+
while (dirname(current) !== current)
|
|
243
|
+
current = dirname(current);
|
|
244
|
+
return current;
|
|
245
|
+
}
|
|
246
|
+
function openCodeWorktreeRoot(configRoot) {
|
|
247
|
+
const directory = canonicalOpenCodeProjectRoot(configRoot);
|
|
248
|
+
try {
|
|
249
|
+
// Keep a caller-controlled root alias as the safe root so the shared guard refuses it before ancestor
|
|
250
|
+
// discovery can retarget a managed config elsewhere in the worktree.
|
|
251
|
+
if (lstatSync(directory).isSymbolicLink())
|
|
252
|
+
return directory;
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
if (error.code !== 'ENOENT')
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
const gitCwd = nearestExistingCanonicalDirectory(directory);
|
|
259
|
+
let output;
|
|
260
|
+
try {
|
|
261
|
+
output = execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
262
|
+
cwd: gitCwd,
|
|
263
|
+
encoding: 'utf8',
|
|
264
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
265
|
+
}).trim();
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return filesystemRoot(directory);
|
|
269
|
+
}
|
|
270
|
+
if (!output)
|
|
271
|
+
return directory;
|
|
272
|
+
let canonicalWorktreeRoot;
|
|
273
|
+
try {
|
|
274
|
+
canonicalWorktreeRoot = realpathSync(output);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return directory;
|
|
278
|
+
}
|
|
279
|
+
return isLexicallyContained(canonicalWorktreeRoot, directory)
|
|
280
|
+
? canonicalWorktreeRoot
|
|
281
|
+
: directory;
|
|
282
|
+
}
|
|
283
|
+
function openCodeProjectLayerPaths(configRoot, worktreeRoot = openCodeWorktreeRoot(configRoot)) {
|
|
284
|
+
const directory = canonicalOpenCodeProjectRoot(configRoot);
|
|
285
|
+
const ancestors = [];
|
|
286
|
+
let current = directory;
|
|
287
|
+
while (true) {
|
|
288
|
+
ancestors.push(current);
|
|
289
|
+
if (current === worktreeRoot)
|
|
290
|
+
break;
|
|
291
|
+
const parent = dirname(current);
|
|
292
|
+
if (parent === current)
|
|
293
|
+
return [
|
|
294
|
+
join(directory, 'opencode.json'),
|
|
295
|
+
join(directory, 'opencode.jsonc'),
|
|
296
|
+
join(directory, '.opencode', 'opencode.json'),
|
|
297
|
+
join(directory, '.opencode', 'opencode.jsonc'),
|
|
298
|
+
];
|
|
299
|
+
current = parent;
|
|
300
|
+
}
|
|
301
|
+
return [
|
|
302
|
+
...[...ancestors].reverse().flatMap((ancestor) => [
|
|
303
|
+
join(ancestor, 'opencode.json'),
|
|
304
|
+
join(ancestor, 'opencode.jsonc'),
|
|
305
|
+
]),
|
|
306
|
+
...ancestors.flatMap((ancestor) => [
|
|
307
|
+
join(ancestor, '.opencode', 'opencode.json'),
|
|
308
|
+
join(ancestor, '.opencode', 'opencode.jsonc'),
|
|
309
|
+
]),
|
|
310
|
+
];
|
|
311
|
+
}
|
|
312
|
+
function openCodeLayerPaths(opts, projectDisabled) {
|
|
313
|
+
const home = opts.homeDir ?? homedir();
|
|
314
|
+
const xdgHome = configuredPath(opts.xdgConfigHome ?? process.env['XDG_CONFIG_HOME']) ?? join(home, '.config');
|
|
315
|
+
const globalDir = join(xdgHome, 'opencode');
|
|
316
|
+
const paths = [
|
|
317
|
+
join(globalDir, 'config.json'),
|
|
318
|
+
join(globalDir, 'opencode.json'),
|
|
319
|
+
join(globalDir, 'opencode.jsonc'),
|
|
320
|
+
];
|
|
321
|
+
const explicitConfig = configuredPath(opts.opencodeConfig ?? process.env['OPENCODE_CONFIG']);
|
|
322
|
+
if (explicitConfig)
|
|
323
|
+
paths.push(explicitConfig);
|
|
324
|
+
if ((opts.scope ?? 'project') === 'project' && !projectDisabled) {
|
|
325
|
+
paths.push(...openCodeProjectLayerPaths(opts.configRoot));
|
|
326
|
+
}
|
|
327
|
+
paths.push(join(home, '.opencode', 'opencode.json'), join(home, '.opencode', 'opencode.jsonc'));
|
|
328
|
+
const explicitDir = configuredPath(opts.opencodeConfigDir ?? process.env['OPENCODE_CONFIG_DIR']);
|
|
329
|
+
if (explicitDir)
|
|
330
|
+
paths.push(join(explicitDir, 'opencode.json'), join(explicitDir, 'opencode.jsonc'));
|
|
331
|
+
return uniquePaths(paths);
|
|
332
|
+
}
|
|
333
|
+
function entryFromStrictConfig(raw, source) {
|
|
334
|
+
let parsed;
|
|
335
|
+
try {
|
|
336
|
+
parsed = JSON.parse(raw);
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
throw new McpConfigShapeError('opencode', source, 'higher-precedence configuration is not valid strict JSON');
|
|
340
|
+
}
|
|
341
|
+
if (!isRecord(parsed)) {
|
|
342
|
+
throw new McpConfigShapeError('opencode', source, 'higher-precedence configuration must be a JSON object');
|
|
343
|
+
}
|
|
344
|
+
const mcp = parsed['mcp'];
|
|
345
|
+
if (mcp === undefined)
|
|
346
|
+
return { present: false };
|
|
347
|
+
if (!isRecord(mcp)) {
|
|
348
|
+
throw new McpConfigShapeError('opencode', source, 'mcp must be a JSON object');
|
|
349
|
+
}
|
|
350
|
+
return Object.prototype.hasOwnProperty.call(mcp, 'evolver')
|
|
351
|
+
? { present: true, value: mcp['evolver'] }
|
|
352
|
+
: { present: false };
|
|
353
|
+
}
|
|
354
|
+
function openCodeInstallPreflight(resolution, opts, expected) {
|
|
355
|
+
const disableProjectFromOptions = opts.opencodeDisableProjectConfig !== undefined;
|
|
356
|
+
const disableProjectEnvironment = process.env['OPENCODE_DISABLE_PROJECT_CONFIG'];
|
|
357
|
+
const projectDisabled = disableProjectFromOptions
|
|
358
|
+
? opts.opencodeDisableProjectConfig === true
|
|
359
|
+
: ['true', '1'].includes(disableProjectEnvironment?.toLowerCase() ?? '');
|
|
360
|
+
const projectTargetDisabled = projectDisabled
|
|
361
|
+
&& (opts.scope ?? 'project') === 'project'
|
|
362
|
+
&& !configuredPath(opts.opencodeConfigDir ?? process.env['OPENCODE_CONFIG_DIR']);
|
|
363
|
+
const layerPaths = openCodeLayerPaths(opts, projectDisabled);
|
|
364
|
+
const resolvedTargetPath = resolve(resolution.configPath);
|
|
365
|
+
const targetIndex = layerPaths.findIndex((path) => resolve(path) === resolvedTargetPath);
|
|
366
|
+
const layerSnapshots = layerPaths.map(readReadOnlySource);
|
|
367
|
+
let effectiveEntry = { present: false };
|
|
368
|
+
let plannedEffectiveEntry = { present: false };
|
|
369
|
+
for (const [index, snapshot] of layerSnapshots.entries()) {
|
|
370
|
+
const isTarget = resolve(snapshot.path) === resolvedTargetPath;
|
|
371
|
+
const source = isTarget
|
|
372
|
+
? 'target'
|
|
373
|
+
: targetIndex >= 0 && index < targetIndex ? 'lower' : 'higher';
|
|
374
|
+
const entry = snapshot.raw === null
|
|
375
|
+
? { present: false }
|
|
376
|
+
: entryFromLayeredConfig(snapshot.raw, snapshot.path);
|
|
377
|
+
effectiveEntry = mergeEffectiveEntry(effectiveEntry, entry, source);
|
|
378
|
+
plannedEffectiveEntry = mergeEffectiveEntry(plannedEffectiveEntry, isTarget ? { present: true, value: expected } : entry, source);
|
|
379
|
+
}
|
|
380
|
+
const inlineFromOptions = opts.opencodeConfigContent !== undefined;
|
|
381
|
+
const inline = inlineFromOptions ? opts.opencodeConfigContent : process.env['OPENCODE_CONFIG_CONTENT'];
|
|
382
|
+
if (inline) {
|
|
383
|
+
const entry = entryFromStrictConfig(inline, 'OPENCODE_CONFIG_CONTENT');
|
|
384
|
+
effectiveEntry = mergeEffectiveEntry(effectiveEntry, entry, 'higher');
|
|
385
|
+
plannedEffectiveEntry = mergeEffectiveEntry(plannedEffectiveEntry, entry, 'higher');
|
|
386
|
+
}
|
|
387
|
+
const managedDir = opts.opencodeManagedConfigDir
|
|
388
|
+
?? process.env['OPENCODE_TEST_MANAGED_CONFIG_DIR']
|
|
389
|
+
?? resolveOpenCodeManagedConfigDir(opts);
|
|
390
|
+
const managedPaths = [join(managedDir, 'opencode.json'), join(managedDir, 'opencode.jsonc')];
|
|
391
|
+
const managedSnapshots = managedPaths.map(readReadOnlySource);
|
|
392
|
+
for (const snapshot of managedSnapshots) {
|
|
393
|
+
if (snapshot.raw === null)
|
|
394
|
+
continue;
|
|
395
|
+
const entry = entryFromLayeredConfig(snapshot.raw, snapshot.path);
|
|
396
|
+
effectiveEntry = mergeEffectiveEntry(effectiveEntry, entry, 'higher');
|
|
397
|
+
plannedEffectiveEntry = mergeEffectiveEntry(plannedEffectiveEntry, entry, 'higher');
|
|
398
|
+
}
|
|
399
|
+
const preferencePaths = opts.opencodeManagedPreferencePaths ?? resolveOpenCodeManagedPreferencePaths(opts);
|
|
400
|
+
if (preferencePaths.some(existsSync)) {
|
|
401
|
+
throw new McpConfigOwnershipError('opencode', 'macOS managed preferences are active and cannot be safely inspected without invoking system tooling');
|
|
402
|
+
}
|
|
403
|
+
const effectiveMatches = effectiveEntry.present && valuesEqual(effectiveEntry.value, expected);
|
|
404
|
+
const plannedEffectiveMatches = plannedEffectiveEntry.present
|
|
405
|
+
&& valuesEqual(plannedEffectiveEntry.value, expected);
|
|
406
|
+
if (!projectTargetDisabled && !effectiveMatches && !plannedEffectiveMatches) {
|
|
407
|
+
throw new McpConfigConflictError('opencode', 'mcp.evolver', expected, plannedEffectiveEntry.present ? plannedEffectiveEntry.value : '<missing>');
|
|
408
|
+
}
|
|
409
|
+
if (effectiveEntry.present && !effectiveMatches && effectiveEntry.source !== 'target') {
|
|
410
|
+
const forceCanOverride = opts.force === true && !projectTargetDisabled;
|
|
411
|
+
if (!forceCanOverride) {
|
|
412
|
+
throw new McpConfigConflictError('opencode', 'mcp.evolver', expected, effectiveEntry.value);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (projectTargetDisabled && !effectiveMatches) {
|
|
416
|
+
throw new McpConfigConflictError('opencode', 'mcp.evolver', expected, effectiveEntry.present ? effectiveEntry.value : '<missing while OPENCODE_DISABLE_PROJECT_CONFIG is enabled>');
|
|
417
|
+
}
|
|
418
|
+
const alreadyInstalled = effectiveMatches && effectiveEntry.source !== 'target';
|
|
419
|
+
const guardedLayerSnapshots = alreadyInstalled
|
|
420
|
+
? layerSnapshots
|
|
421
|
+
: layerSnapshots.filter((snapshot) => resolve(snapshot.path) !== resolvedTargetPath);
|
|
422
|
+
return {
|
|
423
|
+
alreadyInstalled,
|
|
424
|
+
assertUnchanged() {
|
|
425
|
+
if (!inlineFromOptions && process.env['OPENCODE_CONFIG_CONTENT'] !== inline) {
|
|
426
|
+
throw new McpConfigChangedError('opencode', 'OPENCODE_CONFIG_CONTENT');
|
|
427
|
+
}
|
|
428
|
+
if (!disableProjectFromOptions
|
|
429
|
+
&& process.env['OPENCODE_DISABLE_PROJECT_CONFIG'] !== disableProjectEnvironment) {
|
|
430
|
+
throw new McpConfigChangedError('opencode', 'OPENCODE_DISABLE_PROJECT_CONFIG');
|
|
431
|
+
}
|
|
432
|
+
for (const snapshot of [...guardedLayerSnapshots, ...managedSnapshots]) {
|
|
433
|
+
const current = readReadOnlySource(snapshot.path);
|
|
434
|
+
if (current.raw !== snapshot.raw)
|
|
435
|
+
throw new McpConfigChangedError('opencode', snapshot.path);
|
|
436
|
+
}
|
|
437
|
+
if (preferencePaths.some(existsSync))
|
|
438
|
+
throw new McpConfigChangedError('opencode', 'macOS managed preferences');
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
/** Resolve the OpenCode file that is active at the requested scope.
|
|
443
|
+
* OpenCode layers project files from the worktree root down to the current directory, or from the filesystem root
|
|
444
|
+
* outside Git, then .opencode directories in reverse order. JSONC follows JSON at every location. Global,
|
|
445
|
+
* explicit, home, and managed layers retain their surrounding precedence. A JSONC path is writable only when its
|
|
446
|
+
* contents are strict JSON;
|
|
447
|
+
* comment/trailing-comma syntax is rejected by the shared strict parser so setup never rewrites it lossy.
|
|
448
|
+
*/
|
|
449
|
+
export function resolveOpenCodeConfig(opts) {
|
|
450
|
+
const scope = opts.scope ?? 'project';
|
|
451
|
+
const explicitDir = configuredPath(opts.opencodeConfigDir ?? process.env['OPENCODE_CONFIG_DIR']);
|
|
452
|
+
if (explicitDir) {
|
|
453
|
+
const jsonPath = join(explicitDir, 'opencode.json');
|
|
454
|
+
const jsoncPath = join(explicitDir, 'opencode.jsonc');
|
|
455
|
+
return {
|
|
456
|
+
configPath: existsSync(jsoncPath) ? jsoncPath : jsonPath,
|
|
457
|
+
safeRoot: explicitDir,
|
|
458
|
+
conflictingPaths: [],
|
|
459
|
+
evidencePaths: [explicitDir, jsonPath, jsoncPath],
|
|
460
|
+
topologyCandidatePaths: [jsonPath, jsoncPath],
|
|
461
|
+
uninstallCandidatePaths: [jsonPath, jsoncPath],
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
if (scope === 'user') {
|
|
465
|
+
const home = opts.homeDir ?? homedir();
|
|
466
|
+
const explicitConfig = configuredPath(opts.opencodeConfig ?? process.env['OPENCODE_CONFIG']);
|
|
467
|
+
if (explicitConfig) {
|
|
468
|
+
return {
|
|
469
|
+
configPath: explicitConfig,
|
|
470
|
+
safeRoot: dirname(explicitConfig),
|
|
471
|
+
conflictingPaths: [],
|
|
472
|
+
evidencePaths: [explicitConfig],
|
|
473
|
+
topologyCandidatePaths: [explicitConfig],
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
const xdgHome = configuredPath(opts.xdgConfigHome ?? process.env['XDG_CONFIG_HOME']) ?? join(home, '.config');
|
|
477
|
+
const directory = join(xdgHome, 'opencode');
|
|
478
|
+
const jsonPath = join(directory, 'opencode.json');
|
|
479
|
+
const jsoncPath = join(directory, 'opencode.jsonc');
|
|
480
|
+
return {
|
|
481
|
+
configPath: existsSync(jsoncPath) ? jsoncPath : jsonPath,
|
|
482
|
+
safeRoot: directory,
|
|
483
|
+
conflictingPaths: [],
|
|
484
|
+
evidencePaths: [directory, jsonPath, jsoncPath],
|
|
485
|
+
topologyCandidatePaths: [jsonPath, jsoncPath],
|
|
486
|
+
uninstallCandidatePaths: [jsonPath, jsoncPath],
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
const projectRoot = canonicalOpenCodeProjectRoot(opts.configRoot);
|
|
490
|
+
const rootJson = join(projectRoot, 'opencode.json');
|
|
491
|
+
const rootJsonc = join(projectRoot, 'opencode.jsonc');
|
|
492
|
+
const nestedJson = join(projectRoot, '.opencode', 'opencode.json');
|
|
493
|
+
const nestedJsonc = join(projectRoot, '.opencode', 'opencode.jsonc');
|
|
494
|
+
const target = existsSync(nestedJsonc)
|
|
495
|
+
? nestedJsonc
|
|
496
|
+
: existsSync(nestedJson)
|
|
497
|
+
? nestedJson
|
|
498
|
+
: existsSync(rootJsonc)
|
|
499
|
+
? rootJsonc
|
|
500
|
+
: rootJson;
|
|
501
|
+
const userResolution = resolveOpenCodeConfig({ ...opts, scope: 'user' });
|
|
502
|
+
const worktreeRoot = openCodeWorktreeRoot(projectRoot);
|
|
503
|
+
const projectLayerPaths = openCodeProjectLayerPaths(projectRoot, worktreeRoot);
|
|
504
|
+
return {
|
|
505
|
+
configPath: target,
|
|
506
|
+
safeRoot: projectRoot,
|
|
507
|
+
conflictingPaths: [],
|
|
508
|
+
evidencePaths: [...(userResolution.evidencePaths ?? []), ...projectLayerPaths],
|
|
509
|
+
topologyCandidatePaths: [rootJson, rootJsonc, nestedJson, nestedJsonc],
|
|
510
|
+
uninstallCandidatePaths: [rootJson, rootJsonc, nestedJson, nestedJsonc],
|
|
511
|
+
installDiscoveryPaths: projectLayerPaths,
|
|
512
|
+
installSafeRoot: worktreeRoot,
|
|
513
|
+
uninstallDiscoveryPaths: projectLayerPaths,
|
|
514
|
+
uninstallSafeRoot: worktreeRoot,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
export const OPENCODE_SPEC = {
|
|
518
|
+
runtime: 'opencode',
|
|
519
|
+
configPath: (opts) => resolveOpenCodeConfig(opts).configPath,
|
|
520
|
+
resolveConfig: resolveOpenCodeConfig,
|
|
521
|
+
installPreflight: openCodeInstallPreflight,
|
|
522
|
+
containerKey: 'mcp',
|
|
523
|
+
entry: (server) => ({
|
|
524
|
+
type: 'local',
|
|
525
|
+
command: [server.command, ...(server.args ?? [])],
|
|
526
|
+
...(server.env && Object.keys(server.env).length > 0 ? { environment: server.env } : {}),
|
|
527
|
+
enabled: true,
|
|
528
|
+
}),
|
|
529
|
+
};
|
|
530
|
+
export const installOpenCode = installJsonMcpRuntime.bind(undefined, OPENCODE_SPEC);
|
|
531
|
+
export const uninstallOpenCode = uninstallJsonMcpRuntime.bind(undefined, OPENCODE_SPEC);
|
package/dist/proxyClient.d.ts
CHANGED
|
@@ -41,6 +41,20 @@ export interface ProxyReuseResultArgs {
|
|
|
41
41
|
timeSavedSeconds?: number;
|
|
42
42
|
reason?: string;
|
|
43
43
|
}
|
|
44
|
+
export interface ProxyAgentSearchArgs {
|
|
45
|
+
query?: string;
|
|
46
|
+
signals?: string[];
|
|
47
|
+
availability?: string;
|
|
48
|
+
sort?: string;
|
|
49
|
+
order?: string;
|
|
50
|
+
cursor?: string;
|
|
51
|
+
limit?: number;
|
|
52
|
+
timeoutMs?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface ProxyAgentDiscoverArgs extends ProxyAgentSearchArgs {
|
|
55
|
+
title: string;
|
|
56
|
+
description?: string;
|
|
57
|
+
}
|
|
44
58
|
export declare class EvolverProxyClient {
|
|
45
59
|
private baseUrl;
|
|
46
60
|
private token;
|
|
@@ -52,6 +66,9 @@ export declare class EvolverProxyClient {
|
|
|
52
66
|
}): Promise<unknown>;
|
|
53
67
|
search(args: ProxySearchArgs): Promise<unknown>;
|
|
54
68
|
fetchAsset(args: ProxyFetchArgs): Promise<unknown>;
|
|
69
|
+
searchAgents(args: ProxyAgentSearchArgs): Promise<unknown>;
|
|
70
|
+
getAgentProfile(agentId: string, timeoutMs?: number): Promise<unknown>;
|
|
71
|
+
discoverAgentsForTask(args: ProxyAgentDiscoverArgs): Promise<unknown>;
|
|
55
72
|
submitAsset(asset: unknown): Promise<unknown>;
|
|
56
73
|
/** Pre-publish dry-run: the hub runs its quality + content-safety gate but stores nothing and charges no credits. */
|
|
57
74
|
validateAsset(asset: unknown): Promise<unknown>;
|
package/dist/proxyClient.js
CHANGED
|
@@ -31,6 +31,19 @@ export class EvolverProxyClient {
|
|
|
31
31
|
...(args.assetIds ? { asset_ids: args.assetIds } : {}),
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
+
searchAgents(args) {
|
|
35
|
+
return this.call('POST', '/agent/search', agentDirectoryBody(args));
|
|
36
|
+
}
|
|
37
|
+
getAgentProfile(agentId, timeoutMs) {
|
|
38
|
+
return this.call('POST', '/agent/profile', { agent_id: agentId, ...(timeoutMs !== undefined ? { timeout_ms: timeoutMs } : {}) });
|
|
39
|
+
}
|
|
40
|
+
discoverAgentsForTask(args) {
|
|
41
|
+
return this.call('POST', '/agent/discover', {
|
|
42
|
+
title: args.title,
|
|
43
|
+
...(args.description ? { description: args.description } : {}),
|
|
44
|
+
...agentDirectoryBody(args),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
34
47
|
submitAsset(asset) {
|
|
35
48
|
return this.call('POST', '/asset/submit', { assets: [asset] });
|
|
36
49
|
}
|
|
@@ -107,6 +120,18 @@ export class EvolverProxyClient {
|
|
|
107
120
|
return new Error(message);
|
|
108
121
|
}
|
|
109
122
|
}
|
|
123
|
+
function agentDirectoryBody(args) {
|
|
124
|
+
return {
|
|
125
|
+
...(args.query ? { query: args.query } : {}),
|
|
126
|
+
...(args.signals && args.signals.length > 0 ? { signals: args.signals } : {}),
|
|
127
|
+
...(args.availability ? { availability: args.availability } : {}),
|
|
128
|
+
...(args.sort ? { sort: args.sort } : {}),
|
|
129
|
+
...(args.order ? { order: args.order } : {}),
|
|
130
|
+
...(args.cursor ? { cursor: args.cursor } : {}),
|
|
131
|
+
...(args.limit !== undefined ? { limit: args.limit } : {}),
|
|
132
|
+
...(args.timeoutMs !== undefined ? { timeout_ms: args.timeoutMs } : {}),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
110
135
|
export function proxyClientFromEnv(env = process.env) {
|
|
111
136
|
const token = env['EVOLVER_IPC_TOKEN']?.trim();
|
|
112
137
|
if (!token)
|
package/dist/tools.js
CHANGED
|
@@ -375,6 +375,40 @@ export function buildEvolverTools(deps) {
|
|
|
375
375
|
}
|
|
376
376
|
if (deps.proxy) {
|
|
377
377
|
tools.push({
|
|
378
|
+
name: 'evolver_agent_search',
|
|
379
|
+
description: '按自然语言 query 或 capability signals 搜索可协作 agent;结果来自 Hub,不代表实时可用,availability=unknown 时不得推断在线。',
|
|
380
|
+
inputSchema: agentDirectorySearchSchema(),
|
|
381
|
+
handler: async (a) => deps.proxy.searchAgents(agentSearchArgs(a)),
|
|
382
|
+
}, {
|
|
383
|
+
name: 'evolver_agent_profile',
|
|
384
|
+
description: '读取 Hub 授权返回的最小安全 agent profile;不返回凭证、node secret、workspace path 或设备指纹。',
|
|
385
|
+
inputSchema: {
|
|
386
|
+
type: 'object',
|
|
387
|
+
required: ['agentId'],
|
|
388
|
+
properties: {
|
|
389
|
+
agentId: { type: 'string', minLength: 1, maxLength: hub.AGENT_DIRECTORY_MAX_AGENT_ID_LENGTH },
|
|
390
|
+
timeoutMs: { type: 'integer', minimum: 100, maximum: hub.AGENT_DIRECTORY_MAX_TIMEOUT_MS },
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
handler: async (a) => deps.proxy.getAgentProfile(str(a['agentId']), typeof a['timeoutMs'] === 'number' ? a['timeoutMs'] : undefined),
|
|
394
|
+
}, {
|
|
395
|
+
name: 'evolver_agent_discover',
|
|
396
|
+
description: '按任务标题、描述和 capability signals 发现候选 agent;分页和排序由 Hub 执行。',
|
|
397
|
+
inputSchema: {
|
|
398
|
+
...agentDirectorySearchSchema(),
|
|
399
|
+
required: ['title'],
|
|
400
|
+
properties: {
|
|
401
|
+
...agentDirectorySearchSchema()['properties'],
|
|
402
|
+
title: { type: 'string', minLength: 1, maxLength: hub.AGENT_DIRECTORY_MAX_QUERY_LENGTH },
|
|
403
|
+
description: { type: 'string', maxLength: hub.AGENT_DIRECTORY_MAX_QUERY_LENGTH },
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
handler: async (a) => deps.proxy.discoverAgentsForTask({
|
|
407
|
+
title: str(a['title']),
|
|
408
|
+
...(typeof a['description'] === 'string' ? { description: a['description'] } : {}),
|
|
409
|
+
...agentSearchArgs(a),
|
|
410
|
+
}),
|
|
411
|
+
}, {
|
|
378
412
|
name: 'evolver_asset_validate',
|
|
379
413
|
description: '通过本机 evolver-proxy 对 PHub 做发布前 dry-run 校验: 先执行与发布相同的本地脱敏/泄漏拦截, 再跑 hub 端质量门禁 + 内容安全扫描, 不落库、不计费. 返回 {valid, reason?}. 建议在 evolver_asset_publish 前调用. Capsule.gene 须非空或 ad-hoc.',
|
|
380
414
|
inputSchema: {
|
|
@@ -398,4 +432,31 @@ export function buildEvolverTools(deps) {
|
|
|
398
432
|
});
|
|
399
433
|
}
|
|
400
434
|
return tools;
|
|
435
|
+
}
|
|
436
|
+
function agentDirectorySearchSchema() {
|
|
437
|
+
return {
|
|
438
|
+
type: 'object',
|
|
439
|
+
properties: {
|
|
440
|
+
query: { type: 'string', minLength: 1, maxLength: hub.AGENT_DIRECTORY_MAX_QUERY_LENGTH },
|
|
441
|
+
signals: { type: 'array', maxItems: hub.AGENT_DIRECTORY_MAX_SIGNAL_COUNT, items: { type: 'string', minLength: 1, maxLength: hub.AGENT_DIRECTORY_MAX_SIGNAL_LENGTH } },
|
|
442
|
+
availability: { type: 'string', enum: ['online', 'busy', 'offline', 'unknown'] },
|
|
443
|
+
sort: { type: 'string', enum: ['relevance', 'reputation', 'recent', 'availability'] },
|
|
444
|
+
order: { type: 'string', enum: ['asc', 'desc'] },
|
|
445
|
+
cursor: { type: 'string', maxLength: hub.AGENT_DIRECTORY_MAX_CURSOR_LENGTH },
|
|
446
|
+
limit: { type: 'integer', minimum: 1, maximum: hub.AGENT_DIRECTORY_MAX_LIMIT },
|
|
447
|
+
timeoutMs: { type: 'integer', minimum: 100, maximum: hub.AGENT_DIRECTORY_MAX_TIMEOUT_MS },
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function agentSearchArgs(args) {
|
|
452
|
+
return {
|
|
453
|
+
...(typeof args['query'] === 'string' ? { query: args['query'] } : {}),
|
|
454
|
+
...(Array.isArray(args['signals']) ? { signals: strArray(args['signals']) ?? [] } : {}),
|
|
455
|
+
...(typeof args['availability'] === 'string' ? { availability: args['availability'] } : {}),
|
|
456
|
+
...(typeof args['sort'] === 'string' ? { sort: args['sort'] } : {}),
|
|
457
|
+
...(typeof args['order'] === 'string' ? { order: args['order'] } : {}),
|
|
458
|
+
...(typeof args['cursor'] === 'string' ? { cursor: args['cursor'] } : {}),
|
|
459
|
+
...(typeof args['limit'] === 'number' ? { limit: args['limit'] } : {}),
|
|
460
|
+
...(typeof args['timeoutMs'] === 'number' ? { timeoutMs: args['timeoutMs'] } : {}),
|
|
461
|
+
};
|
|
401
462
|
}
|