@lightcone-ai/daemon 0.10.0 → 0.10.2
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/mcp-servers/platform/index.js +1 -1
- package/package.json +4 -2
- package/src/_vendor/mcp/registry.js +327 -0
- package/src/agent-manager.js +101 -2
- package/src/chat-bridge.js +1 -1
- package/src/drivers/claude.js +3 -3
- package/src/index.js +1 -1
- package/src/mcp-config.js +10 -3
|
@@ -3,7 +3,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
|
6
|
-
const SERVER_URL = process.env.PLATFORM_SERVER_URL ?? process.env.SERVER_URL ?? 'http://localhost:
|
|
6
|
+
const SERVER_URL = process.env.PLATFORM_SERVER_URL ?? process.env.SERVER_URL ?? 'http://localhost:9779';
|
|
7
7
|
const MACHINE_API_KEY = process.env.PLATFORM_MACHINE_API_KEY ?? process.env.MACHINE_API_KEY ?? '';
|
|
8
8
|
const AGENT_ID = process.env.PLATFORM_AGENT_ID ?? process.env.AGENT_ID ?? '';
|
|
9
9
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightcone-ai/daemon",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
15
|
"start": "node src/index.js",
|
|
16
|
-
"dev": "node --watch src/index.js"
|
|
16
|
+
"dev": "node --watch src/index.js",
|
|
17
|
+
"prepack": "node scripts/vendor-shared.js",
|
|
18
|
+
"postpack": "node scripts/unvendor-shared.js"
|
|
17
19
|
},
|
|
18
20
|
"dependencies": {
|
|
19
21
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const MANIFEST_FILE = 'manifest.json';
|
|
6
|
+
const ALLOWED_ROOT_RELATIVE_PATHS = Object.freeze([
|
|
7
|
+
'mcp-servers',
|
|
8
|
+
'daemon/mcp-servers',
|
|
9
|
+
]);
|
|
10
|
+
const VALID_TOOL_CLASSIFICATIONS = new Set(['mandatory', 'cacheable', 'local']);
|
|
11
|
+
|
|
12
|
+
let cachedRegistry = null;
|
|
13
|
+
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isPathInside(parentDir, targetPath) {
|
|
19
|
+
const relative = path.relative(parentDir, targetPath);
|
|
20
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeServerId(value, fallback = '') {
|
|
24
|
+
const normalized = String(value ?? fallback).trim().toLowerCase();
|
|
25
|
+
if (!normalized) return '';
|
|
26
|
+
if (!/^[a-z0-9][a-z0-9_-]*$/.test(normalized)) return '';
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeToolDeclarations(manifest, serverId) {
|
|
31
|
+
const declarations = [];
|
|
32
|
+
|
|
33
|
+
const direct = Array.isArray(manifest.tool_declarations)
|
|
34
|
+
? manifest.tool_declarations
|
|
35
|
+
: Array.isArray(manifest.toolDeclarations)
|
|
36
|
+
? manifest.toolDeclarations
|
|
37
|
+
: [];
|
|
38
|
+
|
|
39
|
+
for (const item of direct) {
|
|
40
|
+
if (typeof item === 'string') {
|
|
41
|
+
const name = String(item).trim();
|
|
42
|
+
if (!name) continue;
|
|
43
|
+
declarations.push({ name, classification: null });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!isPlainObject(item)) continue;
|
|
47
|
+
const name = String(item.name ?? item.tool ?? '').trim();
|
|
48
|
+
if (!name) continue;
|
|
49
|
+
const classification = String(item.classification ?? item.tool_classification ?? '').trim().toLowerCase();
|
|
50
|
+
declarations.push({
|
|
51
|
+
name,
|
|
52
|
+
classification: VALID_TOOL_CLASSIFICATIONS.has(classification) ? classification : null,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (declarations.length > 0) {
|
|
57
|
+
return dedupeToolDeclarations(declarations);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const toolNames = Array.isArray(manifest.tool_names)
|
|
61
|
+
? manifest.tool_names
|
|
62
|
+
: Array.isArray(manifest.toolNames)
|
|
63
|
+
? manifest.toolNames
|
|
64
|
+
: [];
|
|
65
|
+
const declaredClassification = isPlainObject(manifest.tool_classification)
|
|
66
|
+
? manifest.tool_classification
|
|
67
|
+
: isPlainObject(manifest.toolClassification)
|
|
68
|
+
? manifest.toolClassification
|
|
69
|
+
: {};
|
|
70
|
+
|
|
71
|
+
for (const toolName of toolNames) {
|
|
72
|
+
const name = String(toolName ?? '').trim();
|
|
73
|
+
if (!name) continue;
|
|
74
|
+
const classification = String(declaredClassification[name] ?? '').trim().toLowerCase();
|
|
75
|
+
declarations.push({
|
|
76
|
+
name,
|
|
77
|
+
classification: VALID_TOOL_CLASSIFICATIONS.has(classification) ? classification : null,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (declarations.length === 0 && serverId === 'chat') {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return dedupeToolDeclarations(declarations);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function dedupeToolDeclarations(declarations) {
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
const output = [];
|
|
91
|
+
for (const declaration of declarations) {
|
|
92
|
+
const key = declaration.name;
|
|
93
|
+
if (seen.has(key)) continue;
|
|
94
|
+
seen.add(key);
|
|
95
|
+
output.push(declaration);
|
|
96
|
+
}
|
|
97
|
+
return output;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function normalizeSmokeTest(manifest) {
|
|
101
|
+
const raw = manifest.smoke_test ?? manifest.smokeTest;
|
|
102
|
+
if (!isPlainObject(raw)) return null;
|
|
103
|
+
const tool = String(raw.tool ?? raw.tool_name ?? '').trim();
|
|
104
|
+
if (!tool) return null;
|
|
105
|
+
const args = isPlainObject(raw.arguments) ? raw.arguments : {};
|
|
106
|
+
return { tool, arguments: args };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function collectManifestFiles(rootDir, maxDepth = 4) {
|
|
110
|
+
const files = [];
|
|
111
|
+
|
|
112
|
+
function walk(currentDir, depth) {
|
|
113
|
+
if (depth > maxDepth) return;
|
|
114
|
+
let entries = [];
|
|
115
|
+
try {
|
|
116
|
+
entries = readdirSync(currentDir, { withFileTypes: true });
|
|
117
|
+
} catch {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
123
|
+
if (entry.isDirectory()) {
|
|
124
|
+
walk(fullPath, depth + 1);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (!entry.isFile() || entry.name !== MANIFEST_FILE) continue;
|
|
128
|
+
files.push(fullPath);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
walk(rootDir, 0);
|
|
133
|
+
return files;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function resolveAllowedRoots(repoRoot) {
|
|
137
|
+
return ALLOWED_ROOT_RELATIVE_PATHS
|
|
138
|
+
.map((relativePath) => path.resolve(repoRoot, relativePath))
|
|
139
|
+
.filter((absolutePath) => existsSync(absolutePath));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function normalizeManifestEntry({
|
|
143
|
+
rawManifest,
|
|
144
|
+
manifestPath,
|
|
145
|
+
repoRoot,
|
|
146
|
+
allowedRoots,
|
|
147
|
+
}) {
|
|
148
|
+
if (!isPlainObject(rawManifest)) {
|
|
149
|
+
throw new Error(`manifest root must be object: ${manifestPath}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const manifestDir = path.dirname(manifestPath);
|
|
153
|
+
const fallbackId = path.basename(manifestDir);
|
|
154
|
+
const id = normalizeServerId(rawManifest.id, fallbackId);
|
|
155
|
+
if (!id) {
|
|
156
|
+
throw new Error(`invalid server id in manifest: ${manifestPath}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const entrypointRaw = String(rawManifest.entrypoint ?? 'index.js').trim();
|
|
160
|
+
if (!entrypointRaw) {
|
|
161
|
+
throw new Error(`entrypoint is required: ${manifestPath}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const entrypointPath = path.resolve(manifestDir, entrypointRaw);
|
|
165
|
+
const insideAllowedBoundary = allowedRoots.some((root) => isPathInside(root, entrypointPath));
|
|
166
|
+
if (!insideAllowedBoundary) {
|
|
167
|
+
throw new Error(`entrypoint escapes allowed roots for '${id}': ${entrypointPath}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const entrypointExists = (() => {
|
|
171
|
+
try {
|
|
172
|
+
return statSync(entrypointPath).isFile();
|
|
173
|
+
} catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
})();
|
|
177
|
+
|
|
178
|
+
const toolDeclarations = normalizeToolDeclarations(rawManifest, id);
|
|
179
|
+
const toolClassification = {};
|
|
180
|
+
for (const declaration of toolDeclarations) {
|
|
181
|
+
if (!declaration.classification) continue;
|
|
182
|
+
toolClassification[declaration.name] = declaration.classification;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
id,
|
|
187
|
+
name: String(rawManifest.name ?? id).trim() || id,
|
|
188
|
+
version: String(rawManifest.version ?? '0.0.0').trim() || '0.0.0',
|
|
189
|
+
manifestPath,
|
|
190
|
+
manifestDir,
|
|
191
|
+
entrypointPath,
|
|
192
|
+
entrypointExists,
|
|
193
|
+
runtime: String(rawManifest.runtime ?? 'node').trim() || 'node',
|
|
194
|
+
toolDeclarations,
|
|
195
|
+
toolClassification,
|
|
196
|
+
smokeTest: normalizeSmokeTest(rawManifest),
|
|
197
|
+
rawManifest,
|
|
198
|
+
repoRoot,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function buildRegistry({ repoRoot }) {
|
|
203
|
+
const normalizedRepoRoot = path.resolve(repoRoot);
|
|
204
|
+
const allowedRoots = resolveAllowedRoots(normalizedRepoRoot);
|
|
205
|
+
const manifestFiles = allowedRoots.flatMap((root) => collectManifestFiles(root));
|
|
206
|
+
|
|
207
|
+
const errors = [];
|
|
208
|
+
const servers = new Map();
|
|
209
|
+
|
|
210
|
+
for (const manifestPath of manifestFiles) {
|
|
211
|
+
let rawManifest = null;
|
|
212
|
+
try {
|
|
213
|
+
rawManifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
214
|
+
} catch (error) {
|
|
215
|
+
errors.push({
|
|
216
|
+
manifestPath,
|
|
217
|
+
code: 'manifest_parse_failed',
|
|
218
|
+
message: error.message,
|
|
219
|
+
});
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let entry;
|
|
224
|
+
try {
|
|
225
|
+
entry = normalizeManifestEntry({
|
|
226
|
+
rawManifest,
|
|
227
|
+
manifestPath,
|
|
228
|
+
repoRoot: normalizedRepoRoot,
|
|
229
|
+
allowedRoots,
|
|
230
|
+
});
|
|
231
|
+
} catch (error) {
|
|
232
|
+
errors.push({
|
|
233
|
+
manifestPath,
|
|
234
|
+
code: 'manifest_invalid',
|
|
235
|
+
message: error.message,
|
|
236
|
+
});
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (servers.has(entry.id)) {
|
|
241
|
+
const previous = servers.get(entry.id);
|
|
242
|
+
errors.push({
|
|
243
|
+
manifestPath,
|
|
244
|
+
code: 'duplicate_server_id',
|
|
245
|
+
message: `duplicate server id '${entry.id}' (${previous.manifestPath} vs ${entry.manifestPath})`,
|
|
246
|
+
});
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
servers.set(entry.id, entry);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
repoRoot: normalizedRepoRoot,
|
|
255
|
+
allowedRoots,
|
|
256
|
+
manifestFiles,
|
|
257
|
+
servers,
|
|
258
|
+
errors,
|
|
259
|
+
loadedAt: new Date().toISOString(),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function defaultRepoRoot() {
|
|
264
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
265
|
+
return path.resolve(path.dirname(currentFile), '..', '..');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function loadMcpServerRegistry({
|
|
269
|
+
repoRoot = defaultRepoRoot(),
|
|
270
|
+
refresh = false,
|
|
271
|
+
strict = false,
|
|
272
|
+
} = {}) {
|
|
273
|
+
const normalizedRepoRoot = path.resolve(repoRoot);
|
|
274
|
+
|
|
275
|
+
if (!refresh && cachedRegistry && cachedRegistry.repoRoot === normalizedRepoRoot) {
|
|
276
|
+
if (strict && cachedRegistry.errors.length > 0) {
|
|
277
|
+
throw new Error(`mcp manifest registry has ${cachedRegistry.errors.length} error(s)`);
|
|
278
|
+
}
|
|
279
|
+
return cachedRegistry;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const registry = buildRegistry({ repoRoot: normalizedRepoRoot });
|
|
283
|
+
cachedRegistry = registry;
|
|
284
|
+
|
|
285
|
+
if (strict && registry.errors.length > 0) {
|
|
286
|
+
throw new Error(`mcp manifest registry has ${registry.errors.length} error(s)`);
|
|
287
|
+
}
|
|
288
|
+
return registry;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function getMcpServerManifest(serverId, options = {}) {
|
|
292
|
+
const id = normalizeServerId(serverId);
|
|
293
|
+
if (!id) return null;
|
|
294
|
+
const registry = loadMcpServerRegistry(options);
|
|
295
|
+
return registry.servers.get(id) ?? null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function resolveMcpServerEntrypoint(serverId, options = {}) {
|
|
299
|
+
const entry = getMcpServerManifest(serverId, options);
|
|
300
|
+
if (!entry) return null;
|
|
301
|
+
return entry.entrypointPath;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function getMcpToolClassificationMap(options = {}) {
|
|
305
|
+
const registry = loadMcpServerRegistry(options);
|
|
306
|
+
const classification = {};
|
|
307
|
+
|
|
308
|
+
for (const server of registry.servers.values()) {
|
|
309
|
+
for (const [toolName, toolClass] of Object.entries(server.toolClassification)) {
|
|
310
|
+
if (!VALID_TOOL_CLASSIFICATIONS.has(toolClass)) continue;
|
|
311
|
+
classification[toolName] = toolClass;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return classification;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function getMcpRegistryDiagnostics(options = {}) {
|
|
319
|
+
const registry = loadMcpServerRegistry(options);
|
|
320
|
+
return {
|
|
321
|
+
repoRoot: registry.repoRoot,
|
|
322
|
+
loadedAt: registry.loadedAt,
|
|
323
|
+
manifestCount: registry.manifestFiles.length,
|
|
324
|
+
serverCount: registry.servers.size,
|
|
325
|
+
errors: registry.errors,
|
|
326
|
+
};
|
|
327
|
+
}
|
package/src/agent-manager.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
statSync,
|
|
8
|
+
unlinkSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from 'fs';
|
|
3
11
|
import { homedir } from 'os';
|
|
4
12
|
import path from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
5
14
|
import { parseCodexLine, adaptCodexSystemPrompt } from './drivers/codex.js';
|
|
6
15
|
import { parseKimiLine, encodeKimiStdin } from './drivers/kimi.js';
|
|
7
16
|
import { startSession, stopSession, stopAllSessions } from './browser-login.js';
|
|
@@ -10,6 +19,95 @@ import { markInvalidatedLeases } from './governance-state.js';
|
|
|
10
19
|
const KIMI_SYSTEM_PROMPT_FILE = '.lightcone-kimi-system.md';
|
|
11
20
|
const KIMI_AGENT_FILE = '.lightcone-kimi-agent.yaml';
|
|
12
21
|
const KIMI_MCP_FILE = '.lightcone-kimi-mcp.json';
|
|
22
|
+
const LOCAL_FILE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const LOCAL_MCP_ROOTS = Object.freeze([
|
|
24
|
+
path.resolve(LOCAL_FILE_DIR, '../mcp-servers'),
|
|
25
|
+
path.resolve(LOCAL_FILE_DIR, '../../mcp-servers'),
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
function isPlainObject(value) {
|
|
29
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeMcpServerId(value, fallback = '') {
|
|
33
|
+
const normalized = String(value ?? fallback).trim().toLowerCase();
|
|
34
|
+
if (!normalized) return '';
|
|
35
|
+
if (!/^[a-z0-9][a-z0-9_-]*$/.test(normalized)) return '';
|
|
36
|
+
return normalized;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isFile(filePath) {
|
|
40
|
+
try {
|
|
41
|
+
return statSync(filePath).isFile();
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function collectMcpPathsFromRoot(rootDir, output) {
|
|
48
|
+
if (!existsSync(rootDir)) return;
|
|
49
|
+
|
|
50
|
+
const pending = [rootDir];
|
|
51
|
+
const visited = new Set();
|
|
52
|
+
const fallbackCandidates = new Map();
|
|
53
|
+
|
|
54
|
+
while (pending.length > 0) {
|
|
55
|
+
const currentDir = pending.pop();
|
|
56
|
+
if (visited.has(currentDir)) continue;
|
|
57
|
+
visited.add(currentDir);
|
|
58
|
+
|
|
59
|
+
const manifestPath = path.join(currentDir, 'manifest.json');
|
|
60
|
+
if (isFile(manifestPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
63
|
+
const serverId = normalizeMcpServerId(manifest?.id, path.basename(currentDir));
|
|
64
|
+
const entrypointRaw = String(manifest?.entrypoint ?? 'index.js').trim() || 'index.js';
|
|
65
|
+
const entrypointPath = path.resolve(currentDir, entrypointRaw);
|
|
66
|
+
if (serverId && isFile(entrypointPath) && !Object.hasOwn(output, serverId)) {
|
|
67
|
+
output[serverId] = entrypointPath;
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Ignore malformed manifest and keep scanning sibling directories.
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
const fallbackId = normalizeMcpServerId(path.basename(currentDir));
|
|
74
|
+
const fallbackEntrypoint = path.join(currentDir, 'index.js');
|
|
75
|
+
if (
|
|
76
|
+
currentDir !== rootDir
|
|
77
|
+
&& fallbackId
|
|
78
|
+
&& isFile(fallbackEntrypoint)
|
|
79
|
+
&& !Object.hasOwn(output, fallbackId)
|
|
80
|
+
&& !fallbackCandidates.has(fallbackId)
|
|
81
|
+
) {
|
|
82
|
+
fallbackCandidates.set(fallbackId, fallbackEntrypoint);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let entries = [];
|
|
87
|
+
try {
|
|
88
|
+
entries = readdirSync(currentDir, { withFileTypes: true });
|
|
89
|
+
} catch {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
if (!entry.isDirectory()) continue;
|
|
94
|
+
pending.push(path.join(currentDir, entry.name));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const [serverId, entrypointPath] of fallbackCandidates.entries()) {
|
|
99
|
+
if (Object.hasOwn(output, serverId)) continue;
|
|
100
|
+
output[serverId] = entrypointPath;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function collectLocalMcpPaths() {
|
|
105
|
+
const output = {};
|
|
106
|
+
for (const rootDir of LOCAL_MCP_ROOTS) {
|
|
107
|
+
collectMcpPathsFromRoot(rootDir, output);
|
|
108
|
+
}
|
|
109
|
+
return output;
|
|
110
|
+
}
|
|
13
111
|
|
|
14
112
|
function runtimeMissingDetail(runtime) {
|
|
15
113
|
return `cli_missing:${runtime}`;
|
|
@@ -25,7 +123,7 @@ function resolveExitOfflineDetail({ code, signal, stopCause }) {
|
|
|
25
123
|
}
|
|
26
124
|
|
|
27
125
|
function normalizeObject(value) {
|
|
28
|
-
return
|
|
126
|
+
return isPlainObject(value) ? value : {};
|
|
29
127
|
}
|
|
30
128
|
|
|
31
129
|
function replacePlaceholders(text, replacements) {
|
|
@@ -434,6 +532,7 @@ export class AgentManager {
|
|
|
434
532
|
session_id: config?.sessionId ?? null,
|
|
435
533
|
workspace_dir: workspaceDir,
|
|
436
534
|
chat_bridge_path: chatBridgePath,
|
|
535
|
+
mcp_paths: collectLocalMcpPaths(),
|
|
437
536
|
},
|
|
438
537
|
agent_profile: {
|
|
439
538
|
name: config?.name ?? null,
|
package/src/chat-bridge.js
CHANGED
|
@@ -14,7 +14,7 @@ function getArg(name) {
|
|
|
14
14
|
return idx !== -1 && cliArgs[idx + 1] ? cliArgs[idx + 1] : '';
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const SERVER_URL = process.env.SERVER_URL || getArg('--server-url') || 'http://localhost:
|
|
17
|
+
const SERVER_URL = process.env.SERVER_URL || getArg('--server-url') || 'http://localhost:9779';
|
|
18
18
|
const MACHINE_API_KEY = process.env.MACHINE_API_KEY || getArg('--auth-token') || '';
|
|
19
19
|
const AGENT_ID = process.env.AGENT_ID || getArg('--agent-id') || '';
|
|
20
20
|
const WORKSPACE_ID = process.env.WORKSPACE_ID || getArg('--workspace-id') || ''; // injected per-workspace at spawn time
|
package/src/drivers/claude.js
CHANGED
|
@@ -169,9 +169,9 @@ When referencing a workspace or mentioning someone, write them as plain text wit
|
|
|
169
169
|
|
|
170
170
|
When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.), always wrap the URL in angle brackets or use markdown link syntax. Otherwise the punctuation may be rendered as part of the URL.
|
|
171
171
|
|
|
172
|
-
- **Wrong**: \`测试环境:http://localhost:
|
|
173
|
-
- **Correct**: \`测试环境:<http://localhost:
|
|
174
|
-
- **Also correct**: \`测试环境:[http://localhost:
|
|
172
|
+
- **Wrong**: \`测试环境:http://localhost:9779,请查看\` (the \`,\` gets swallowed into the link)
|
|
173
|
+
- **Correct**: \`测试环境:<http://localhost:9779>,请查看\`
|
|
174
|
+
- **Also correct**: \`测试环境:[http://localhost:9779](http://localhost:9779),请查看\`
|
|
175
175
|
|
|
176
176
|
## Filesystem Access
|
|
177
177
|
|
package/src/index.js
CHANGED
|
@@ -39,7 +39,7 @@ if (opts['--help'] || opts['-h']) {
|
|
|
39
39
|
process.exit(0);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
const SERVER_URL = String(opts['--server-url'] || process.env.SERVER_URL || 'http://localhost:
|
|
42
|
+
const SERVER_URL = String(opts['--server-url'] || process.env.SERVER_URL || 'http://localhost:9779').trim();
|
|
43
43
|
const MACHINE_API_KEY = String(opts['--api-key'] || process.env.MACHINE_API_KEY || '').trim();
|
|
44
44
|
|
|
45
45
|
if (!MACHINE_API_KEY) {
|
package/src/mcp-config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { profileDir } from './browser-login.js';
|
|
2
|
-
import { resolveMcpServerEntrypoint } from '
|
|
2
|
+
import { resolveMcpServerEntrypoint } from './_vendor/mcp/registry.js';
|
|
3
3
|
|
|
4
4
|
const LEGACY_MCP_PATH_TOKENS = Object.freeze({
|
|
5
5
|
'{mysql_mcp_path}': 'mysql',
|
|
@@ -16,7 +16,7 @@ const LEGACY_MCP_PATH_TOKENS = Object.freeze({
|
|
|
16
16
|
'{portfolio_analysis_mcp_path}': 'portfolio-analysis',
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
function resolveMcpPathToken(arg) {
|
|
19
|
+
function resolveMcpPathToken(arg, mcpPaths = {}) {
|
|
20
20
|
if (typeof arg !== 'string') return null;
|
|
21
21
|
|
|
22
22
|
const trimmed = arg.trim();
|
|
@@ -27,6 +27,13 @@ function resolveMcpPathToken(arg) {
|
|
|
27
27
|
const serverId = legacyId ?? (dynamicMatch ? dynamicMatch[1].toLowerCase() : null);
|
|
28
28
|
if (!serverId) return null;
|
|
29
29
|
|
|
30
|
+
const runtimeMcpPath = typeof mcpPaths?.[serverId] === 'string'
|
|
31
|
+
? mcpPaths[serverId].trim()
|
|
32
|
+
: '';
|
|
33
|
+
if (runtimeMcpPath) {
|
|
34
|
+
return runtimeMcpPath;
|
|
35
|
+
}
|
|
36
|
+
|
|
30
37
|
const entrypointPath = resolveMcpServerEntrypoint(serverId, { strict: true });
|
|
31
38
|
if (!entrypointPath) {
|
|
32
39
|
throw new Error(`MCP server '${serverId}' is not registered in manifest registry`);
|
|
@@ -35,7 +42,7 @@ function resolveMcpPathToken(arg) {
|
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
function resolveSkillArg(arg, config) {
|
|
38
|
-
const resolvedMcpPath = resolveMcpPathToken(arg);
|
|
45
|
+
const resolvedMcpPath = resolveMcpPathToken(arg, config?.mcpPaths);
|
|
39
46
|
if (resolvedMcpPath) return resolvedMcpPath;
|
|
40
47
|
if (arg === '{xhs_profile_dir}')
|
|
41
48
|
return profileDir('xhs', config.userId ?? 'default');
|