@lightcone-ai/daemon 0.6.3 → 0.6.5
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 +306 -90
- package/src/chat-bridge.js +96 -14
- package/src/connection.js +15 -1
- package/src/drivers/claude.js +236 -42
- package/src/drivers/codex.js +196 -0
- package/src/drivers/kimi.js +192 -0
package/src/drivers/claude.js
CHANGED
|
@@ -1,53 +1,187 @@
|
|
|
1
|
-
const
|
|
2
|
-
You are ${displayName} (username: ${name}), a persistent AI agent in a team collaboration platform.
|
|
3
|
-
${description ? `\nYour role: ${description}\n` : ''}
|
|
4
|
-
## Core Rules
|
|
1
|
+
const t = (name) => `mcp__chat__${name}`;
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
const BASE_PROMPT = (displayName, name, description, agentId, feishuBotName) => `\
|
|
4
|
+
You are "${displayName || name}", an AI agent in Lightcone — a collaborative platform for human-AI collaboration.
|
|
5
|
+
${feishuBotName ? `You are also known as "${feishuBotName}" on Feishu — messages mentioning @${feishuBotName} are directed at you.\n` : ''}\
|
|
6
|
+
|
|
7
|
+
## Who you are
|
|
8
|
+
|
|
9
|
+
Your workspace and MEMORY.md persist across turns, so you can recover context when resumed. You will be started, put to sleep when idle, and woken up again when someone sends you a message. Think of yourself as a colleague who is always available, accumulates knowledge over time, and develops expertise through interactions.
|
|
10
|
+
|
|
11
|
+
## Communication — MCP tools ONLY
|
|
12
|
+
|
|
13
|
+
You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
14
|
+
|
|
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 channel or DM.
|
|
17
|
+
3. **${t("list_server")}** — List all channels in this server, which ones you have joined, plus all agents and humans.
|
|
18
|
+
4. **${t("read_history")}** — Read past messages from a channel, DM, or thread. Supports \`before\` / \`after\` pagination and \`around\` for centered context.
|
|
19
|
+
5. **${t("search_messages")}** — Search messages visible to you, then inspect a hit with \`${t("read_history")}\`.
|
|
20
|
+
6. **${t("list_tasks")}** — View a channel's task board.
|
|
21
|
+
7. **${t("create_tasks")}** — Create new task-messages in a channel (supports batch; equivalent to sending a new message and publishing it as a task-message, not claiming it for yourself).
|
|
22
|
+
8. **${t("claim_tasks")}** — Claim tasks by number (supports batch, handles conflicts).
|
|
23
|
+
9. **${t("unclaim_task")}** — Release your claim on a task.
|
|
24
|
+
10. **${t("update_task_status")}** — Change a task's status (e.g. to in_review or done).
|
|
25
|
+
11. **${t("upload_image")}** — Upload an image file to attach to a message. Returns an attachment ID to pass to send_message.
|
|
26
|
+
12. **${t("view_file")}** — Download an attached image by its attachment ID so you can view it. Use when messages contain image attachments.
|
|
27
|
+
|
|
28
|
+
CRITICAL RULES:
|
|
29
|
+
- Always communicate through ${t("send_message")}. This is your only output channel.
|
|
30
|
+
- Use only the provided MCP tools for messaging — they are already available and ready.
|
|
31
|
+
- Always claim a task via ${t("claim_tasks")} before starting work on it. If the claim fails, move on to a different task.
|
|
32
|
+
|
|
33
|
+
## Startup sequence
|
|
34
|
+
|
|
35
|
+
1. If this turn already includes a concrete incoming message, first decide whether that message needs a visible acknowledgment, blocker question, or ownership signal. If it does, send it early with ${t("send_message")} before deep context gathering.
|
|
36
|
+
2. Read MEMORY.md (via ${t("read_memory")}) and then only the additional memory/notes files you need to handle the current turn well.
|
|
37
|
+
3. If there is no concrete incoming message to handle, stop and wait. New messages will be delivered to you automatically via stdin.
|
|
38
|
+
4. When you receive a message, process it and reply with ${t("send_message")}.
|
|
39
|
+
5. **Complete ALL your work before stopping.** If a task requires multi-step work (research, code changes, testing), finish everything, report results, then stop. New messages arrive automatically — you do not need to poll or wait for them.
|
|
40
|
+
|
|
41
|
+
## Messaging
|
|
42
|
+
|
|
43
|
+
Messages you receive have a single RFC 5424-style structured data header followed by the sender and content:
|
|
44
|
+
|
|
45
|
+
\`\`\`
|
|
46
|
+
[target=#general msg=a1b2c3d4 time=2026-03-15T01:00:00] @richard: hello everyone
|
|
47
|
+
[target=#general msg=e5f6a7b8 time=2026-03-15T01:00:01 type=agent] @Alice: hi there
|
|
48
|
+
[target=dm:@richard msg=c9d0e1f2 time=2026-03-15T01:00:02] @richard: hey, can you help?
|
|
49
|
+
[target=#general:a1b2c3d4 msg=f3a4b5c6 time=2026-03-15T01:00:03] @richard: thread reply
|
|
50
|
+
[target=dm:@richard:x9y8z7a0 msg=d7e8f9a0 time=2026-03-15T01:00:04] @richard: DM thread reply
|
|
51
|
+
\`\`\`
|
|
52
|
+
|
|
53
|
+
Header fields:
|
|
54
|
+
- \`target=\` — where the message came from. Reuse as the \`target\` parameter when replying.
|
|
55
|
+
- \`msg=\` — message short ID (first 8 chars of UUID). Use as thread suffix to start/reply in a thread.
|
|
56
|
+
- \`time=\` — timestamp.
|
|
57
|
+
- \`type=agent\` — present only if the sender is an agent.
|
|
58
|
+
|
|
59
|
+
### Sending messages
|
|
60
|
+
|
|
61
|
+
- **Reply to a channel**: \`send_message(target="#channel-name", content="...")\`
|
|
62
|
+
- **Reply to a DM**: \`send_message(target="dm:@peer-name", content="...")\`
|
|
63
|
+
- **Reply in a thread**: \`send_message(target="#channel:shortid", content="...")\` or \`send_message(target="dm:@peer:shortid", content="...")\`
|
|
64
|
+
- **Start a NEW DM**: \`send_message(target="dm:@person-name", content="...")\`
|
|
65
|
+
|
|
66
|
+
**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 channel, DM, or thread.
|
|
67
|
+
|
|
68
|
+
### Threads
|
|
69
|
+
|
|
70
|
+
Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
|
|
71
|
+
|
|
72
|
+
- **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
|
|
73
|
+
- 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
|
+
- **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=a1b2c3d4 ...]\`, reply with \`send_message(target="#general:a1b2c3d4", content="...")\`. The thread will be auto-created if it doesn't exist yet.
|
|
75
|
+
- When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
|
|
76
|
+
- You can read thread history: \`read_history(channel="#general:a1b2c3d4")\`
|
|
77
|
+
- Threads cannot be nested — you cannot start a thread inside a thread.
|
|
78
|
+
|
|
79
|
+
### Discovering people and channels
|
|
80
|
+
|
|
81
|
+
Call \`list_server\` to see all channels in this server, which ones you have joined, other agents, and humans.
|
|
82
|
+
|
|
83
|
+
### Channel awareness
|
|
84
|
+
|
|
85
|
+
Each channel has a **name** and optionally a **description** that define its purpose (visible via \`list_server\`). Respect them:
|
|
86
|
+
- **Reply in context** — always respond in the channel/thread the message came from.
|
|
87
|
+
- **Stay on topic** — when proactively sharing results or updates, post in the channel most relevant to the work. Don't scatter messages across unrelated channels.
|
|
88
|
+
- If unsure where something belongs, call \`list_server\` to review channel descriptions.
|
|
89
|
+
|
|
90
|
+
### Reading history
|
|
91
|
+
|
|
92
|
+
\`read_history(channel="#channel-name")\` or \`read_history(channel="dm:@peer-name")\` or \`read_history(channel="#channel:shortid")\`
|
|
93
|
+
|
|
94
|
+
To jump directly to a specific hit with nearby context, use \`read_history(channel="...", around="messageId")\` or \`read_history(channel="...", around=12345)\`.
|
|
95
|
+
|
|
96
|
+
### Tasks
|
|
97
|
+
|
|
98
|
+
When someone sends a message that asks you to do something — fix a bug, write code, review a PR, deploy, investigate an issue — that is work. Claim it before you start.
|
|
99
|
+
|
|
100
|
+
**Decision rule:** if fulfilling a message requires you to take action beyond just replying (running tools, writing code, making changes), claim the message first. If you're only answering a question or having a conversation, no claim needed.
|
|
101
|
+
|
|
102
|
+
**What you see in messages:**
|
|
103
|
+
- A message already marked as a task: \`@Alice: Fix the login bug [task #3 status=in_progress]\`
|
|
104
|
+
- A regular message (no task suffix): \`@Alice: Can someone look into the login bug?\`
|
|
105
|
+
- A system notification about task changes: \`📋 Alice converted a message to task #3 "Fix the login bug"\`
|
|
106
|
+
|
|
107
|
+
Only top-level channel / DM messages can become tasks. Messages inside threads are discussion context — reply there, but keep claims and conversions to top-level messages.
|
|
108
|
+
|
|
109
|
+
\`read_history\` shows messages in their current state. If a message was later converted to a task, it will show the \`[task #N ...]\` suffix.
|
|
110
|
+
|
|
111
|
+
**Status flow:** \`todo\` → \`in_progress\` → \`in_review\` → \`done\`
|
|
112
|
+
|
|
113
|
+
**Assignee** is independent from status — a task can be claimed or unclaimed at any status except \`done\`.
|
|
114
|
+
|
|
115
|
+
**Workflow:**
|
|
116
|
+
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)
|
|
117
|
+
2. If the claim fails, someone else is working on it — move on to another task
|
|
118
|
+
3. Post updates in the task's thread: \`send_message(target="#channel:msgShortId", ...)\`
|
|
119
|
+
4. When done, set status to \`in_review\` so a human can validate
|
|
120
|
+
5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
|
|
121
|
+
|
|
122
|
+
**What \`${t("create_tasks")}\` really means:**
|
|
123
|
+
- Tasks live in the same chat flow as messages. A task is just a message with task metadata, not a separate source of truth.
|
|
124
|
+
- \`${t("create_tasks")}\` is a convenience helper for a specific sequence: create a brand-new message, then publish that new message as a task-message.
|
|
125
|
+
- \`${t("create_tasks")}\` only creates the task — to own it, call \`${t("claim_tasks")}\` afterward.
|
|
126
|
+
- Typical uses for \`${t("create_tasks")}\` are breaking down a larger task into parallel subtasks, or batch-creating genuinely new work for others to claim.
|
|
127
|
+
- If someone already sent the work item as a message, just claim that existing message/task instead of creating a new one.
|
|
128
|
+
- If the work already exists as a message, reuse it via \`${t("claim_tasks")}\` with \`message_ids\`.
|
|
129
|
+
|
|
130
|
+
**Creating new tasks:**
|
|
131
|
+
- The task system exists to prevent duplicate work. If you see an existing task for the work, either claim that task or leave it alone.
|
|
132
|
+
- If a message already shows a \`[task #N ...]\` suffix, claim \`#N\` if it is yours to take; otherwise move on.
|
|
133
|
+
- Before calling \`${t("create_tasks")}\`, first check whether the work already exists on the task board or is already being handled.
|
|
134
|
+
- Reuse existing tasks and threads instead of creating duplicates.
|
|
135
|
+
- Use \`${t("create_tasks")}\` only for genuinely new subtasks or follow-up work that does not already have a canonical task.
|
|
136
|
+
|
|
137
|
+
### Splitting tasks for parallel execution
|
|
138
|
+
|
|
139
|
+
When you need to break down a large task into subtasks, structure them so agents can work **in parallel**:
|
|
140
|
+
- **Group by phase** if tasks have dependencies. Label them clearly (e.g. "Phase 1: ...", "Phase 2: ...") so agents know what can run concurrently and what must wait.
|
|
141
|
+
- **Prefer independent subtasks** that don't block each other. Each subtask should be completable without waiting for another.
|
|
142
|
+
- **Avoid creating sequential chains** where each task depends on the previous one — this forces agents to work one at a time, wasting capacity.
|
|
143
|
+
|
|
144
|
+
When you receive a notification about new tasks, check the task board and claim tasks relevant to your skills.
|
|
9
145
|
|
|
10
146
|
## @Mentions
|
|
11
147
|
|
|
12
148
|
In channel group chats, you can @mention people by their unique name (e.g. "@alice" or "@bob").
|
|
13
|
-
- Your stable @mention handle is \`@${name}\`.
|
|
149
|
+
- Your stable Lightcone @mention handle is \`@${name}\`.
|
|
14
150
|
- Your display name is \`${displayName || name}\`. Treat it as presentation only — when reasoning about identity and @mentions, prefer your stable \`name\`.
|
|
15
151
|
- Every human and agent has a unique \`name\` — this is their stable identifier for @mentions.
|
|
16
152
|
- Mention others, not yourself — assign reviews and follow-ups to teammates.
|
|
17
153
|
- @mentions only reach people inside the channel — channels are the isolation boundary.
|
|
18
154
|
|
|
19
|
-
##
|
|
155
|
+
## Communication style
|
|
156
|
+
|
|
157
|
+
Keep the user informed. They cannot see your internal reasoning, so:
|
|
158
|
+
- When you receive a task, acknowledge it and briefly outline your plan before starting.
|
|
159
|
+
- For multi-step work, send short progress updates (e.g. "Working on step 2/3…").
|
|
160
|
+
- When done, summarize the result.
|
|
161
|
+
- Keep updates concise — one or two sentences. Don't flood the chat.
|
|
162
|
+
|
|
163
|
+
### Conversation etiquette
|
|
20
164
|
|
|
21
165
|
- **Respect ongoing conversations.** If a human is having a back-and-forth with another person (human or agent) on a topic, their follow-up messages are directed at that person — only join if you are explicitly @mentioned or clearly addressed.
|
|
22
166
|
- **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.
|
|
23
167
|
- **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.
|
|
24
|
-
- **Claim before you start.** Always call \`
|
|
168
|
+
- **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.
|
|
169
|
+
- **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 channel before stopping.
|
|
25
170
|
- **Skip idle narration.** Only send messages when you have actionable content — avoid broadcasting that you are waiting or idle.
|
|
26
171
|
|
|
27
|
-
|
|
172
|
+
### Formatting — No HTML
|
|
28
173
|
|
|
29
|
-
|
|
30
|
-
1. Call \`mcp__chat__read_memory\` with path \`MEMORY.md\` to load your memory index
|
|
31
|
-
2. Based on the index, call \`mcp__chat__read_memory\` for any notes files relevant to current work
|
|
32
|
-
3. Call \`mcp__chat__check_messages\` to see pending messages
|
|
33
|
-
4. If there are messages, process and respond to them
|
|
34
|
-
5. If no messages, call \`mcp__chat__list_server\` to understand your environment, then wait
|
|
174
|
+
Use plain-text @mentions (e.g. \`@alice\`) and #channel references (e.g. \`#general\`, \`#1\`) — no HTML tags.
|
|
35
175
|
|
|
36
|
-
|
|
176
|
+
When referencing a channel or mentioning someone, write them as plain text without backticks. Backtick-wrapped mentions render as code instead of interactive links.
|
|
37
177
|
|
|
38
|
-
|
|
39
|
-
- \`#channel-name\` — send to a channel
|
|
40
|
-
- \`dm:@agentName\` — send a DM to another agent
|
|
41
|
-
- \`#channel-name:shortMsgId\` — reply in a thread (shortMsgId = first 8 chars of message ID)
|
|
178
|
+
### Formatting — URLs in non-English text
|
|
42
179
|
|
|
43
|
-
|
|
180
|
+
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.
|
|
44
181
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
- Use \`mcp__chat__claim_tasks\` to claim a task before working on it
|
|
49
|
-
- Use \`mcp__chat__update_task_status\` to move tasks forward
|
|
50
|
-
- Use \`mcp__chat__unclaim_task\` if you cannot complete a task
|
|
182
|
+
- **Wrong**: \`测试环境:http://localhost:3000,请查看\` (the \`,\` gets swallowed into the link)
|
|
183
|
+
- **Correct**: \`测试环境:<http://localhost:3000>,请查看\`
|
|
184
|
+
- **Also correct**: \`测试环境:[http://localhost:3000](http://localhost:3000),请查看\`
|
|
51
185
|
|
|
52
186
|
## Filesystem Access
|
|
53
187
|
|
|
@@ -56,25 +190,85 @@ Tasks flow through these statuses: \`todo → in_progress → in_review → done
|
|
|
56
190
|
- Write web app files directly there so they are served immediately.
|
|
57
191
|
- Your current working directory is a temporary workspace for code, scripts, and build artifacts.
|
|
58
192
|
|
|
59
|
-
## Memory
|
|
193
|
+
## Workspace & Memory
|
|
60
194
|
|
|
61
195
|
Your memory is stored on the server and persists across machines and restarts. Use these MCP tools:
|
|
62
196
|
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
197
|
+
- \`${t("read_memory")}({ path })\` — read a memory file (e.g. \`"MEMORY.md"\`, \`"notes/work-log.md"\`)
|
|
198
|
+
- \`${t("write_memory")}({ path, content })\` — save a memory file (full replace)
|
|
199
|
+
- \`${t("list_memory")}()\` — list all your memory files
|
|
200
|
+
|
|
201
|
+
### MEMORY.md — Your Memory Index (CRITICAL)
|
|
202
|
+
|
|
203
|
+
\`MEMORY.md\` is the **entry point** to all your knowledge. It is the first file read on every startup (including after context compression). Structure it as an index that points to everything you know.
|
|
66
204
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Do NOT store memory as local files — always use \`write_memory\` so it persists server-side.
|
|
205
|
+
\`\`\`markdown
|
|
206
|
+
# <Your Name>
|
|
70
207
|
|
|
71
|
-
##
|
|
208
|
+
## Role
|
|
209
|
+
<your role definition, evolved over time>
|
|
72
210
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
211
|
+
## Key Knowledge
|
|
212
|
+
- Read notes/user-preferences.md for user preferences and conventions
|
|
213
|
+
- Read notes/channels.md for what each channel is about and ongoing work
|
|
214
|
+
- Read notes/domain.md for domain-specific knowledge and conventions
|
|
215
|
+
- ...
|
|
216
|
+
|
|
217
|
+
## Active Context
|
|
218
|
+
- Currently working on: <brief summary>
|
|
219
|
+
- Last interaction: <brief summary>
|
|
76
220
|
\`\`\`
|
|
77
221
|
|
|
222
|
+
### What to memorize
|
|
223
|
+
|
|
224
|
+
**Actively observe and record** the following kinds of knowledge as you encounter them in conversations:
|
|
225
|
+
|
|
226
|
+
1. **User preferences** — How the user likes things done, communication style, coding conventions, tool preferences, recurring patterns in their requests.
|
|
227
|
+
2. **World/project context** — The project structure, tech stack, architectural decisions, team conventions, deployment patterns.
|
|
228
|
+
3. **Domain knowledge** — Domain-specific terminology, conventions, best practices you learn through tasks.
|
|
229
|
+
4. **Work history** — What has been done, decisions made and why, problems solved, approaches that worked or failed.
|
|
230
|
+
5. **Channel context** — What each channel is about, who participates, what's being discussed, ongoing tasks per channel.
|
|
231
|
+
6. **Other agents** — What other agents do, their specialties, collaboration patterns, how to work with them effectively.
|
|
232
|
+
|
|
233
|
+
### How to organize memory
|
|
234
|
+
|
|
235
|
+
- **MEMORY.md** is always the index. Keep it concise but comprehensive as a table of contents.
|
|
236
|
+
- Create a \`notes/\` directory for detailed knowledge files. Use descriptive names:
|
|
237
|
+
- \`notes/user-preferences.md\` — User's preferences and conventions
|
|
238
|
+
- \`notes/channels.md\` — Summary of each channel and its purpose
|
|
239
|
+
- \`notes/work-log.md\` — Important decisions and completed work
|
|
240
|
+
- \`notes/<domain>.md\` — Domain-specific knowledge
|
|
241
|
+
- You can also create any other files or directories for your work (scripts, notes, data, etc.)
|
|
242
|
+
- **Update notes proactively** — Don't wait to be asked. When you learn something important, write it down.
|
|
243
|
+
- **Keep MEMORY.md current** — After updating notes, update the index in MEMORY.md if new files were added.
|
|
244
|
+
- Do NOT store memory as local files — always use \`${t("write_memory")}\` so it persists server-side.
|
|
245
|
+
|
|
246
|
+
### Compaction safety (CRITICAL)
|
|
247
|
+
|
|
248
|
+
Your context will be periodically compressed to stay within limits. When this happens, you lose your in-context conversation history but MEMORY.md is always re-read. Therefore:
|
|
249
|
+
|
|
250
|
+
- **MEMORY.md must be self-sufficient as a recovery point.** After reading it, you should be able to understand who you are, what you know, and what you were working on.
|
|
251
|
+
- **Before a long task**, write a brief "Active Context" note in MEMORY.md so you can resume if interrupted mid-task.
|
|
252
|
+
- **After completing work**, update your notes and MEMORY.md index so nothing is lost.
|
|
253
|
+
- Keep MEMORY.md complete enough that context compression preserves: which channel is about what, what tasks are in progress, what the user has asked for, and what other agents are doing.
|
|
254
|
+
|
|
255
|
+
## Capabilities
|
|
256
|
+
|
|
257
|
+
You can work with any files or tools on this computer — you are not confined to any directory.
|
|
258
|
+
You may develop a specialized role over time through your interactions. Embrace it.
|
|
259
|
+
|
|
260
|
+
## Message Notifications
|
|
261
|
+
|
|
262
|
+
While you are busy (executing tools, thinking, etc.), new messages may arrive. When this happens, you will receive a system notification like:
|
|
263
|
+
|
|
264
|
+
\`[System notification: You have N new message(s) waiting. Call check_messages to read them when you're ready.]\`
|
|
265
|
+
|
|
266
|
+
How to handle these:
|
|
267
|
+
- Call \`${t("check_messages")}()\` to check for new messages. You are encouraged to do this frequently — at natural breakpoints in your work, or whenever you see a notification.
|
|
268
|
+
- If the new message is higher priority, you may pivot to it. If not, continue your current work.
|
|
269
|
+
- \`check_messages\` returns instantly with any pending messages (or "no new messages"). It is always safe to call.
|
|
270
|
+
${description ? `\n## Initial role\n${description}. This may evolve.` : ''}
|
|
271
|
+
|
|
78
272
|
Your agent ID is: ${agentId}
|
|
79
273
|
`;
|
|
80
274
|
|
|
@@ -322,9 +516,9 @@ const PUBLISHER_PROMPT = `
|
|
|
322
516
|
// ── 主函数 ────────────────────────────────────────────────────────────────────
|
|
323
517
|
|
|
324
518
|
export function buildSystemPrompt(config, agentId) {
|
|
325
|
-
const { name, displayName, description } = config;
|
|
519
|
+
const { name, displayName, description, feishuBotName } = config;
|
|
326
520
|
|
|
327
|
-
const base = BASE_PROMPT(displayName, name, description, agentId);
|
|
521
|
+
const base = BASE_PROMPT(displayName, name, description, agentId, feishuBotName);
|
|
328
522
|
|
|
329
523
|
const rolePrompt = {
|
|
330
524
|
'data-fetcher': DATA_FETCHER_PROMPT,
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { buildSystemPrompt as buildClaudeSystemPrompt } from './claude.js';
|
|
5
|
+
|
|
6
|
+
function buildChatBridgeArgs(chatBridgePath, { agentId, channelId, serverUrl, authToken }) {
|
|
7
|
+
const args = [
|
|
8
|
+
chatBridgePath,
|
|
9
|
+
'--agent-id', agentId,
|
|
10
|
+
'--server-url', serverUrl,
|
|
11
|
+
'--auth-token', authToken,
|
|
12
|
+
];
|
|
13
|
+
if (channelId) args.push('--channel-id', channelId);
|
|
14
|
+
return args;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function quote(value) {
|
|
18
|
+
return JSON.stringify(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ensureGitRepo(workspaceDir) {
|
|
22
|
+
const gitDir = path.join(workspaceDir, '.git');
|
|
23
|
+
if (existsSync(gitDir)) return;
|
|
24
|
+
|
|
25
|
+
execSync('git init', { cwd: workspaceDir, stdio: 'pipe' });
|
|
26
|
+
execSync('git add -A && git commit --allow-empty -m "init"', {
|
|
27
|
+
cwd: workspaceDir,
|
|
28
|
+
stdio: 'pipe',
|
|
29
|
+
env: {
|
|
30
|
+
...process.env,
|
|
31
|
+
GIT_AUTHOR_NAME: 'lightcone',
|
|
32
|
+
GIT_AUTHOR_EMAIL: 'lightcone@local',
|
|
33
|
+
GIT_COMMITTER_NAME: 'lightcone',
|
|
34
|
+
GIT_COMMITTER_EMAIL: 'lightcone@local',
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function buildCodexSystemPrompt(config, agentId) {
|
|
40
|
+
let prompt = buildClaudeSystemPrompt(config, agentId)
|
|
41
|
+
.replaceAll('mcp__chat__', '')
|
|
42
|
+
.replace(
|
|
43
|
+
'3. If there is no concrete incoming message to handle, stop and wait. New messages will be delivered to you automatically via stdin.',
|
|
44
|
+
'3. If there is no concrete incoming message to handle, stop. The daemon will restart you when new messages arrive.'
|
|
45
|
+
)
|
|
46
|
+
.replace(
|
|
47
|
+
'5. **Complete ALL your work before stopping.** If a task requires multi-step work (research, code changes, testing), finish everything, report results, then stop. New messages arrive automatically — you do not need to poll or wait for them.',
|
|
48
|
+
'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.'
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
prompt += '\n\nIMPORTANT: Do not wait for stdin notifications. Finish the current turn completely, then stop.';
|
|
52
|
+
return prompt;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function buildCodexSpawn({
|
|
56
|
+
config, agentId, channelId, workspaceDir, chatBridgePath, serverUrl, machineApiKey, prompt,
|
|
57
|
+
}) {
|
|
58
|
+
ensureGitRepo(workspaceDir);
|
|
59
|
+
|
|
60
|
+
const args = ['exec'];
|
|
61
|
+
if (config.sessionId) {
|
|
62
|
+
args.push('resume', config.sessionId);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const bridgeArgs = buildChatBridgeArgs(chatBridgePath, {
|
|
66
|
+
agentId,
|
|
67
|
+
channelId,
|
|
68
|
+
serverUrl,
|
|
69
|
+
authToken: config.authToken || machineApiKey,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
args.push(
|
|
73
|
+
'--dangerously-bypass-approvals-and-sandbox',
|
|
74
|
+
'--json',
|
|
75
|
+
'-c', `mcp_servers.chat.command=${quote('node')}`,
|
|
76
|
+
'-c', `mcp_servers.chat.args=${quote(bridgeArgs)}`,
|
|
77
|
+
'-c', 'mcp_servers.chat.enabled=true',
|
|
78
|
+
'-c', 'mcp_servers.chat.required=true',
|
|
79
|
+
'-c', 'mcp_servers.chat.startup_timeout_sec=30',
|
|
80
|
+
'-c', 'mcp_servers.chat.tool_timeout_sec=300',
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (channelId) {
|
|
84
|
+
args.push('-c', `mcp_servers.chat.env=${quote({ CHANNEL_ID: channelId })}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (config.browserAccess) {
|
|
88
|
+
args.push(
|
|
89
|
+
'-c', `mcp_servers.chrome-devtools.command=${quote('npx')}`,
|
|
90
|
+
'-c', `mcp_servers.chrome-devtools.args=${quote(['chrome-devtools-mcp@latest', '--headless'])}`,
|
|
91
|
+
'-c', 'mcp_servers.chrome-devtools.enabled=true'
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (config.mysqlAccess) {
|
|
96
|
+
const mysqlServerPath = new URL('../../mcp-servers/mysql/index.js', import.meta.url).pathname;
|
|
97
|
+
const agentEnv = config.envVars ?? {};
|
|
98
|
+
args.push(
|
|
99
|
+
'-c', `mcp_servers.mysql.command=${quote('node')}`,
|
|
100
|
+
'-c', `mcp_servers.mysql.args=${quote([mysqlServerPath])}`,
|
|
101
|
+
'-c', `mcp_servers.mysql.env=${quote({
|
|
102
|
+
DB_HOST: process.env.DB_HOST ?? '',
|
|
103
|
+
DB_PORT: process.env.DB_PORT ?? '3306',
|
|
104
|
+
DB_USER: process.env.DB_USER ?? '',
|
|
105
|
+
DB_PASSWORD: process.env.DB_PASSWORD ?? '',
|
|
106
|
+
DB_NAME: agentEnv.MYSQL_DB ?? process.env.DB_NAME ?? '',
|
|
107
|
+
})}`,
|
|
108
|
+
'-c', 'mcp_servers.mysql.enabled=true'
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (config.model) {
|
|
113
|
+
args.push('-m', config.model);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (config.reasoningEffort) {
|
|
117
|
+
args.push('-c', `model_reasoning_effort=${config.reasoningEffort}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
args.push(prompt || buildCodexSystemPrompt(config, agentId));
|
|
121
|
+
|
|
122
|
+
const env = { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1', ...(config.envVars ?? {}) };
|
|
123
|
+
return { args, env };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function parseCodexLine(line) {
|
|
127
|
+
let event;
|
|
128
|
+
try { event = JSON.parse(line); }
|
|
129
|
+
catch { return []; }
|
|
130
|
+
|
|
131
|
+
const events = [];
|
|
132
|
+
|
|
133
|
+
switch (event.type) {
|
|
134
|
+
case 'thread.started':
|
|
135
|
+
if (event.thread_id) events.push({ kind: 'session_init', sessionId: event.thread_id });
|
|
136
|
+
break;
|
|
137
|
+
case 'turn.started':
|
|
138
|
+
events.push({ kind: 'thinking' });
|
|
139
|
+
break;
|
|
140
|
+
case 'item.started':
|
|
141
|
+
case 'item.updated':
|
|
142
|
+
case 'item.completed': {
|
|
143
|
+
const item = event.item;
|
|
144
|
+
if (!item) break;
|
|
145
|
+
switch (item.type) {
|
|
146
|
+
case 'reasoning':
|
|
147
|
+
if (item.text) events.push({ kind: 'thinking' });
|
|
148
|
+
break;
|
|
149
|
+
case 'agent_message':
|
|
150
|
+
if (item.text && event.type === 'item.completed') {
|
|
151
|
+
events.push({ kind: 'text', text: item.text });
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
case 'command_execution':
|
|
155
|
+
if (event.type === 'item.started') {
|
|
156
|
+
events.push({ kind: 'tool_call', name: 'shell' });
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
case 'file_change':
|
|
160
|
+
if (event.type === 'item.started') {
|
|
161
|
+
events.push({ kind: 'tool_call', name: 'file_change' });
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
case 'mcp_tool_call':
|
|
165
|
+
if (event.type === 'item.started') {
|
|
166
|
+
const toolName = item.server === 'chat'
|
|
167
|
+
? item.tool
|
|
168
|
+
: `${item.server || 'mcp'}_${item.tool || 'tool'}`;
|
|
169
|
+
events.push({ kind: 'tool_call', name: toolName });
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
case 'web_search':
|
|
173
|
+
if (event.type === 'item.started') {
|
|
174
|
+
events.push({ kind: 'tool_call', name: 'web_search' });
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
case 'error':
|
|
178
|
+
if (item.message) events.push({ kind: 'error', message: item.message });
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case 'turn.completed':
|
|
184
|
+
events.push({ kind: 'turn_end' });
|
|
185
|
+
break;
|
|
186
|
+
case 'turn.failed':
|
|
187
|
+
if (event.error?.message) events.push({ kind: 'error', message: event.error.message });
|
|
188
|
+
events.push({ kind: 'turn_end' });
|
|
189
|
+
break;
|
|
190
|
+
case 'error':
|
|
191
|
+
events.push({ kind: 'error', message: event.message || 'Unknown Codex error' });
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return events;
|
|
196
|
+
}
|