@lightcone-ai/daemon 0.6.6 → 0.6.8
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/package.json +1 -1
- package/src/agent-manager.js +36 -119
- package/src/chat-bridge.js +60 -0
- package/src/drivers/claude.js +41 -2
- package/src/drivers/codex.js +37 -26
- package/src/drivers/kimi.js +17 -24
package/package.json
CHANGED
package/src/agent-manager.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
|
-
import { mkdirSync,
|
|
2
|
+
import { mkdirSync, statSync } from 'fs';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { buildSystemPrompt } from './drivers/claude.js';
|
|
@@ -25,7 +25,6 @@ export class AgentManager {
|
|
|
25
25
|
case 'agent:start': return this._startAgent(msg, connection);
|
|
26
26
|
case 'agent:stop': return this._stopAgent(msg.agentId, msg.channelId, connection);
|
|
27
27
|
case 'agent:deliver': return this._deliverMessage(msg, connection);
|
|
28
|
-
case 'agent:skills:list': return this._listSkills(msg, connection);
|
|
29
28
|
case 'ping': return connection.send({ type: 'pong' });
|
|
30
29
|
default:
|
|
31
30
|
console.log(`[AgentManager] Unhandled: ${msg.type}`);
|
|
@@ -62,7 +61,7 @@ export class AgentManager {
|
|
|
62
61
|
return msg;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
_startAgent({ agentId, channelId, config }, connection) {
|
|
64
|
+
async _startAgent({ agentId, channelId, config }, connection) {
|
|
66
65
|
const key = this._key(agentId, channelId);
|
|
67
66
|
if (this.agents.has(key) || this.starting.has(key)) {
|
|
68
67
|
console.log(`[AgentManager] Agent ${agentId} in channel ${channelId} already registered`);
|
|
@@ -75,13 +74,25 @@ export class AgentManager {
|
|
|
75
74
|
const chatBridgePath = new URL('./chat-bridge.js', import.meta.url).pathname;
|
|
76
75
|
const startupMsg = runtime === 'codex' ? this._takePendingMessage(key) : null;
|
|
77
76
|
|
|
77
|
+
// Fetch skills index for system prompt + MCP derivation (non-blocking on failure)
|
|
78
|
+
let skills = [];
|
|
79
|
+
try {
|
|
80
|
+
const chParam = channelId ? `?channelId=${encodeURIComponent(channelId)}` : '';
|
|
81
|
+
const res = await fetch(`${this.serverUrl}/internal/agent/${agentId}/skills${chParam}`, {
|
|
82
|
+
headers: { 'Authorization': `Bearer ${this.machineApiKey}` },
|
|
83
|
+
});
|
|
84
|
+
if (res.ok) skills = await res.json();
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.log(`[AgentManager] Skills fetch failed for ${agentId} (non-fatal): ${err.message}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
78
89
|
let proc;
|
|
79
90
|
|
|
80
91
|
if (runtime === 'kimi') {
|
|
81
92
|
// ── Kimi CLI ──────────────────────────────────────────────────────────
|
|
82
93
|
const kimiSpawn = buildKimiSpawn({
|
|
83
94
|
config, agentId, channelId, workspaceDir, chatBridgePath,
|
|
84
|
-
serverUrl: this.serverUrl, machineApiKey: this.machineApiKey,
|
|
95
|
+
serverUrl: this.serverUrl, machineApiKey: this.machineApiKey, skills,
|
|
85
96
|
});
|
|
86
97
|
|
|
87
98
|
console.log(`[AgentManager] Spawning kimi for ${agentId} channel=${channelId ?? 'none'} (session=${kimiSpawn.sessionId})`);
|
|
@@ -131,6 +142,7 @@ export class AgentManager {
|
|
|
131
142
|
serverUrl: this.serverUrl,
|
|
132
143
|
machineApiKey: this.machineApiKey,
|
|
133
144
|
prompt: startupPrompt,
|
|
145
|
+
skills,
|
|
134
146
|
});
|
|
135
147
|
|
|
136
148
|
console.log(`[AgentManager] Spawning codex for ${agentId} channel=${channelId ?? 'none'} (session=${config.sessionId ?? 'new'})`);
|
|
@@ -172,27 +184,25 @@ export class AgentManager {
|
|
|
172
184
|
},
|
|
173
185
|
};
|
|
174
186
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
DB_NAME: agentEnv.MYSQL_DB ?? process.env.DB_NAME ?? '',
|
|
195
|
-
},
|
|
187
|
+
// Derive MCP servers from skills with mcp_config
|
|
188
|
+
const agentEnv = config.envVars ?? {};
|
|
189
|
+
for (const skill of skills) {
|
|
190
|
+
if (!skill.mcpConfig) continue;
|
|
191
|
+
const mc = skill.mcpConfig;
|
|
192
|
+
if (mcpServers[mc.server]) continue; // already added
|
|
193
|
+
const resolvedArgs = (mc.args ?? []).map(a =>
|
|
194
|
+
a === '{mysql_mcp_path}'
|
|
195
|
+
? new URL('../../mcp-servers/mysql/index.js', import.meta.url).pathname
|
|
196
|
+
: a
|
|
197
|
+
);
|
|
198
|
+
const resolvedEnv = {};
|
|
199
|
+
for (const envKey of (mc.env ?? [])) {
|
|
200
|
+
resolvedEnv[envKey] = agentEnv[envKey] ?? process.env[envKey] ?? '';
|
|
201
|
+
}
|
|
202
|
+
mcpServers[mc.server] = {
|
|
203
|
+
command: mc.command,
|
|
204
|
+
args: resolvedArgs,
|
|
205
|
+
env: resolvedEnv,
|
|
196
206
|
};
|
|
197
207
|
}
|
|
198
208
|
|
|
@@ -206,7 +216,7 @@ export class AgentManager {
|
|
|
206
216
|
'--output-format', 'stream-json',
|
|
207
217
|
'--input-format', 'stream-json',
|
|
208
218
|
'--mcp-config', JSON.stringify(mcpConfig),
|
|
209
|
-
'--system-prompt', buildSystemPrompt(config, agentId),
|
|
219
|
+
'--system-prompt', buildSystemPrompt(config, agentId, skills),
|
|
210
220
|
'--disallowed-tools', 'EnterPlanMode,ExitPlanMode',
|
|
211
221
|
];
|
|
212
222
|
|
|
@@ -383,99 +393,6 @@ export class AgentManager {
|
|
|
383
393
|
}
|
|
384
394
|
}
|
|
385
395
|
|
|
386
|
-
// ── skills ────────────────────────────────────────────────────────────────
|
|
387
|
-
|
|
388
|
-
_listSkills({ agentId, channelId, requestId }, connection) {
|
|
389
|
-
const home = homedir();
|
|
390
|
-
// Find workspace dir for this agent (check running agents first, else derive)
|
|
391
|
-
const key = this._key(agentId, channelId);
|
|
392
|
-
const agent = this.agents.get(key);
|
|
393
|
-
const workspaceDir = agent
|
|
394
|
-
? this._workspaceDir(agentId, agent.channelId)
|
|
395
|
-
: this._workspaceDir(agentId, channelId);
|
|
396
|
-
|
|
397
|
-
const runtime = agent?.config?.runtime ?? 'claude';
|
|
398
|
-
const skillDirs = runtime === 'codex'
|
|
399
|
-
? {
|
|
400
|
-
global: [
|
|
401
|
-
path.join(home, '.codex', 'skills'),
|
|
402
|
-
path.join(home, '.codex', 'skills', '.system'),
|
|
403
|
-
path.join(home, '.agents', 'skills'),
|
|
404
|
-
],
|
|
405
|
-
workspace: [
|
|
406
|
-
path.join(workspaceDir, '.codex', 'skills'),
|
|
407
|
-
path.join(workspaceDir, '.agents', 'skills'),
|
|
408
|
-
],
|
|
409
|
-
}
|
|
410
|
-
: {
|
|
411
|
-
global: [
|
|
412
|
-
path.join(home, '.claude', 'skills'),
|
|
413
|
-
path.join(home, '.claude', 'commands'),
|
|
414
|
-
],
|
|
415
|
-
workspace: [
|
|
416
|
-
path.join(workspaceDir, '.claude', 'skills'),
|
|
417
|
-
path.join(workspaceDir, '.claude', 'commands'),
|
|
418
|
-
],
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
const dedup = (skills) => {
|
|
422
|
-
const seen = new Set();
|
|
423
|
-
return skills.filter(s => {
|
|
424
|
-
if (seen.has(s.name)) return false;
|
|
425
|
-
seen.add(s.name);
|
|
426
|
-
return true;
|
|
427
|
-
});
|
|
428
|
-
};
|
|
429
|
-
const shorten = (skills) => skills.map(s => ({
|
|
430
|
-
...s,
|
|
431
|
-
sourcePath: s.sourcePath?.startsWith(home) ? '~' + s.sourcePath.slice(home.length) : s.sourcePath,
|
|
432
|
-
}));
|
|
433
|
-
|
|
434
|
-
const global = shorten(dedup(skillDirs.global.flatMap(d => this._scanSkillsDir(d))));
|
|
435
|
-
const workspace = shorten(dedup(skillDirs.workspace.flatMap(d => this._scanSkillsDir(d))));
|
|
436
|
-
|
|
437
|
-
connection.send({ type: 'agent:skills:list_result', agentId, requestId, global, workspace });
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
_scanSkillsDir(dir) {
|
|
441
|
-
let entries;
|
|
442
|
-
try { entries = readdirSync(dir, { withFileTypes: true }); }
|
|
443
|
-
catch { return []; }
|
|
444
|
-
const skills = [];
|
|
445
|
-
for (const entry of entries) {
|
|
446
|
-
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
447
|
-
const skillMd = path.join(dir, entry.name, 'SKILL.md');
|
|
448
|
-
try {
|
|
449
|
-
const content = readFileSync(skillMd, 'utf-8');
|
|
450
|
-
skills.push({ ...this._parseSkillMd(entry.name, content), sourcePath: dir });
|
|
451
|
-
} catch {}
|
|
452
|
-
} else if (entry.name.endsWith('.md')) {
|
|
453
|
-
const cmdName = entry.name.replace(/\.md$/, '');
|
|
454
|
-
try {
|
|
455
|
-
const content = readFileSync(path.join(dir, entry.name), 'utf-8');
|
|
456
|
-
skills.push({ ...this._parseSkillMd(cmdName, content), sourcePath: dir });
|
|
457
|
-
} catch {}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
return skills;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
_parseSkillMd(dirName, content) {
|
|
464
|
-
const info = { name: dirName, displayName: dirName, description: '', userInvocable: false };
|
|
465
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
466
|
-
if (!match) return info;
|
|
467
|
-
for (const line of match[1].split('\n')) {
|
|
468
|
-
const idx = line.indexOf(':');
|
|
469
|
-
if (idx === -1) continue;
|
|
470
|
-
const key = line.slice(0, idx).trim();
|
|
471
|
-
const value = line.slice(idx + 1).trim();
|
|
472
|
-
if (key === 'name') info.displayName = value;
|
|
473
|
-
if (key === 'description') info.description = value;
|
|
474
|
-
if (key === 'user-invocable') info.userInvocable = value === 'true';
|
|
475
|
-
}
|
|
476
|
-
return info;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
396
|
_parseKimiLine(key, agentId, channelId, line, connection) {
|
|
480
397
|
const agent = this.agents.get(key);
|
|
481
398
|
if (!agent) return;
|
package/src/chat-bridge.js
CHANGED
|
@@ -260,6 +260,66 @@ server.tool('write_memory', 'Write or update a memory file (full content replace
|
|
|
260
260
|
return { content: [{ type: 'text', text: `Saved ${path}` }] };
|
|
261
261
|
});
|
|
262
262
|
|
|
263
|
+
// ── skill_list ───────────────────────────────────────────────────────────────
|
|
264
|
+
server.tool('skill_list', 'List all skills available to you (platform + bound). Returns index only (name + description), not full content.', {}, async () => {
|
|
265
|
+
const chParam = currentChannelId ? `?channelId=${encodeURIComponent(currentChannelId)}` : '';
|
|
266
|
+
const skills = await api('GET', `/skills${chParam}`);
|
|
267
|
+
if (!skills || skills.length === 0) return { content: [{ type: 'text', text: 'No skills available.' }] };
|
|
268
|
+
const lines = skills.map(s => `- [${s.type}] **${s.name}** — ${s.description}`);
|
|
269
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// ── skill_read ───────────────────────────────────────────────────────────────
|
|
273
|
+
server.tool('skill_read', 'Read the full content of a skill by name or ID', {
|
|
274
|
+
name: z.string().describe('Skill name or ID'),
|
|
275
|
+
}, async ({ name }) => {
|
|
276
|
+
try {
|
|
277
|
+
const skill = await api('GET', `/skills/${encodeURIComponent(name)}`);
|
|
278
|
+
return { content: [{ type: 'text', text: `# ${skill.name}\n\n${skill.content}` }] };
|
|
279
|
+
} catch (err) {
|
|
280
|
+
if (err.message.includes('404')) return { content: [{ type: 'text', text: `Skill "${name}" not found.` }] };
|
|
281
|
+
throw err;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// ── skill_create ─────────────────────────────────────────────────────────────
|
|
286
|
+
server.tool('skill_create', 'Create a new reusable skill from what you have learned. Auto-binds to the current channel.', {
|
|
287
|
+
name: z.string().describe('Short skill name (lowercase, hyphens ok), e.g. "xhs-posting"'),
|
|
288
|
+
description: z.string().describe('One-line description of what this skill covers'),
|
|
289
|
+
content: z.string().describe('Full skill content in markdown — procedures, steps, tips'),
|
|
290
|
+
tags: z.array(z.string()).optional().describe('Optional tags for categorization'),
|
|
291
|
+
}, async ({ name, description, content, tags }) => {
|
|
292
|
+
const body = { name, description, content, tags: tags ?? [] };
|
|
293
|
+
if (currentChannelId) body.channelId = currentChannelId;
|
|
294
|
+
const result = await api('POST', '/skills', body);
|
|
295
|
+
return { content: [{ type: 'text', text: `Skill "${result.name}" created and bound to current channel.` }] };
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// ── skill_update ─────────────────────────────────────────────────────────────
|
|
299
|
+
server.tool('skill_update', 'Update an existing skill content, description, or tags', {
|
|
300
|
+
name: z.string().describe('Skill name or ID to update'),
|
|
301
|
+
content: z.string().optional().describe('New full content (replaces existing)'),
|
|
302
|
+
description: z.string().optional().describe('New description'),
|
|
303
|
+
tags: z.array(z.string()).optional().describe('New tags'),
|
|
304
|
+
}, async ({ name, content, description, tags }) => {
|
|
305
|
+
const body = {};
|
|
306
|
+
if (content != null) body.content = content;
|
|
307
|
+
if (description != null) body.description = description;
|
|
308
|
+
if (tags != null) body.tags = tags;
|
|
309
|
+
const result = await api('PATCH', `/skills/${encodeURIComponent(name)}`, body);
|
|
310
|
+
return { content: [{ type: 'text', text: `Skill "${result.name}" updated.` }] };
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// ── skill_search ─────────────────────────────────────────────────────────────
|
|
314
|
+
server.tool('skill_search', 'Search for skills by keyword across all accessible skills', {
|
|
315
|
+
query: z.string().describe('Search keyword'),
|
|
316
|
+
}, async ({ query }) => {
|
|
317
|
+
const skills = await api('GET', `/skills/search?q=${encodeURIComponent(query)}`);
|
|
318
|
+
if (!skills || skills.length === 0) return { content: [{ type: 'text', text: `No skills found for "${query}".` }] };
|
|
319
|
+
const lines = skills.map(s => `- [${s.type}] **${s.name}** — ${s.description}`);
|
|
320
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
321
|
+
});
|
|
322
|
+
|
|
263
323
|
// ── read_file_base64 ──────────────────────────────────────────────────────────
|
|
264
324
|
// Agent 需要在本机读取图片文件内容,转为 base64 后上传服务器
|
|
265
325
|
server.tool('read_file_base64',
|
package/src/drivers/claude.js
CHANGED
|
@@ -515,7 +515,44 @@ const PUBLISHER_PROMPT = `
|
|
|
515
515
|
|
|
516
516
|
// ── 主函数 ────────────────────────────────────────────────────────────────────
|
|
517
517
|
|
|
518
|
-
|
|
518
|
+
function buildSkillsPrompt(skills) {
|
|
519
|
+
if (!skills || skills.length === 0) return '';
|
|
520
|
+
|
|
521
|
+
const platform = skills.filter(s => s.type === 'platform');
|
|
522
|
+
const user = skills.filter(s => s.type === 'user');
|
|
523
|
+
|
|
524
|
+
let section = `
|
|
525
|
+
|
|
526
|
+
## Skills — Reusable Knowledge & Procedures
|
|
527
|
+
|
|
528
|
+
You have access to reusable skills — proven procedures for common tasks.
|
|
529
|
+
Skills are loaded progressively: the index below shows what's available.
|
|
530
|
+
Use \`skill_read\` to load full content when needed.
|
|
531
|
+
|
|
532
|
+
### How to use skills
|
|
533
|
+
|
|
534
|
+
- Before starting unfamiliar work, search for relevant skills: \`skill_search({ query: "..." })\`
|
|
535
|
+
- Load a skill's full procedure: \`skill_read({ name: "skill-name" })\`
|
|
536
|
+
- After completing a complex task (5+ tool calls), consider saving it as a skill: \`skill_create({ name, description, content })\`
|
|
537
|
+
- When using a skill and finding it outdated or wrong, update it immediately: \`skill_update({ name, content })\`
|
|
538
|
+
- Skills you create are automatically shared with other agents in the same channel
|
|
539
|
+
|
|
540
|
+
### Available Skills
|
|
541
|
+
`;
|
|
542
|
+
|
|
543
|
+
if (platform.length > 0) {
|
|
544
|
+
section += `\n**Platform Skills:**\n`;
|
|
545
|
+
for (const s of platform) section += `- **${s.name}** — ${s.description}\n`;
|
|
546
|
+
}
|
|
547
|
+
if (user.length > 0) {
|
|
548
|
+
section += `\n**Bound Skills:**\n`;
|
|
549
|
+
for (const s of user) section += `- **${s.name}** — ${s.description}\n`;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return section;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export function buildSystemPrompt(config, agentId, skills) {
|
|
519
556
|
const { name, displayName, description, feishuBotName } = config;
|
|
520
557
|
|
|
521
558
|
const base = BASE_PROMPT(displayName, name, description, agentId, feishuBotName);
|
|
@@ -527,5 +564,7 @@ export function buildSystemPrompt(config, agentId) {
|
|
|
527
564
|
'publisher': PUBLISHER_PROMPT,
|
|
528
565
|
}[name] ?? '';
|
|
529
566
|
|
|
530
|
-
|
|
567
|
+
const skillsPrompt = buildSkillsPrompt(skills);
|
|
568
|
+
|
|
569
|
+
return base + skillsPrompt + rolePrompt;
|
|
531
570
|
}
|
package/src/drivers/codex.js
CHANGED
|
@@ -18,6 +18,15 @@ function quote(value) {
|
|
|
18
18
|
return JSON.stringify(value);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function normalizeCodexModel(model) {
|
|
22
|
+
if (!model) return 'gpt-5.2';
|
|
23
|
+
const normalized = String(model).trim().toLowerCase();
|
|
24
|
+
if (!normalized) return 'gpt-5.2';
|
|
25
|
+
if (['sonnet', 'opus', 'haiku'].includes(normalized)) return 'gpt-5.2';
|
|
26
|
+
if (normalized.startsWith('claude-')) return 'gpt-5.2';
|
|
27
|
+
return model;
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
function ensureGitRepo(workspaceDir) {
|
|
22
31
|
const gitDir = path.join(workspaceDir, '.git');
|
|
23
32
|
if (existsSync(gitDir)) return;
|
|
@@ -53,7 +62,7 @@ export function buildCodexSystemPrompt(config, agentId) {
|
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
export function buildCodexSpawn({
|
|
56
|
-
config, agentId, channelId, workspaceDir, chatBridgePath, serverUrl, machineApiKey, prompt,
|
|
65
|
+
config, agentId, channelId, workspaceDir, chatBridgePath, serverUrl, machineApiKey, prompt, skills,
|
|
57
66
|
}) {
|
|
58
67
|
ensureGitRepo(workspaceDir);
|
|
59
68
|
|
|
@@ -80,34 +89,36 @@ export function buildCodexSpawn({
|
|
|
80
89
|
'-c', 'mcp_servers.chat.tool_timeout_sec=300',
|
|
81
90
|
);
|
|
82
91
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const mysqlServerPath = new URL('../../mcp-servers/mysql/index.js', import.meta.url).pathname;
|
|
93
|
-
const agentEnv = config.envVars ?? {};
|
|
94
|
-
args.push(
|
|
95
|
-
'-c', `mcp_servers.mysql.command=${quote('env')}`,
|
|
96
|
-
'-c', `mcp_servers.mysql.args=${quote([
|
|
97
|
-
`DB_HOST=${process.env.DB_HOST ?? ''}`,
|
|
98
|
-
`DB_PORT=${process.env.DB_PORT ?? '3306'}`,
|
|
99
|
-
`DB_USER=${process.env.DB_USER ?? ''}`,
|
|
100
|
-
`DB_PASSWORD=${process.env.DB_PASSWORD ?? ''}`,
|
|
101
|
-
`DB_NAME=${agentEnv.MYSQL_DB ?? process.env.DB_NAME ?? ''}`,
|
|
102
|
-
'node',
|
|
103
|
-
mysqlServerPath,
|
|
104
|
-
])}`,
|
|
105
|
-
'-c', 'mcp_servers.mysql.enabled=true'
|
|
92
|
+
// Derive MCP servers from skills with mcpConfig
|
|
93
|
+
const agentEnv = config.envVars ?? {};
|
|
94
|
+
for (const skill of (skills ?? [])) {
|
|
95
|
+
if (!skill.mcpConfig) continue;
|
|
96
|
+
const mc = skill.mcpConfig;
|
|
97
|
+
const resolvedArgs = (mc.args ?? []).map(a =>
|
|
98
|
+
a === '{mysql_mcp_path}'
|
|
99
|
+
? new URL('../../mcp-servers/mysql/index.js', import.meta.url).pathname
|
|
100
|
+
: a
|
|
106
101
|
);
|
|
102
|
+
// For codex, MCP servers use env wrapper for env vars
|
|
103
|
+
const envPairs = (mc.env ?? []).map(k => `${k}=${agentEnv[k] ?? process.env[k] ?? ''}`);
|
|
104
|
+
if (envPairs.length > 0) {
|
|
105
|
+
args.push(
|
|
106
|
+
'-c', `mcp_servers.${quote(mc.server)}.command=${quote('env')}`,
|
|
107
|
+
'-c', `mcp_servers.${quote(mc.server)}.args=${quote([...envPairs, mc.command, ...resolvedArgs])}`,
|
|
108
|
+
'-c', `mcp_servers.${quote(mc.server)}.enabled=true`
|
|
109
|
+
);
|
|
110
|
+
} else {
|
|
111
|
+
args.push(
|
|
112
|
+
'-c', `mcp_servers.${quote(mc.server)}.command=${quote(mc.command)}`,
|
|
113
|
+
'-c', `mcp_servers.${quote(mc.server)}.args=${quote(resolvedArgs)}`,
|
|
114
|
+
'-c', `mcp_servers.${quote(mc.server)}.enabled=true`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
107
117
|
}
|
|
108
118
|
|
|
109
|
-
|
|
110
|
-
|
|
119
|
+
const model = normalizeCodexModel(config.model);
|
|
120
|
+
if (model) {
|
|
121
|
+
args.push('-m', model);
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
if (config.reasoningEffort) {
|
package/src/drivers/kimi.js
CHANGED
|
@@ -12,7 +12,7 @@ const KIMI_MCP_FILE = '.lightcone-kimi-mcp.json';
|
|
|
12
12
|
* Build Kimi CLI spawn args and config files.
|
|
13
13
|
* Returns { args, env, setupFiles() } ready for spawn('kimi', args, { env }).
|
|
14
14
|
*/
|
|
15
|
-
export function buildKimiSpawn({ config, agentId, channelId, workspaceDir, chatBridgePath, serverUrl, machineApiKey }) {
|
|
15
|
+
export function buildKimiSpawn({ config, agentId, channelId, workspaceDir, chatBridgePath, serverUrl, machineApiKey, skills }) {
|
|
16
16
|
const isResume = !!config.sessionId;
|
|
17
17
|
const sessionId = config.sessionId || randomUUID();
|
|
18
18
|
|
|
@@ -51,29 +51,22 @@ export function buildKimiSpawn({ config, agentId, channelId, workspaceDir, chatB
|
|
|
51
51
|
},
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
DB_HOST: process.env.DB_HOST ?? '',
|
|
71
|
-
DB_PORT: process.env.DB_PORT ?? '3306',
|
|
72
|
-
DB_USER: process.env.DB_USER ?? '',
|
|
73
|
-
DB_PASSWORD: process.env.DB_PASSWORD ?? '',
|
|
74
|
-
DB_NAME: agentEnv.MYSQL_DB ?? process.env.DB_NAME ?? '',
|
|
75
|
-
},
|
|
76
|
-
};
|
|
54
|
+
// Derive MCP servers from skills with mcpConfig
|
|
55
|
+
const agentEnv = config.envVars ?? {};
|
|
56
|
+
for (const skill of (skills ?? [])) {
|
|
57
|
+
if (!skill.mcpConfig) continue;
|
|
58
|
+
const mc = skill.mcpConfig;
|
|
59
|
+
if (mcpServers[mc.server]) continue;
|
|
60
|
+
const resolvedArgs = (mc.args ?? []).map(a =>
|
|
61
|
+
a === '{mysql_mcp_path}'
|
|
62
|
+
? new URL('../../mcp-servers/mysql/index.js', import.meta.url).pathname
|
|
63
|
+
: a
|
|
64
|
+
);
|
|
65
|
+
const resolvedEnv = {};
|
|
66
|
+
for (const envKey of (mc.env ?? [])) {
|
|
67
|
+
resolvedEnv[envKey] = agentEnv[envKey] ?? process.env[envKey] ?? '';
|
|
68
|
+
}
|
|
69
|
+
mcpServers[mc.server] = { command: mc.command, args: resolvedArgs, env: resolvedEnv };
|
|
77
70
|
}
|
|
78
71
|
|
|
79
72
|
writeFileSync(mcpConfigPath, JSON.stringify({ mcpServers }), 'utf8');
|