@lightcone-ai/daemon 0.9.79 → 0.10.0
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/mysql/index.js +13 -5
- package/mcp-servers/mysql/manifest.json +16 -0
- package/mcp-servers/official/company-fundamentals/index.js +34 -0
- package/mcp-servers/official/company-fundamentals/manifest.json +14 -0
- package/mcp-servers/official/compliance-check/index.js +49 -0
- package/mcp-servers/official/compliance-check/manifest.json +14 -0
- package/mcp-servers/official/industry-report/index.js +34 -0
- package/mcp-servers/official/industry-report/manifest.json +14 -0
- package/mcp-servers/official/market-data-query/index.js +34 -0
- package/mcp-servers/official/market-data-query/manifest.json +14 -0
- package/mcp-servers/official/portfolio-analysis/index.js +74 -0
- package/mcp-servers/official/portfolio-analysis/manifest.json +14 -0
- package/mcp-servers/official/portfolio-read/index.js +34 -0
- package/mcp-servers/official/portfolio-read/manifest.json +14 -0
- package/mcp-servers/official/research-fetch/index.js +35 -0
- package/mcp-servers/official/research-fetch/manifest.json +14 -0
- package/mcp-servers/official/risk-metrics/index.js +34 -0
- package/mcp-servers/official/risk-metrics/manifest.json +14 -0
- package/mcp-servers/official-common/fixtures.js +273 -0
- package/mcp-servers/official-common/server.js +34 -0
- package/mcp-servers/platform/manifest.json +15 -0
- package/mcp-servers/portfolio-analysis/core.js +592 -0
- package/mcp-servers/portfolio-analysis/index.js +45 -0
- package/mcp-servers/portfolio-analysis/package-lock.json +1139 -0
- package/mcp-servers/portfolio-analysis/package.json +10 -0
- package/mcp-servers/portfolio-read/core.js +330 -0
- package/mcp-servers/portfolio-read/index.js +127 -0
- package/mcp-servers/portfolio-read/package-lock.json +1243 -0
- package/mcp-servers/portfolio-read/package.json +11 -0
- package/mcp-servers/publisher/index.js +14 -14
- package/mcp-servers/publisher/manifest.json +16 -0
- package/package.json +1 -1
- package/src/agent-manager.js +761 -188
- package/src/chat-bridge.js +567 -92
- package/src/connection.js +1 -1
- package/src/drivers/claude.js +48 -45
- package/src/drivers/codex.js +110 -8
- package/src/drivers/kimi.js +80 -35
- package/src/governance-state.js +89 -0
- package/src/index.js +34 -16
- package/src/lease-window.js +8 -0
- package/src/mcp-config.js +52 -23
package/src/connection.js
CHANGED
|
@@ -58,7 +58,7 @@ export class DaemonConnection {
|
|
|
58
58
|
try { msg = JSON.parse(raw.toString()); }
|
|
59
59
|
catch { return; }
|
|
60
60
|
if (msg.type !== 'pong') {
|
|
61
|
-
console.log(`[Connection] ← ${msg.type}${msg.agentId ? ` agent=${msg.agentId.slice(0,8)}` : ''}${msg.
|
|
61
|
+
console.log(`[Connection] ← ${msg.type}${msg.agentId ? ` agent=${msg.agentId.slice(0,8)}` : ''}${msg.workspaceId ? ` workspace=${msg.workspaceId.slice(0,8)}` : ''}${msg.seq != null ? ` seq=${msg.seq}` : ''}`);
|
|
62
62
|
}
|
|
63
63
|
this.onMessage(msg);
|
|
64
64
|
});
|
package/src/drivers/claude.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const t = (name) => `mcp__chat__${name}`;
|
|
2
2
|
|
|
3
3
|
const BASE_PROMPT = (displayName, name, description, agentId, feishuBotName) => `\
|
|
4
|
-
You are "${displayName || name}", an AI agent in
|
|
4
|
+
You are "${displayName || name}", an AI agent in lightcone — a collaborative platform for human-AI collaboration.
|
|
5
5
|
${feishuBotName ? `You are also known as "${feishuBotName}" on Feishu — messages mentioning @${feishuBotName} are directed at you.\n` : ''}\
|
|
6
6
|
|
|
7
7
|
## Who you are
|
|
@@ -13,11 +13,11 @@ Your workspace and MEMORY.md persist across turns, so you can recover context wh
|
|
|
13
13
|
You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
14
14
|
|
|
15
15
|
1. **${t("check_messages")}** — Non-blocking check for new messages. Use freely during work — at natural breakpoints or after notifications.
|
|
16
|
-
2. **${t("send_message")}** — Send a message to a
|
|
17
|
-
3. **${t("list_server")}** — List all
|
|
16
|
+
2. **${t("send_message")}** — Send a message to a workspace or DM.
|
|
17
|
+
3. **${t("list_server")}** — List all workspaces in this server, which ones you have joined, plus all agents and humans.
|
|
18
18
|
4. **${t("search_messages")}** — Search messages visible to you by keyword.
|
|
19
|
-
5. **${t("list_tasks")}** — View a
|
|
20
|
-
6. **${t("create_tasks")}** — Create new task-messages in a
|
|
19
|
+
5. **${t("list_tasks")}** — View a workspace's task board.
|
|
20
|
+
6. **${t("create_tasks")}** — Create new task-messages in a workspace (supports batch; equivalent to sending a new message and publishing it as a task-message, not claiming it for yourself).
|
|
21
21
|
7. **${t("claim_tasks")}** — Claim tasks by number (supports batch, handles conflicts).
|
|
22
22
|
8. **${t("unclaim_task")}** — Release your claim on a task.
|
|
23
23
|
9. **${t("update_task_status")}** — Change a task's status (e.g. to in_review or done).
|
|
@@ -57,16 +57,16 @@ Header fields:
|
|
|
57
57
|
|
|
58
58
|
### Sending messages
|
|
59
59
|
|
|
60
|
-
- **Reply to a
|
|
60
|
+
- **Reply to a workspace**: \`send_message(target="#workspace-name", content="...")\`
|
|
61
61
|
- **Reply to a DM**: \`send_message(target="dm:@peer-name", content="...")\`
|
|
62
|
-
- **Reply in a thread**: \`send_message(target="#
|
|
62
|
+
- **Reply in a thread**: \`send_message(target="#workspace:shortid", content="...")\` or \`send_message(target="dm:@peer:shortid", content="...")\`
|
|
63
63
|
- **Start a NEW DM**: \`send_message(target="dm:@person-name", content="...")\`
|
|
64
64
|
|
|
65
|
-
**IMPORTANT**: To reply to any message, always reuse the exact \`target\` from the received message. This ensures your reply goes to the right place — whether it's a
|
|
65
|
+
**IMPORTANT**: To reply to any message, always reuse the exact \`target\` from the received message. This ensures your reply goes to the right place — whether it's a workspace, DM, or thread.
|
|
66
66
|
|
|
67
67
|
### Threads
|
|
68
68
|
|
|
69
|
-
Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main
|
|
69
|
+
Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main workspace.
|
|
70
70
|
|
|
71
71
|
- **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
|
|
72
72
|
- When you receive a message from a thread (the target has a \`:shortid\` suffix), **always reply using that same target** to keep the conversation in the thread.
|
|
@@ -74,16 +74,16 @@ Threads are sub-conversations attached to a specific message. They let you discu
|
|
|
74
74
|
- When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
|
|
75
75
|
- Threads cannot be nested — you cannot start a thread inside a thread.
|
|
76
76
|
|
|
77
|
-
### Discovering people and
|
|
77
|
+
### Discovering people and workspaces
|
|
78
78
|
|
|
79
|
-
Call \`list_server\` to see all
|
|
79
|
+
Call \`list_server\` to see all workspaces in this server, which ones you have joined, other agents, and humans.
|
|
80
80
|
|
|
81
|
-
###
|
|
81
|
+
### Workspace awareness
|
|
82
82
|
|
|
83
|
-
Each
|
|
84
|
-
- **Reply in context** — always respond in the
|
|
85
|
-
- **Stay on topic** — when proactively sharing results or updates, post in the
|
|
86
|
-
- If unsure where something belongs, call \`list_server\` to review
|
|
83
|
+
Each workspace has a **name** and optionally a **description** that define its purpose (visible via \`list_server\`). Respect them:
|
|
84
|
+
- **Reply in context** — always respond in the workspace/thread the message came from.
|
|
85
|
+
- **Stay on topic** — when proactively sharing results or updates, post in the workspace most relevant to the work. Don't scatter messages across unrelated workspaces.
|
|
86
|
+
- If unsure where something belongs, call \`list_server\` to review workspace descriptions.
|
|
87
87
|
|
|
88
88
|
### Tasks
|
|
89
89
|
|
|
@@ -96,7 +96,7 @@ When someone sends a message that asks you to do something — fix a bug, write
|
|
|
96
96
|
- A regular message (no task suffix): \`@Alice: Can someone look into the login bug?\`
|
|
97
97
|
- A system notification about task changes: \`📋 Alice converted a message to task #3 "Fix the login bug"\`
|
|
98
98
|
|
|
99
|
-
Only top-level
|
|
99
|
+
Only top-level workspace / DM messages can become tasks. Messages inside threads are discussion context — reply there, but keep claims and conversions to top-level messages.
|
|
100
100
|
|
|
101
101
|
**Status flow:** \`todo\` → \`in_progress\` → \`in_review\` → \`done\`
|
|
102
102
|
|
|
@@ -105,7 +105,7 @@ Only top-level team / DM messages can become tasks. Messages inside threads are
|
|
|
105
105
|
**Workflow:**
|
|
106
106
|
1. Receive a message that requires action → claim it first (by task number if already a task, or by message ID if it's a regular message)
|
|
107
107
|
2. If the claim fails, someone else is working on it — move on to another task
|
|
108
|
-
3. Post updates in the task's thread: \`send_message(target="#
|
|
108
|
+
3. Post updates in the task's thread: \`send_message(target="#workspace:msgShortId", ...)\`
|
|
109
109
|
4. When done, set status to \`in_review\` so a human can validate
|
|
110
110
|
5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
|
|
111
111
|
|
|
@@ -135,12 +135,12 @@ When you receive a notification about new tasks, check the task board and claim
|
|
|
135
135
|
|
|
136
136
|
## @Mentions
|
|
137
137
|
|
|
138
|
-
In
|
|
139
|
-
- Your stable
|
|
138
|
+
In workspace group chats, you can @mention people by their unique name (e.g. "@alice" or "@bob").
|
|
139
|
+
- Your stable lightcone @mention handle is \`@${name}\`.
|
|
140
140
|
- Your display name is \`${displayName || name}\`. Treat it as presentation only — when reasoning about identity and @mentions, prefer your stable \`name\`.
|
|
141
141
|
- Every human and agent has a unique \`name\` — this is their stable identifier for @mentions.
|
|
142
142
|
- Mention others, not yourself — assign reviews and follow-ups to teammates.
|
|
143
|
-
- @mentions only reach people inside the
|
|
143
|
+
- @mentions only reach people inside the workspace — workspaces are the isolation boundary.
|
|
144
144
|
|
|
145
145
|
## Communication style
|
|
146
146
|
|
|
@@ -156,14 +156,14 @@ Keep the user informed. They cannot see your internal reasoning, so:
|
|
|
156
156
|
- **Only respond when relevant.** If a message does not @mention you and is not clearly directed at you or your expertise, do NOT respond. Let the appropriate agent handle it.
|
|
157
157
|
- **Only the person doing the work should report on it.** If someone else completed a task or submitted a PR, don't echo or summarize their work — let them respond to questions about it.
|
|
158
158
|
- **Claim before you start.** Always call \`${t("claim_tasks")}\` before doing any work on a task. If the claim fails, stop immediately and pick a different task.
|
|
159
|
-
- **Before stopping, check for concrete blockers you own.** If you still owe a specific handoff, review, decision, or reply that is currently blocking a specific person, send one minimal actionable message to that person or
|
|
159
|
+
- **Before stopping, check for concrete blockers you own.** If you still owe a specific handoff, review, decision, or reply that is currently blocking a specific person, send one minimal actionable message to that person or workspace before stopping.
|
|
160
160
|
- **Skip idle narration.** Only send messages when you have actionable content — avoid broadcasting that you are waiting or idle.
|
|
161
161
|
|
|
162
162
|
### Formatting — No HTML
|
|
163
163
|
|
|
164
|
-
Use plain-text @mentions (e.g. \`@alice\`) and #
|
|
164
|
+
Use plain-text @mentions (e.g. \`@alice\`) and #workspace references (e.g. \`#general\`, \`#1\`) — no HTML tags.
|
|
165
165
|
|
|
166
|
-
When referencing a
|
|
166
|
+
When referencing a workspace or mentioning someone, write them as plain text without backticks. Backtick-wrapped mentions render as code instead of interactive links.
|
|
167
167
|
|
|
168
168
|
### Formatting — URLs in non-English text
|
|
169
169
|
|
|
@@ -178,7 +178,7 @@ When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.), alwa
|
|
|
178
178
|
- You have filesystem access via Bash, Read, Write, Edit tools.
|
|
179
179
|
- **You MUST only read/write files inside your workspace directories.** Never modify files outside these paths:
|
|
180
180
|
- Your personal workspace (shown at startup)
|
|
181
|
-
- The
|
|
181
|
+
- The workspace shared workspace (one level up)
|
|
182
182
|
- **NEVER touch other projects or directories outside your workspace roots** without explicit permission from a human in this conversation.
|
|
183
183
|
- If a task requires modifying an external codebase, **ask for explicit authorization first**, stating exactly which files you intend to change.
|
|
184
184
|
|
|
@@ -186,23 +186,23 @@ When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.), alwa
|
|
|
186
186
|
|
|
187
187
|
Your workspace is organized in two layers:
|
|
188
188
|
|
|
189
|
-
### Personal workspace (this
|
|
189
|
+
### Personal workspace (this workspace only)
|
|
190
190
|
Your current working directory. Contains:
|
|
191
|
-
- \`MEMORY.md\` — your memory index for this
|
|
192
|
-
- \`notes/\` — your personal notes for this
|
|
191
|
+
- \`MEMORY.md\` — your memory index for this workspace context (managed via \`${t("read_memory")}\` / \`${t("write_memory")}\`)
|
|
192
|
+
- \`notes/\` — your personal notes for this workspace
|
|
193
193
|
- \`tmp/\` — in-progress work files
|
|
194
194
|
- \`.skills/\` — **read-only**, system-injected skill files. Each \`.md\` file is a skill bound to you. Read them with your Read tool — they are already on disk.
|
|
195
195
|
|
|
196
|
-
###
|
|
196
|
+
### Workspace shared workspace (shared with all agents in this workspace)
|
|
197
197
|
Located one level up from your personal workspace. Contains:
|
|
198
|
-
- \`BRIEF.md\` — **read this on every startup**. Set by humans. Defines
|
|
199
|
-
- \`KNOWLEDGE.md\` — shared knowledge index. Use \`${t("write_workspace")}\` to record
|
|
198
|
+
- \`BRIEF.md\` — **read this on every startup**. Set by humans. Defines workspace mission, conventions, and background.
|
|
199
|
+
- \`KNOWLEDGE.md\` — shared knowledge index. Use \`${t("write_workspace")}\` to record workspace-level learnings here.
|
|
200
200
|
- \`notes/\` — shared research notes and decisions.
|
|
201
201
|
- \`artifacts/\` — **ALL deliverables go here without exception**: code, scripts, HTML pages, data files, reports, images — everything you produce for a task. Use \`${t("write_workspace")}({ path: "artifacts/filename.ext", content: "..." })\` for text files and \`${t("write_workspace_file")}({ file_path: "tmp/local-file.png", path: "artifacts/file.png" })\` for local binary files. Never create deliverable files anywhere else.
|
|
202
202
|
|
|
203
203
|
**Write rule:**
|
|
204
204
|
- Personal learnings → \`${t("write_memory")}\`
|
|
205
|
-
-
|
|
205
|
+
- Workspace-level knowledge → \`${t("write_workspace")}({ path: "KNOWLEDGE.md", ... })\`
|
|
206
206
|
- **Any file you produce for a task** → \`${t("write_workspace")}({ path: "artifacts/your-file.ext", ... })\` or \`${t("write_workspace_file")}({ file_path, path: "artifacts/your-file.ext" })\`
|
|
207
207
|
|
|
208
208
|
Temporary local files belong under \`tmp/\` in your personal workspace. If you need to show an image in chat, first save the durable copy to \`artifacts/\`, then optionally call \`${t("upload_image")}\` for a temporary public preview URL.
|
|
@@ -211,21 +211,21 @@ Example: writing a web page → \`${t("write_workspace")}({ path: "artifacts/job
|
|
|
211
211
|
|
|
212
212
|
## Memory MCP tools
|
|
213
213
|
|
|
214
|
-
**Personal memory** (per-agent, per-
|
|
214
|
+
**Personal memory** (per-agent, per-workspace — stored server-side):
|
|
215
215
|
- \`${t("read_memory")}({ path })\` — read a personal memory file (e.g. \`"MEMORY.md"\`)
|
|
216
216
|
- \`${t("write_memory")}({ path, content })\` — save a personal memory file (full replace)
|
|
217
217
|
- \`${t("list_memory")}()\` — list your personal memory files
|
|
218
218
|
|
|
219
|
-
**
|
|
220
|
-
- \`${t("read_workspace")}({ path })\` — read a
|
|
221
|
-
- \`${t("write_workspace")}({ path, content })\` — write a
|
|
222
|
-
- \`${t("write_workspace_file")}({ file_path, path })\` — write a local file from your workspace to a
|
|
223
|
-
- \`${t("list_workspace")}()\` — list all files in the
|
|
219
|
+
**Workspace memory** (shared filesystem — visible to all agents in the workspace):
|
|
220
|
+
- \`${t("read_workspace")}({ path })\` — read a workspace file (e.g. \`"BRIEF.md"\`, \`"KNOWLEDGE.md"\`)
|
|
221
|
+
- \`${t("write_workspace")}({ path, content })\` — write a workspace file
|
|
222
|
+
- \`${t("write_workspace_file")}({ file_path, path })\` — write a local file from your workspace to a workspace artifact without putting base64 in context
|
|
223
|
+
- \`${t("list_workspace")}()\` — list all files in the workspace
|
|
224
224
|
|
|
225
225
|
### Startup sequence (CRITICAL)
|
|
226
226
|
|
|
227
227
|
1. Call \`${t("read_memory")}({ path: "MEMORY.md" })\` to load your personal memory index.
|
|
228
|
-
2. Call \`${t("read_workspace")}({ path: "BRIEF.md" })\` to read the
|
|
228
|
+
2. Call \`${t("read_workspace")}({ path: "BRIEF.md" })\` to read the workspace brief. If empty, proceed normally.
|
|
229
229
|
3. Then check messages and handle work.
|
|
230
230
|
|
|
231
231
|
### MEMORY.md — Your Personal Memory Index
|
|
@@ -236,7 +236,7 @@ Example: writing a web page → \`${t("write_workspace")}({ path: "artifacts/job
|
|
|
236
236
|
# <Your Name>
|
|
237
237
|
|
|
238
238
|
## Role
|
|
239
|
-
<your role in this
|
|
239
|
+
<your role in this workspace, evolved over time>
|
|
240
240
|
|
|
241
241
|
## Key Knowledge
|
|
242
242
|
- Read notes/work-log.md for decisions and completed work
|
|
@@ -249,15 +249,15 @@ Example: writing a web page → \`${t("write_workspace")}({ path: "artifacts/job
|
|
|
249
249
|
\`\`\`
|
|
250
250
|
|
|
251
251
|
**What belongs in personal memory:**
|
|
252
|
-
1. Your role and how it has evolved in this
|
|
252
|
+
1. Your role and how it has evolved in this workspace
|
|
253
253
|
2. User preferences and communication style
|
|
254
254
|
3. Work history — decisions made, problems solved, approaches that worked or failed
|
|
255
255
|
4. Pointers to your notes files
|
|
256
256
|
|
|
257
|
-
**What belongs in
|
|
257
|
+
**What belongs in workspace memory (KNOWLEDGE.md):**
|
|
258
258
|
1. Facts about the project that all agents need (tech stack, domain conventions)
|
|
259
259
|
2. Shared learnings — things you discovered that teammates would benefit from
|
|
260
|
-
3. Ongoing
|
|
260
|
+
3. Ongoing workspace context — which tasks are in progress across all agents
|
|
261
261
|
|
|
262
262
|
### Compaction safety
|
|
263
263
|
|
|
@@ -305,7 +305,7 @@ Use \`skill_read\` to load full content when needed.
|
|
|
305
305
|
- Load a skill's full procedure: \`skill_read({ name: "skill-name" })\`
|
|
306
306
|
- After completing a complex task (5+ tool calls), consider saving it as a skill: \`skill_create({ name, description, content })\`
|
|
307
307
|
- When using a skill and finding it outdated or wrong, update it immediately: \`skill_update({ name, content })\`
|
|
308
|
-
- Skills you create are automatically shared with other agents in the same
|
|
308
|
+
- Skills you create are automatically shared with other agents in the same workspace
|
|
309
309
|
|
|
310
310
|
### Available Skills
|
|
311
311
|
`;
|
|
@@ -323,12 +323,15 @@ Use \`skill_read\` to load full content when needed.
|
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
export function buildSystemPrompt(config, agentId, skills) {
|
|
326
|
+
if (typeof config?.systemPrompt === 'string' && config.systemPrompt.trim()) {
|
|
327
|
+
return config.systemPrompt;
|
|
328
|
+
}
|
|
326
329
|
const { name, displayName, description, feishuBotName, rolePrompt } = config;
|
|
327
330
|
|
|
328
331
|
const base = BASE_PROMPT(displayName, name, description, agentId, feishuBotName);
|
|
329
332
|
|
|
330
333
|
const roleSection = rolePrompt
|
|
331
|
-
? `\n\n## Your role in this
|
|
334
|
+
? `\n\n## Your role in this workspace\n\n${rolePrompt}`
|
|
332
335
|
: '';
|
|
333
336
|
|
|
334
337
|
const skillsPrompt = buildSkillsPrompt(skills);
|
package/src/drivers/codex.js
CHANGED
|
@@ -4,7 +4,14 @@ import path from 'path';
|
|
|
4
4
|
import { buildSystemPrompt as buildClaudeSystemPrompt } from './claude.js';
|
|
5
5
|
import { buildSkillMcpServers } from '../mcp-config.js';
|
|
6
6
|
|
|
7
|
-
function buildChatBridgeArgs(chatBridgePath, {
|
|
7
|
+
function buildChatBridgeArgs(chatBridgePath, {
|
|
8
|
+
agentId,
|
|
9
|
+
workspaceId,
|
|
10
|
+
serverUrl,
|
|
11
|
+
authToken,
|
|
12
|
+
workspaceDir,
|
|
13
|
+
governanceEnv = {},
|
|
14
|
+
}) {
|
|
8
15
|
const args = [
|
|
9
16
|
chatBridgePath,
|
|
10
17
|
'--agent-id', agentId,
|
|
@@ -12,7 +19,19 @@ function buildChatBridgeArgs(chatBridgePath, { agentId, teamId, serverUrl, authT
|
|
|
12
19
|
'--auth-token', authToken,
|
|
13
20
|
'--workspace-dir', workspaceDir,
|
|
14
21
|
];
|
|
15
|
-
if (
|
|
22
|
+
if (workspaceId) args.push('--workspace-id', workspaceId);
|
|
23
|
+
if (governanceEnv.GOVERNANCE_SPAWN_BUNDLE_ID) {
|
|
24
|
+
args.push('--spawn-bundle-id', governanceEnv.GOVERNANCE_SPAWN_BUNDLE_ID);
|
|
25
|
+
}
|
|
26
|
+
if (governanceEnv.GOVERNANCE_POLICY_VERSION) {
|
|
27
|
+
args.push('--policy-version', governanceEnv.GOVERNANCE_POLICY_VERSION);
|
|
28
|
+
}
|
|
29
|
+
if (governanceEnv.GOVERNANCE_POLICY_LEASE) {
|
|
30
|
+
args.push('--policy-lease', governanceEnv.GOVERNANCE_POLICY_LEASE);
|
|
31
|
+
}
|
|
32
|
+
if (governanceEnv.GOVERNANCE_MCP_CLASSIFICATION) {
|
|
33
|
+
args.push('--mcp-classification', governanceEnv.GOVERNANCE_MCP_CLASSIFICATION);
|
|
34
|
+
}
|
|
16
35
|
return args;
|
|
17
36
|
}
|
|
18
37
|
|
|
@@ -47,8 +66,11 @@ function ensureGitRepo(workspaceDir) {
|
|
|
47
66
|
});
|
|
48
67
|
}
|
|
49
68
|
|
|
50
|
-
export function
|
|
51
|
-
|
|
69
|
+
export function adaptCodexSystemPrompt(sourcePrompt) {
|
|
70
|
+
const basePrompt = typeof sourcePrompt === 'string' ? sourcePrompt : '';
|
|
71
|
+
if (!basePrompt.trim()) return '';
|
|
72
|
+
|
|
73
|
+
let prompt = basePrompt
|
|
52
74
|
.replaceAll('mcp__chat__', '')
|
|
53
75
|
.replace(
|
|
54
76
|
'3. If there is no concrete incoming message to handle, stop and wait. New messages will be delivered to you automatically via stdin.',
|
|
@@ -59,15 +81,94 @@ export function buildCodexSystemPrompt(config, agentId) {
|
|
|
59
81
|
'5. **Complete ALL your work before stopping.** If a task requires multi-step work (research, code changes, testing), finish everything, report results, then stop. Your process exits after each turn and will be restarted automatically for new work.'
|
|
60
82
|
);
|
|
61
83
|
|
|
62
|
-
|
|
84
|
+
const codexStopRule = 'IMPORTANT: Do not wait for stdin notifications. Finish the current turn completely, then stop.';
|
|
85
|
+
if (!prompt.includes(codexStopRule)) {
|
|
86
|
+
prompt += `\n\n${codexStopRule}`;
|
|
87
|
+
}
|
|
63
88
|
return prompt;
|
|
64
89
|
}
|
|
65
90
|
|
|
91
|
+
export function buildCodexSystemPrompt(config, agentId) {
|
|
92
|
+
const sourcePrompt = typeof config?.systemPrompt === 'string' && config.systemPrompt.trim()
|
|
93
|
+
? config.systemPrompt
|
|
94
|
+
: buildClaudeSystemPrompt(config, agentId);
|
|
95
|
+
return adaptCodexSystemPrompt(sourcePrompt);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function normalizeDirectiveMcpServers(value) {
|
|
99
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
100
|
+
const normalized = {};
|
|
101
|
+
for (const [serverKey, rawServer] of Object.entries(value)) {
|
|
102
|
+
if (!rawServer || typeof rawServer !== 'object' || Array.isArray(rawServer)) continue;
|
|
103
|
+
const command = typeof rawServer.command === 'string' ? rawServer.command.trim() : '';
|
|
104
|
+
if (!command) continue;
|
|
105
|
+
normalized[serverKey] = {
|
|
106
|
+
command,
|
|
107
|
+
args: Array.isArray(rawServer.args) ? rawServer.args.map(item => String(item)) : [],
|
|
108
|
+
env: rawServer.env && typeof rawServer.env === 'object' && !Array.isArray(rawServer.env)
|
|
109
|
+
? Object.fromEntries(Object.entries(rawServer.env).map(([k, v]) => [k, String(v ?? '')]))
|
|
110
|
+
: {},
|
|
111
|
+
required: rawServer.required === true,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return normalized;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildDirectiveMcpFlags(mcpServers) {
|
|
118
|
+
const args = [];
|
|
119
|
+
for (const [serverKey, mc] of Object.entries(mcpServers)) {
|
|
120
|
+
const envPairs = Object.entries(mc.env ?? {}).map(([k, v]) => `${k}=${v ?? ''}`);
|
|
121
|
+
if (envPairs.length > 0) {
|
|
122
|
+
args.push(
|
|
123
|
+
'-c', `mcp_servers.${quote(serverKey)}.command=${quote('env')}`,
|
|
124
|
+
'-c', `mcp_servers.${quote(serverKey)}.args=${quote([...envPairs, mc.command, ...(mc.args ?? [])])}`,
|
|
125
|
+
'-c', `mcp_servers.${quote(serverKey)}.enabled=true`
|
|
126
|
+
);
|
|
127
|
+
} else {
|
|
128
|
+
args.push(
|
|
129
|
+
'-c', `mcp_servers.${quote(serverKey)}.command=${quote(mc.command)}`,
|
|
130
|
+
'-c', `mcp_servers.${quote(serverKey)}.args=${quote(mc.args ?? [])}`,
|
|
131
|
+
'-c', `mcp_servers.${quote(serverKey)}.enabled=true`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (mc.required) {
|
|
135
|
+
args.push('-c', `mcp_servers.${quote(serverKey)}.required=true`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return args;
|
|
139
|
+
}
|
|
140
|
+
|
|
66
141
|
export function buildCodexSpawn({
|
|
67
|
-
config, agentId,
|
|
142
|
+
config, agentId, workspaceId, workspaceDir, chatBridgePath, serverUrl, machineApiKey, prompt, skills, credentialGrants, directive = null,
|
|
68
143
|
}) {
|
|
69
144
|
ensureGitRepo(workspaceDir);
|
|
70
145
|
|
|
146
|
+
if (directive && typeof directive === 'object') {
|
|
147
|
+
const baseArgs = Array.isArray(directive.spawn_args) && directive.spawn_args.length > 0
|
|
148
|
+
? directive.spawn_args.map(item => String(item))
|
|
149
|
+
: ['exec', '--dangerously-bypass-approvals-and-sandbox', '--json'];
|
|
150
|
+
const promptText = (typeof directive.system_prompt === 'string' && directive.system_prompt.trim())
|
|
151
|
+
? directive.system_prompt
|
|
152
|
+
: (prompt || buildCodexSystemPrompt(config, agentId));
|
|
153
|
+
const mcpServers = normalizeDirectiveMcpServers(directive.mcp_servers);
|
|
154
|
+
if (!baseArgs.some(arg => String(arg).includes('mcp_servers.')) && Object.keys(mcpServers).length > 0) {
|
|
155
|
+
baseArgs.push(...buildDirectiveMcpFlags(mcpServers));
|
|
156
|
+
}
|
|
157
|
+
const args = baseArgs.map(arg => String(arg).replaceAll('__SYSTEM_PROMPT__', promptText));
|
|
158
|
+
const hasPromptPlaceholder = baseArgs.some(arg => String(arg).includes('__SYSTEM_PROMPT__'));
|
|
159
|
+
if (!hasPromptPlaceholder) {
|
|
160
|
+
args.push(promptText);
|
|
161
|
+
}
|
|
162
|
+
const env = {
|
|
163
|
+
...process.env,
|
|
164
|
+
FORCE_COLOR: '0',
|
|
165
|
+
NO_COLOR: '1',
|
|
166
|
+
...(config.envVars ?? {}),
|
|
167
|
+
...(directive.env_vars ?? {}),
|
|
168
|
+
};
|
|
169
|
+
return { args, env };
|
|
170
|
+
}
|
|
171
|
+
|
|
71
172
|
const args = ['exec'];
|
|
72
173
|
if (config.sessionId) {
|
|
73
174
|
args.push('resume', config.sessionId);
|
|
@@ -75,10 +176,11 @@ export function buildCodexSpawn({
|
|
|
75
176
|
|
|
76
177
|
const bridgeArgs = buildChatBridgeArgs(chatBridgePath, {
|
|
77
178
|
agentId,
|
|
78
|
-
|
|
179
|
+
workspaceId,
|
|
79
180
|
serverUrl,
|
|
80
181
|
authToken: config.authToken || machineApiKey,
|
|
81
182
|
workspaceDir,
|
|
183
|
+
governanceEnv: config.envVars ?? {},
|
|
82
184
|
});
|
|
83
185
|
|
|
84
186
|
args.push(
|
|
@@ -97,7 +199,7 @@ export function buildCodexSpawn({
|
|
|
97
199
|
credentialGrants,
|
|
98
200
|
config,
|
|
99
201
|
agentId,
|
|
100
|
-
|
|
202
|
+
workspaceId,
|
|
101
203
|
workspaceDir,
|
|
102
204
|
serverUrl,
|
|
103
205
|
authToken: config.authToken || machineApiKey,
|
package/src/drivers/kimi.js
CHANGED
|
@@ -9,11 +9,30 @@ const KIMI_SYSTEM_PROMPT_FILE = '.lightcone-kimi-system.md';
|
|
|
9
9
|
const KIMI_AGENT_FILE = '.lightcone-kimi-agent.yaml';
|
|
10
10
|
const KIMI_MCP_FILE = '.lightcone-kimi-mcp.json';
|
|
11
11
|
|
|
12
|
+
function normalizeDirectiveMcpServers(value) {
|
|
13
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
14
|
+
const normalized = {};
|
|
15
|
+
for (const [serverKey, rawServer] of Object.entries(value)) {
|
|
16
|
+
if (!rawServer || typeof rawServer !== 'object' || Array.isArray(rawServer)) continue;
|
|
17
|
+
const command = typeof rawServer.command === 'string' ? rawServer.command.trim() : '';
|
|
18
|
+
if (!command) continue;
|
|
19
|
+
normalized[serverKey] = {
|
|
20
|
+
command,
|
|
21
|
+
args: Array.isArray(rawServer.args) ? rawServer.args.map(item => String(item)) : [],
|
|
22
|
+
env: rawServer.env && typeof rawServer.env === 'object' && !Array.isArray(rawServer.env)
|
|
23
|
+
? Object.fromEntries(Object.entries(rawServer.env).map(([k, v]) => [k, String(v ?? '')]))
|
|
24
|
+
: {},
|
|
25
|
+
required: rawServer.required === true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
|
|
12
31
|
/**
|
|
13
32
|
* Build Kimi CLI spawn args and config files.
|
|
14
33
|
* Returns { args, env, setupFiles() } ready for spawn('kimi', args, { env }).
|
|
15
34
|
*/
|
|
16
|
-
export function buildKimiSpawn({ config, agentId,
|
|
35
|
+
export function buildKimiSpawn({ config, agentId, workspaceId, workspaceDir, chatBridgePath, serverUrl, machineApiKey, skills, credentialGrants, directive = null }) {
|
|
17
36
|
const isResume = !!config.sessionId;
|
|
18
37
|
const sessionId = config.sessionId || randomUUID();
|
|
19
38
|
|
|
@@ -22,7 +41,11 @@ export function buildKimiSpawn({ config, agentId, teamId, workspaceDir, chatBrid
|
|
|
22
41
|
const mcpConfigPath = path.join(workspaceDir, KIMI_MCP_FILE);
|
|
23
42
|
|
|
24
43
|
// Build system prompt (reuse claude's prompt builder)
|
|
25
|
-
const prompt =
|
|
44
|
+
const prompt = (directive && typeof directive.system_prompt === 'string' && directive.system_prompt.trim())
|
|
45
|
+
? directive.system_prompt
|
|
46
|
+
: (typeof config?.systemPrompt === 'string' && config.systemPrompt.trim())
|
|
47
|
+
? config.systemPrompt
|
|
48
|
+
: buildClaudeSystemPrompt(config, agentId);
|
|
26
49
|
|
|
27
50
|
// Write system prompt file (skip if resuming and file exists)
|
|
28
51
|
if (!isResume || !existsSync(systemPromptPath)) {
|
|
@@ -39,47 +62,69 @@ export function buildKimiSpawn({ config, agentId, teamId, workspaceDir, chatBrid
|
|
|
39
62
|
].join('\n'), 'utf8');
|
|
40
63
|
|
|
41
64
|
// Build MCP config
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
let mcpServers = normalizeDirectiveMcpServers(directive?.mcp_servers);
|
|
66
|
+
if (Object.keys(mcpServers).length === 0) {
|
|
67
|
+
mcpServers = {
|
|
68
|
+
chat: {
|
|
69
|
+
command: 'node',
|
|
70
|
+
args: [chatBridgePath],
|
|
71
|
+
env: {
|
|
72
|
+
SERVER_URL: serverUrl,
|
|
73
|
+
MACHINE_API_KEY: config.authToken || machineApiKey,
|
|
74
|
+
AGENT_ID: agentId,
|
|
75
|
+
WORKSPACE_ID: workspaceId ?? '',
|
|
76
|
+
WORKSPACE_DIR: workspaceDir,
|
|
77
|
+
GOVERNANCE_SPAWN_BUNDLE_ID: config.envVars?.GOVERNANCE_SPAWN_BUNDLE_ID ?? '',
|
|
78
|
+
GOVERNANCE_POLICY_VERSION: config.envVars?.GOVERNANCE_POLICY_VERSION ?? '',
|
|
79
|
+
GOVERNANCE_POLICY_LEASE: config.envVars?.GOVERNANCE_POLICY_LEASE ?? '',
|
|
80
|
+
GOVERNANCE_MCP_CLASSIFICATION: config.envVars?.GOVERNANCE_MCP_CLASSIFICATION ?? '',
|
|
81
|
+
},
|
|
52
82
|
},
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
Object.assign(mcpServers, buildSkillMcpServers({
|
|
86
|
+
skills,
|
|
87
|
+
credentialGrants,
|
|
88
|
+
config,
|
|
89
|
+
agentId,
|
|
90
|
+
workspaceId,
|
|
91
|
+
workspaceDir,
|
|
92
|
+
serverUrl,
|
|
93
|
+
authToken: config.authToken || machineApiKey,
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
66
96
|
|
|
67
97
|
writeFileSync(mcpConfigPath, JSON.stringify({ mcpServers }), 'utf8');
|
|
68
98
|
|
|
69
99
|
// Build CLI args
|
|
70
|
-
const args =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
100
|
+
const args = Array.isArray(directive?.spawn_args) && directive.spawn_args.length > 0
|
|
101
|
+
? directive.spawn_args.map(item => String(item))
|
|
102
|
+
: [
|
|
103
|
+
'--wire',
|
|
104
|
+
'--yolo',
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
if (!args.includes('--agent-file')) {
|
|
108
|
+
args.push('--agent-file', agentFilePath);
|
|
109
|
+
}
|
|
110
|
+
if (!args.includes('--mcp-config-file')) {
|
|
111
|
+
args.push('--mcp-config-file', mcpConfigPath);
|
|
112
|
+
}
|
|
113
|
+
if (!args.includes('--session')) {
|
|
114
|
+
args.push('--session', sessionId);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!directive && config.model && config.model !== 'default') {
|
|
79
118
|
args.push('--model', config.model);
|
|
80
119
|
}
|
|
81
120
|
|
|
82
|
-
const spawnEnv = {
|
|
121
|
+
const spawnEnv = {
|
|
122
|
+
...process.env,
|
|
123
|
+
FORCE_COLOR: '0',
|
|
124
|
+
NO_COLOR: '1',
|
|
125
|
+
...(config.envVars ?? {}),
|
|
126
|
+
...(directive?.env_vars ?? {}),
|
|
127
|
+
};
|
|
83
128
|
|
|
84
129
|
return { args, env: spawnEnv, sessionId, isResume, prompt };
|
|
85
130
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
const STATE_DIR = path.join(homedir(), '.lightcone', 'governance');
|
|
6
|
+
const DEFAULT_STATE_FILE = path.join(STATE_DIR, 'state.json');
|
|
7
|
+
const MAX_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
8
|
+
|
|
9
|
+
function stateFilePath() {
|
|
10
|
+
return process.env.GOVERNANCE_STATE_FILE || DEFAULT_STATE_FILE;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function emptyState() {
|
|
14
|
+
return {
|
|
15
|
+
invalidated_leases: {},
|
|
16
|
+
updated_at: new Date().toISOString(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readState() {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(readFileSync(stateFilePath(), 'utf8'));
|
|
23
|
+
if (!parsed || typeof parsed !== 'object') return emptyState();
|
|
24
|
+
return {
|
|
25
|
+
invalidated_leases: parsed.invalidated_leases && typeof parsed.invalidated_leases === 'object'
|
|
26
|
+
? parsed.invalidated_leases
|
|
27
|
+
: {},
|
|
28
|
+
updated_at: parsed.updated_at ?? new Date().toISOString(),
|
|
29
|
+
};
|
|
30
|
+
} catch {
|
|
31
|
+
return emptyState();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writeState(nextState) {
|
|
36
|
+
mkdirSync(path.dirname(stateFilePath()), { recursive: true });
|
|
37
|
+
writeFileSync(stateFilePath(), JSON.stringify(nextState, null, 2), 'utf8');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function pruneExpired(state, nowMs = Date.now()) {
|
|
41
|
+
const trimmed = { ...state.invalidated_leases };
|
|
42
|
+
for (const [leaseId, payload] of Object.entries(trimmed)) {
|
|
43
|
+
const invalidatedAt = Date.parse(payload?.invalidated_at ?? '');
|
|
44
|
+
if (!Number.isFinite(invalidatedAt) || (nowMs - invalidatedAt) > MAX_RETENTION_MS) {
|
|
45
|
+
delete trimmed[leaseId];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
invalidated_leases: trimmed,
|
|
50
|
+
updated_at: new Date(nowMs).toISOString(),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function markInvalidatedLeases(leaseIds, {
|
|
55
|
+
reason = 'policy_updated',
|
|
56
|
+
newPolicyHash = null,
|
|
57
|
+
} = {}) {
|
|
58
|
+
const ids = Array.isArray(leaseIds) ? leaseIds.filter(Boolean) : [];
|
|
59
|
+
if (ids.length === 0) return 0;
|
|
60
|
+
const now = new Date().toISOString();
|
|
61
|
+
const current = pruneExpired(readState());
|
|
62
|
+
for (const leaseId of ids) {
|
|
63
|
+
current.invalidated_leases[leaseId] = {
|
|
64
|
+
reason,
|
|
65
|
+
new_policy_hash: newPolicyHash,
|
|
66
|
+
invalidated_at: now,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
current.updated_at = now;
|
|
70
|
+
writeState(current);
|
|
71
|
+
return ids.length;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const markLeasesInvalidated = markInvalidatedLeases;
|
|
75
|
+
|
|
76
|
+
export function isLeaseInvalidated(leaseId) {
|
|
77
|
+
if (!leaseId) return false;
|
|
78
|
+
const current = pruneExpired(readState());
|
|
79
|
+
return Boolean(current.invalidated_leases[leaseId]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function clearInvalidatedLease(leaseId) {
|
|
83
|
+
if (!leaseId) return;
|
|
84
|
+
const current = pruneExpired(readState());
|
|
85
|
+
if (!current.invalidated_leases[leaseId]) return;
|
|
86
|
+
delete current.invalidated_leases[leaseId];
|
|
87
|
+
current.updated_at = new Date().toISOString();
|
|
88
|
+
writeState(current);
|
|
89
|
+
}
|