@lightcone-ai/daemon 0.10.1 → 0.10.3
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 +1 -1
- package/src/agent-manager.js +110 -88
- 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 +9 -2
|
@@ -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
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) {
|
|
@@ -46,7 +144,6 @@ export class AgentManager {
|
|
|
46
144
|
// key → true (spawn in progress)
|
|
47
145
|
this.starting = new Set();
|
|
48
146
|
this.directiveRefreshTimers = new Map();
|
|
49
|
-
this.directiveGovernanceCache = new Map();
|
|
50
147
|
}
|
|
51
148
|
|
|
52
149
|
_key(agentId, workspaceId) {
|
|
@@ -160,90 +257,6 @@ export class AgentManager {
|
|
|
160
257
|
return arg;
|
|
161
258
|
}
|
|
162
259
|
|
|
163
|
-
_normalizeDirectiveForLocalSpawn(directive, {
|
|
164
|
-
agentId,
|
|
165
|
-
workspaceId,
|
|
166
|
-
workspaceDir,
|
|
167
|
-
chatBridgePath,
|
|
168
|
-
config,
|
|
169
|
-
}) {
|
|
170
|
-
const authToken = config?.authToken || this.machineApiKey;
|
|
171
|
-
const userId = config?.userId ?? 'default';
|
|
172
|
-
const profileRoot = path.join(homedir(), '.lightcone', 'chrome-profiles');
|
|
173
|
-
const replacements = {
|
|
174
|
-
'${SERVER_URL}': this.serverUrl,
|
|
175
|
-
'${MACHINE_API_KEY}': authToken,
|
|
176
|
-
'${AGENT_ID}': agentId,
|
|
177
|
-
'${WORKSPACE_ID}': workspaceId ?? '',
|
|
178
|
-
'${WORKSPACE_DIR}': workspaceDir,
|
|
179
|
-
'${XHS_PROFILE_DIR}': path.join(profileRoot, `xhs-${userId}`),
|
|
180
|
-
'${DOUYIN_PROFILE_DIR}': path.join(profileRoot, `douyin-${userId}`),
|
|
181
|
-
'${KUAISHOU_PROFILE_DIR}': path.join(profileRoot, `kuaishou-${userId}`),
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
const normalized = this._replaceDirectiveValue(normalizeObject(directive), replacements);
|
|
185
|
-
const mcpServers = normalizeObject(normalized.mcp_servers);
|
|
186
|
-
const governanceLease = normalizeObject(normalized.policy_lease);
|
|
187
|
-
const leaseValidUntil = Date.parse(governanceLease.valid_until ?? '');
|
|
188
|
-
const leaseState = Number.isFinite(leaseValidUntil) && leaseValidUntil > Date.now() ? 'valid' : 'expired';
|
|
189
|
-
const governanceEnv = normalized.spawn_bundle_id || normalized.policy_version || normalized.directive_id
|
|
190
|
-
? {
|
|
191
|
-
LC_GOVERNANCE_DIRECTIVE_ID: normalized.directive_id ?? '',
|
|
192
|
-
LC_GOVERNANCE_SPAWN_BUNDLE_ID: normalized.spawn_bundle_id ?? '',
|
|
193
|
-
LC_GOVERNANCE_POLICY_VERSION: normalized.policy_version ?? '',
|
|
194
|
-
LC_GOVERNANCE_POLICY_LEASE_ID: governanceLease.lease_id ?? '',
|
|
195
|
-
LC_GOVERNANCE_POLICY_LEASE_STATE: leaseState,
|
|
196
|
-
LC_GOVERNANCE_POLICY_LEASE_JSON: JSON.stringify(governanceLease),
|
|
197
|
-
LC_GOVERNANCE_MCP_CLASSIFICATION_JSON: JSON.stringify(normalizeObject(normalized.mcp_classification)),
|
|
198
|
-
LC_GOVERNANCE_WORKSPACE_ID: workspaceId ?? '',
|
|
199
|
-
}
|
|
200
|
-
: {};
|
|
201
|
-
|
|
202
|
-
for (const [serverKey, rawServer] of Object.entries(mcpServers)) {
|
|
203
|
-
const server = normalizeObject(rawServer);
|
|
204
|
-
const args = Array.isArray(server.args) ? server.args.map((arg) => this._resolveDirectiveArgPath(String(arg), { chatBridgePath })) : [];
|
|
205
|
-
const env = Object.fromEntries(Object.entries(normalizeObject(server.env)).map(([k, v]) => [k, String(v ?? '')]));
|
|
206
|
-
const baseEnv = {};
|
|
207
|
-
if (serverKey === 'chat' || serverKey === 'publisher' || serverKey === 'platform') {
|
|
208
|
-
baseEnv.SERVER_URL = this.serverUrl;
|
|
209
|
-
baseEnv.MACHINE_API_KEY = authToken;
|
|
210
|
-
baseEnv.AGENT_ID = agentId;
|
|
211
|
-
baseEnv.WORKSPACE_ID = workspaceId ?? '';
|
|
212
|
-
baseEnv.WORKSPACE_DIR = workspaceDir;
|
|
213
|
-
} else if (serverKey === 'workspace-migrate') {
|
|
214
|
-
baseEnv.SERVER_URL = this.serverUrl;
|
|
215
|
-
baseEnv.MACHINE_API_KEY = authToken;
|
|
216
|
-
baseEnv.AGENT_ID = agentId;
|
|
217
|
-
}
|
|
218
|
-
mcpServers[serverKey] = {
|
|
219
|
-
...server,
|
|
220
|
-
args,
|
|
221
|
-
env: {
|
|
222
|
-
...env,
|
|
223
|
-
...baseEnv,
|
|
224
|
-
...(serverKey === 'chat' ? governanceEnv : {}),
|
|
225
|
-
},
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
normalized.mcp_servers = mcpServers;
|
|
230
|
-
return normalized;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
_cacheDirectiveGovernanceContract(key, normalizedDirective, workspaceId) {
|
|
234
|
-
const lease = normalizeObject(normalizedDirective?.policy_lease);
|
|
235
|
-
const cached = {
|
|
236
|
-
directiveId: normalizedDirective?.directive_id ?? null,
|
|
237
|
-
spawnBundleId: normalizedDirective?.spawn_bundle_id ?? null,
|
|
238
|
-
policyVersion: normalizedDirective?.policy_version ?? null,
|
|
239
|
-
leaseId: lease.lease_id ?? null,
|
|
240
|
-
workspaceId: workspaceId ?? null,
|
|
241
|
-
updatedAt: new Date().toISOString(),
|
|
242
|
-
};
|
|
243
|
-
this.directiveGovernanceCache.set(key, cached);
|
|
244
|
-
return cached;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
260
|
_replaceDirectiveValue(value, replacements) {
|
|
248
261
|
if (typeof value === 'string') return replacePlaceholders(value, replacements);
|
|
249
262
|
if (Array.isArray(value)) return value.map((item) => this._replaceDirectiveValue(item, replacements));
|
|
@@ -321,6 +334,7 @@ export class AgentManager {
|
|
|
321
334
|
agentId,
|
|
322
335
|
workspaceId,
|
|
323
336
|
startupMsg,
|
|
337
|
+
config,
|
|
324
338
|
}) {
|
|
325
339
|
const systemPrompt = typeof directive?.system_prompt === 'string'
|
|
326
340
|
? directive.system_prompt
|
|
@@ -330,13 +344,19 @@ export class AgentManager {
|
|
|
330
344
|
? `${codexBasePrompt}\n\nNew message received:\n\n${this._formatDeliveryText(startupMsg.message)}\n\nRespond as appropriate. Complete all required work before stopping.`
|
|
331
345
|
: codexBasePrompt;
|
|
332
346
|
|
|
347
|
+
const authToken = config?.authToken || this.machineApiKey;
|
|
348
|
+
const userId = config?.userId ?? 'default';
|
|
349
|
+
const profileRoot = path.join(homedir(), '.lightcone', 'chrome-profiles');
|
|
333
350
|
const baseReplacements = {
|
|
334
351
|
'__CHAT_BRIDGE_PATH__': chatBridgePath,
|
|
335
352
|
'__WORKSPACE_DIR__': workspaceDir,
|
|
336
353
|
'__SERVER_URL__': this.serverUrl,
|
|
337
|
-
'__MACHINE_API_KEY__':
|
|
354
|
+
'__MACHINE_API_KEY__': authToken,
|
|
338
355
|
'__AGENT_ID__': agentId,
|
|
339
356
|
'__WORKSPACE_ID__': workspaceId ?? '',
|
|
357
|
+
'${XHS_PROFILE_DIR}': path.join(profileRoot, `xhs-${userId}`),
|
|
358
|
+
'${DOUYIN_PROFILE_DIR}': path.join(profileRoot, `douyin-${userId}`),
|
|
359
|
+
'${KUAISHOU_PROFILE_DIR}': path.join(profileRoot, `kuaishou-${userId}`),
|
|
340
360
|
};
|
|
341
361
|
const mcpServers = this._resolveDirectiveMcpServers(directive, baseReplacements);
|
|
342
362
|
|
|
@@ -434,6 +454,7 @@ export class AgentManager {
|
|
|
434
454
|
session_id: config?.sessionId ?? null,
|
|
435
455
|
workspace_dir: workspaceDir,
|
|
436
456
|
chat_bridge_path: chatBridgePath,
|
|
457
|
+
mcp_paths: collectLocalMcpPaths(),
|
|
437
458
|
},
|
|
438
459
|
agent_profile: {
|
|
439
460
|
name: config?.name ?? null,
|
|
@@ -638,6 +659,7 @@ export class AgentManager {
|
|
|
638
659
|
agentId,
|
|
639
660
|
workspaceId,
|
|
640
661
|
startupMsg,
|
|
662
|
+
config: runtimeConfig,
|
|
641
663
|
});
|
|
642
664
|
} catch (err) {
|
|
643
665
|
console.log(`[AgentManager] Failed to build spawn plan for ${agentId}: ${err.message}`);
|
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
|
@@ -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');
|