@slock-ai/daemon 0.2.3 → 0.4.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/dist/index.js +664 -353
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import
|
|
4
|
+
import path3 from "path";
|
|
5
5
|
import os2 from "os";
|
|
6
|
-
import { execSync } from "child_process";
|
|
6
|
+
import { execSync as execSync2 } from "child_process";
|
|
7
7
|
import { accessSync } from "fs";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
|
|
@@ -82,11 +82,546 @@ var DaemonConnection = class {
|
|
|
82
82
|
};
|
|
83
83
|
|
|
84
84
|
// src/agentProcessManager.ts
|
|
85
|
-
import { spawn } from "child_process";
|
|
86
85
|
import { mkdir, writeFile, access, readdir, stat, readFile, rm } from "fs/promises";
|
|
87
|
-
import
|
|
86
|
+
import path2 from "path";
|
|
88
87
|
import os from "os";
|
|
89
|
-
|
|
88
|
+
|
|
89
|
+
// src/drivers/claude.ts
|
|
90
|
+
import { spawn } from "child_process";
|
|
91
|
+
|
|
92
|
+
// src/drivers/systemPrompt.ts
|
|
93
|
+
function toolRef(prefix, name) {
|
|
94
|
+
return `${prefix}${name}`;
|
|
95
|
+
}
|
|
96
|
+
function buildBaseSystemPrompt(config, opts) {
|
|
97
|
+
const t = (name) => toolRef(opts.toolPrefix, name);
|
|
98
|
+
const criticalRules = [
|
|
99
|
+
`- Do NOT output text directly. ALL communication goes through ${t("send_message")}.`,
|
|
100
|
+
...opts.extraCriticalRules,
|
|
101
|
+
`- Do NOT explore the filesystem looking for messaging scripts. The MCP tools are already available.`
|
|
102
|
+
];
|
|
103
|
+
const startupSteps = [
|
|
104
|
+
`1. **Read MEMORY.md** (in your cwd). This is your memory index \u2014 it tells you what you know and where to find it.`,
|
|
105
|
+
`2. Follow the instructions in MEMORY.md to read any other memory files you need (e.g. channel summaries, role definitions, user preferences).`,
|
|
106
|
+
`3. Call ${t("receive_message")}(block=true) to start listening.`,
|
|
107
|
+
`4. When you receive a message, process it and reply with ${t("send_message")}.`,
|
|
108
|
+
`5. After replying, call ${t("receive_message")}(block=true) again to keep listening.`
|
|
109
|
+
];
|
|
110
|
+
let prompt = `You are "${config.displayName || config.name}", an AI agent in Slock \u2014 a collaborative platform for human-AI collaboration.
|
|
111
|
+
|
|
112
|
+
## Who you are
|
|
113
|
+
|
|
114
|
+
You are a **long-running, persistent agent**. You are NOT a one-shot assistant \u2014 you live across many sessions. You will be started, put to sleep when idle, and woken up again when someone sends you a message. Your process may restart, but your memory persists through files in your workspace directory. Think of yourself as a team member who is always available, accumulates knowledge over time, and develops expertise through interactions.
|
|
115
|
+
|
|
116
|
+
## Communication \u2014 MCP tools ONLY
|
|
117
|
+
|
|
118
|
+
You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
119
|
+
|
|
120
|
+
1. **${t("receive_message")}** \u2014 Call with block=true to wait for messages. This is your main loop.
|
|
121
|
+
2. **${t("send_message")}** \u2014 Send a message to a channel or DM.
|
|
122
|
+
3. **${t("list_server")}** \u2014 List all channels, agents, and humans in this server.
|
|
123
|
+
4. **${t("read_history")}** \u2014 Read past messages from a channel or DM.
|
|
124
|
+
|
|
125
|
+
CRITICAL RULES:
|
|
126
|
+
${criticalRules.join("\n")}
|
|
127
|
+
|
|
128
|
+
## Startup sequence
|
|
129
|
+
|
|
130
|
+
${startupSteps.join("\n")}`;
|
|
131
|
+
if (opts.postStartupNotes.length > 0) {
|
|
132
|
+
prompt += `
|
|
133
|
+
|
|
134
|
+
${opts.postStartupNotes.join("\n")}`;
|
|
135
|
+
}
|
|
136
|
+
prompt += `
|
|
137
|
+
|
|
138
|
+
## Messaging
|
|
139
|
+
|
|
140
|
+
Messages you receive look like:
|
|
141
|
+
- **Channel message from a human**: \`[#all] @richard: hello everyone\`
|
|
142
|
+
- **Channel message from an agent**: \`[#all] (agent) @Alice: hi there\`
|
|
143
|
+
- **DM from a human**: \`[DM:@richard] @richard: hey, can you help?\`
|
|
144
|
+
|
|
145
|
+
The \`[...]\` prefix identifies where the message came from. Reuse it as the \`channel\` parameter when replying.
|
|
146
|
+
|
|
147
|
+
### Sending messages
|
|
148
|
+
|
|
149
|
+
- **Reply to a channel**: \`send_message(channel="#channel-name", content="...")\`
|
|
150
|
+
- **Reply to a DM**: \`send_message(channel="DM:@peer-name", content="...")\` \u2014 reuse the channel value from the received message
|
|
151
|
+
- **Start a NEW DM**: \`send_message(dm_to="peer-name", content="...")\` \u2014 use the human's name from list_server (no @ prefix)
|
|
152
|
+
|
|
153
|
+
**IMPORTANT**: To reply to any message (channel or DM), always use \`channel\` with the exact identifier from the received message. Only use \`dm_to\` when you want to start a brand new DM that doesn't exist yet.
|
|
154
|
+
|
|
155
|
+
### Discovering people and channels
|
|
156
|
+
|
|
157
|
+
Call \`list_server\` to see all your channels, other agents, and humans in this server.
|
|
158
|
+
|
|
159
|
+
### Reading history
|
|
160
|
+
|
|
161
|
+
\`read_history(channel="#channel-name")\` or \`read_history(channel="DM:@peer-name")\`
|
|
162
|
+
|
|
163
|
+
## @Mentions
|
|
164
|
+
|
|
165
|
+
In channel group chats, you can @mention people by their unique name (e.g. "@alice" or "@bob").
|
|
166
|
+
- Every human and agent has a unique \`name\` \u2014 this is their stable identifier for @mentions.
|
|
167
|
+
- @mentions do not notify people outside the channel \u2014 channels are the isolation boundary.
|
|
168
|
+
|
|
169
|
+
## Communication style
|
|
170
|
+
|
|
171
|
+
Keep the user informed. They cannot see your internal reasoning, so:
|
|
172
|
+
- When you receive a task, acknowledge it and briefly outline your plan before starting.
|
|
173
|
+
- For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
|
|
174
|
+
- When done, summarize the result.
|
|
175
|
+
- Keep updates concise \u2014 one or two sentences. Don't flood the chat.
|
|
176
|
+
|
|
177
|
+
## Workspace & Memory
|
|
178
|
+
|
|
179
|
+
Your working directory (cwd) is your **persistent workspace**. Everything you write here survives across sessions.
|
|
180
|
+
|
|
181
|
+
### MEMORY.md \u2014 Your Memory Index (CRITICAL)
|
|
182
|
+
|
|
183
|
+
\`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. This file is called \`MEMORY.md\` (not tied to any specific runtime) \u2014 keep it updated after every significant interaction or learning.
|
|
184
|
+
|
|
185
|
+
\`\`\`markdown
|
|
186
|
+
# <Your Name>
|
|
187
|
+
|
|
188
|
+
## Role
|
|
189
|
+
<your role definition, evolved over time>
|
|
190
|
+
|
|
191
|
+
## Key Knowledge
|
|
192
|
+
- Read notes/user-preferences.md for user preferences and conventions
|
|
193
|
+
- Read notes/channels.md for what each channel is about and ongoing work
|
|
194
|
+
- Read notes/domain.md for domain-specific knowledge and conventions
|
|
195
|
+
- ...
|
|
196
|
+
|
|
197
|
+
## Active Context
|
|
198
|
+
- Currently working on: <brief summary>
|
|
199
|
+
- Last interaction: <brief summary>
|
|
200
|
+
\`\`\`
|
|
201
|
+
|
|
202
|
+
### What to memorize
|
|
203
|
+
|
|
204
|
+
**Actively observe and record** the following kinds of knowledge as you encounter them in conversations:
|
|
205
|
+
|
|
206
|
+
1. **User preferences** \u2014 How the user likes things done, communication style, coding conventions, tool preferences, recurring patterns in their requests.
|
|
207
|
+
2. **World/project context** \u2014 The project structure, tech stack, architectural decisions, team conventions, deployment patterns.
|
|
208
|
+
3. **Domain knowledge** \u2014 Domain-specific terminology, conventions, best practices you learn through tasks.
|
|
209
|
+
4. **Work history** \u2014 What has been done, decisions made and why, problems solved, approaches that worked or failed.
|
|
210
|
+
5. **Channel context** \u2014 What each channel is about, who participates, what's being discussed, ongoing tasks per channel.
|
|
211
|
+
6. **Other agents** \u2014 What other agents do, their specialties, collaboration patterns, how to work with them effectively.
|
|
212
|
+
|
|
213
|
+
### How to organize memory
|
|
214
|
+
|
|
215
|
+
- **MEMORY.md** is always the index. Keep it concise but comprehensive as a table of contents.
|
|
216
|
+
- Create a \`notes/\` directory for detailed knowledge files. Use descriptive names:
|
|
217
|
+
- \`notes/user-preferences.md\` \u2014 User's preferences and conventions
|
|
218
|
+
- \`notes/channels.md\` \u2014 Summary of each channel and its purpose
|
|
219
|
+
- \`notes/work-log.md\` \u2014 Important decisions and completed work
|
|
220
|
+
- \`notes/<domain>.md\` \u2014 Domain-specific knowledge
|
|
221
|
+
- You can also create any other files or directories for your work (scripts, notes, data, etc.)
|
|
222
|
+
- **Update notes proactively** \u2014 Don't wait to be asked. When you learn something important, write it down.
|
|
223
|
+
- **Keep MEMORY.md current** \u2014 After updating notes, update the index in MEMORY.md if new files were added.
|
|
224
|
+
|
|
225
|
+
### Compaction safety (CRITICAL)
|
|
226
|
+
|
|
227
|
+
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:
|
|
228
|
+
|
|
229
|
+
- **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.
|
|
230
|
+
- **Before a long task**, write a brief "Active Context" note in MEMORY.md so you can resume if interrupted mid-task.
|
|
231
|
+
- **After completing work**, update your notes and MEMORY.md index so nothing is lost.
|
|
232
|
+
- NEVER let context compression cause you to forget: which channel is about what, what tasks are in progress, what the user has asked for, or what other agents are doing.
|
|
233
|
+
|
|
234
|
+
## Capabilities
|
|
235
|
+
|
|
236
|
+
You can work with any files or tools on this computer \u2014 you are not confined to any directory.
|
|
237
|
+
You may develop a specialized role over time through your interactions. Embrace it.`;
|
|
238
|
+
if (opts.includeStdinNotificationSection) {
|
|
239
|
+
prompt += `
|
|
240
|
+
|
|
241
|
+
## Message Notifications
|
|
242
|
+
|
|
243
|
+
While you are busy (executing tools, thinking, etc.), new messages may arrive. When this happens, you will receive a system notification like:
|
|
244
|
+
|
|
245
|
+
\`[System notification: You have N new message(s) waiting. Call receive_message to read them when you're ready.]\`
|
|
246
|
+
|
|
247
|
+
How to handle these:
|
|
248
|
+
- **Do NOT interrupt your current work.** Finish what you're doing first.
|
|
249
|
+
- After completing your current step, call \`${t("receive_message")}(block=false)\` to check for messages.
|
|
250
|
+
- Do not ignore notifications for too long \u2014 acknowledge new messages in a timely manner.
|
|
251
|
+
- These notifications are batched (you won't get one per message), so the count tells you how many are waiting.`;
|
|
252
|
+
}
|
|
253
|
+
if (config.description) {
|
|
254
|
+
prompt += `
|
|
255
|
+
|
|
256
|
+
## Initial role
|
|
257
|
+
${config.description}. This may evolve.`;
|
|
258
|
+
}
|
|
259
|
+
return prompt;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/drivers/claude.ts
|
|
263
|
+
var ClaudeDriver = class {
|
|
264
|
+
id = "claude";
|
|
265
|
+
supportsStdinNotification = true;
|
|
266
|
+
mcpToolPrefix = "mcp__chat__";
|
|
267
|
+
spawn(ctx) {
|
|
268
|
+
const mcpArgs = [
|
|
269
|
+
ctx.chatBridgePath,
|
|
270
|
+
"--agent-id",
|
|
271
|
+
ctx.agentId,
|
|
272
|
+
"--server-url",
|
|
273
|
+
ctx.config.serverUrl,
|
|
274
|
+
"--auth-token",
|
|
275
|
+
ctx.config.authToken || ctx.daemonApiKey
|
|
276
|
+
];
|
|
277
|
+
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
278
|
+
const mcpConfig = JSON.stringify({
|
|
279
|
+
mcpServers: {
|
|
280
|
+
chat: {
|
|
281
|
+
command: isTsSource ? "npx" : "node",
|
|
282
|
+
args: isTsSource ? ["tsx", ...mcpArgs] : mcpArgs
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
const args2 = [
|
|
287
|
+
"--allow-dangerously-skip-permissions",
|
|
288
|
+
"--dangerously-skip-permissions",
|
|
289
|
+
"--verbose",
|
|
290
|
+
"--output-format",
|
|
291
|
+
"stream-json",
|
|
292
|
+
"--input-format",
|
|
293
|
+
"stream-json",
|
|
294
|
+
"--mcp-config",
|
|
295
|
+
mcpConfig,
|
|
296
|
+
"--model",
|
|
297
|
+
ctx.config.model || "sonnet"
|
|
298
|
+
];
|
|
299
|
+
if (ctx.config.sessionId) {
|
|
300
|
+
args2.push("--resume", ctx.config.sessionId);
|
|
301
|
+
}
|
|
302
|
+
const spawnEnv = { ...process.env, FORCE_COLOR: "0" };
|
|
303
|
+
delete spawnEnv.CLAUDECODE;
|
|
304
|
+
const proc = spawn("claude", args2, {
|
|
305
|
+
cwd: ctx.workingDirectory,
|
|
306
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
307
|
+
env: spawnEnv
|
|
308
|
+
});
|
|
309
|
+
const stdinMsg = JSON.stringify({
|
|
310
|
+
type: "user",
|
|
311
|
+
message: {
|
|
312
|
+
role: "user",
|
|
313
|
+
content: [{ type: "text", text: ctx.prompt }]
|
|
314
|
+
},
|
|
315
|
+
...ctx.config.sessionId ? { session_id: ctx.config.sessionId } : {}
|
|
316
|
+
});
|
|
317
|
+
proc.stdin?.write(stdinMsg + "\n");
|
|
318
|
+
return { process: proc };
|
|
319
|
+
}
|
|
320
|
+
parseLine(line) {
|
|
321
|
+
let event;
|
|
322
|
+
try {
|
|
323
|
+
event = JSON.parse(line);
|
|
324
|
+
} catch {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
const events = [];
|
|
328
|
+
switch (event.type) {
|
|
329
|
+
case "system":
|
|
330
|
+
if (event.subtype === "init" && event.session_id) {
|
|
331
|
+
events.push({ kind: "session_init", sessionId: event.session_id });
|
|
332
|
+
}
|
|
333
|
+
break;
|
|
334
|
+
case "assistant": {
|
|
335
|
+
const content = event.message?.content;
|
|
336
|
+
if (Array.isArray(content)) {
|
|
337
|
+
for (const block of content) {
|
|
338
|
+
if (block.type === "thinking" && block.thinking) {
|
|
339
|
+
events.push({ kind: "thinking", text: block.thinking });
|
|
340
|
+
} else if (block.type === "text" && block.text) {
|
|
341
|
+
events.push({ kind: "text", text: block.text });
|
|
342
|
+
} else if (block.type === "tool_use") {
|
|
343
|
+
events.push({ kind: "tool_call", name: block.name || "unknown_tool", input: block.input });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
case "result": {
|
|
350
|
+
events.push({ kind: "turn_end", sessionId: event.session_id });
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return events;
|
|
355
|
+
}
|
|
356
|
+
encodeStdinMessage(text, sessionId) {
|
|
357
|
+
return JSON.stringify({
|
|
358
|
+
type: "user",
|
|
359
|
+
message: {
|
|
360
|
+
role: "user",
|
|
361
|
+
content: [{ type: "text", text }]
|
|
362
|
+
},
|
|
363
|
+
...sessionId ? { session_id: sessionId } : {}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
buildSystemPrompt(config, _agentId) {
|
|
367
|
+
return buildBaseSystemPrompt(config, {
|
|
368
|
+
toolPrefix: "mcp__chat__",
|
|
369
|
+
extraCriticalRules: [
|
|
370
|
+
"- Do NOT use bash/curl/sqlite to send or receive messages. The MCP tools handle everything."
|
|
371
|
+
],
|
|
372
|
+
postStartupNotes: [],
|
|
373
|
+
includeStdinNotificationSection: true
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
toolDisplayName(name) {
|
|
377
|
+
if (name.startsWith("mcp__chat__")) return "";
|
|
378
|
+
if (name === "Read" || name === "read_file") return "Reading file\u2026";
|
|
379
|
+
if (name === "Write" || name === "write_file") return "Writing file\u2026";
|
|
380
|
+
if (name === "Edit" || name === "edit_file") return "Editing file\u2026";
|
|
381
|
+
if (name === "Bash" || name === "bash") return "Running command\u2026";
|
|
382
|
+
if (name === "Glob" || name === "glob") return "Searching files\u2026";
|
|
383
|
+
if (name === "Grep" || name === "grep") return "Searching code\u2026";
|
|
384
|
+
if (name === "WebFetch" || name === "web_fetch") return "Fetching web\u2026";
|
|
385
|
+
if (name === "WebSearch" || name === "web_search") return "Searching web\u2026";
|
|
386
|
+
if (name === "TodoWrite") return "Updating tasks\u2026";
|
|
387
|
+
return `Using ${name.length > 20 ? name.slice(0, 20) + "\u2026" : name}\u2026`;
|
|
388
|
+
}
|
|
389
|
+
summarizeToolInput(name, input) {
|
|
390
|
+
if (!input || typeof input !== "object") return "";
|
|
391
|
+
try {
|
|
392
|
+
if (name === "Read" || name === "read_file") return input.file_path || input.path || "";
|
|
393
|
+
if (name === "Write" || name === "write_file") return input.file_path || input.path || "";
|
|
394
|
+
if (name === "Edit" || name === "edit_file") return input.file_path || input.path || "";
|
|
395
|
+
if (name === "Bash" || name === "bash") {
|
|
396
|
+
const cmd = input.command || "";
|
|
397
|
+
return cmd.length > 100 ? cmd.slice(0, 100) + "\u2026" : cmd;
|
|
398
|
+
}
|
|
399
|
+
if (name === "Glob" || name === "glob") return input.pattern || "";
|
|
400
|
+
if (name === "Grep" || name === "grep") return input.pattern || "";
|
|
401
|
+
if (name === "WebFetch" || name === "web_fetch") return input.url || "";
|
|
402
|
+
if (name === "WebSearch" || name === "web_search") return input.query || "";
|
|
403
|
+
if (name === "mcp__chat__send_message") {
|
|
404
|
+
return input.channel || (input.dm_to ? `DM:@${input.dm_to}` : "");
|
|
405
|
+
}
|
|
406
|
+
if (name === "mcp__chat__read_history") return input.channel || "";
|
|
407
|
+
return "";
|
|
408
|
+
} catch {
|
|
409
|
+
return "";
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// src/drivers/codex.ts
|
|
415
|
+
import { spawn as spawn2, execSync } from "child_process";
|
|
416
|
+
import { existsSync } from "fs";
|
|
417
|
+
import path from "path";
|
|
418
|
+
var CodexDriver = class {
|
|
419
|
+
id = "codex";
|
|
420
|
+
supportsStdinNotification = false;
|
|
421
|
+
mcpToolPrefix = "mcp_chat_";
|
|
422
|
+
spawn(ctx) {
|
|
423
|
+
const gitDir = path.join(ctx.workingDirectory, ".git");
|
|
424
|
+
if (!existsSync(gitDir)) {
|
|
425
|
+
execSync("git init", { cwd: ctx.workingDirectory, stdio: "pipe" });
|
|
426
|
+
execSync("git add -A && git commit --allow-empty -m 'init'", {
|
|
427
|
+
cwd: ctx.workingDirectory,
|
|
428
|
+
stdio: "pipe",
|
|
429
|
+
env: { ...process.env, GIT_AUTHOR_NAME: "slock", GIT_AUTHOR_EMAIL: "slock@local", GIT_COMMITTER_NAME: "slock", GIT_COMMITTER_EMAIL: "slock@local" }
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
433
|
+
const command = isTsSource ? "npx" : "node";
|
|
434
|
+
const bridgeArgs = isTsSource ? ["tsx", ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey] : [ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey];
|
|
435
|
+
const args2 = ["exec"];
|
|
436
|
+
if (ctx.config.sessionId) {
|
|
437
|
+
args2.push("resume", ctx.config.sessionId);
|
|
438
|
+
}
|
|
439
|
+
args2.push(
|
|
440
|
+
"--full-auto",
|
|
441
|
+
"--json"
|
|
442
|
+
);
|
|
443
|
+
args2.push(
|
|
444
|
+
"-c",
|
|
445
|
+
`mcp_servers.chat.command=${JSON.stringify(command)}`,
|
|
446
|
+
"-c",
|
|
447
|
+
`mcp_servers.chat.args=${JSON.stringify(bridgeArgs)}`,
|
|
448
|
+
"-c",
|
|
449
|
+
"mcp_servers.chat.startup_timeout_sec=30",
|
|
450
|
+
"-c",
|
|
451
|
+
"mcp_servers.chat.tool_timeout_sec=120",
|
|
452
|
+
"-c",
|
|
453
|
+
"mcp_servers.chat.enabled=true",
|
|
454
|
+
"-c",
|
|
455
|
+
"mcp_servers.chat.required=true"
|
|
456
|
+
);
|
|
457
|
+
if (ctx.config.model) {
|
|
458
|
+
args2.push("-m", ctx.config.model);
|
|
459
|
+
}
|
|
460
|
+
if (ctx.config.reasoningEffort) {
|
|
461
|
+
args2.push("-c", `model_reasoning_effort=${ctx.config.reasoningEffort}`);
|
|
462
|
+
}
|
|
463
|
+
args2.push(ctx.prompt);
|
|
464
|
+
const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
465
|
+
const proc = spawn2("codex", args2, {
|
|
466
|
+
cwd: ctx.workingDirectory,
|
|
467
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
468
|
+
env: spawnEnv
|
|
469
|
+
});
|
|
470
|
+
return { process: proc };
|
|
471
|
+
}
|
|
472
|
+
parseLine(line) {
|
|
473
|
+
let event;
|
|
474
|
+
try {
|
|
475
|
+
event = JSON.parse(line);
|
|
476
|
+
} catch {
|
|
477
|
+
return [];
|
|
478
|
+
}
|
|
479
|
+
const events = [];
|
|
480
|
+
switch (event.type) {
|
|
481
|
+
case "thread.started":
|
|
482
|
+
if (event.thread_id) {
|
|
483
|
+
events.push({ kind: "session_init", sessionId: event.thread_id });
|
|
484
|
+
}
|
|
485
|
+
break;
|
|
486
|
+
case "turn.started":
|
|
487
|
+
events.push({ kind: "thinking", text: "" });
|
|
488
|
+
break;
|
|
489
|
+
case "item.started":
|
|
490
|
+
case "item.updated":
|
|
491
|
+
case "item.completed": {
|
|
492
|
+
const item = event.item;
|
|
493
|
+
if (!item) break;
|
|
494
|
+
switch (item.type) {
|
|
495
|
+
case "reasoning":
|
|
496
|
+
if (item.text) {
|
|
497
|
+
events.push({ kind: "thinking", text: item.text });
|
|
498
|
+
}
|
|
499
|
+
break;
|
|
500
|
+
case "agent_message":
|
|
501
|
+
if (item.text && event.type === "item.completed") {
|
|
502
|
+
events.push({ kind: "text", text: item.text });
|
|
503
|
+
}
|
|
504
|
+
break;
|
|
505
|
+
case "command_execution":
|
|
506
|
+
if (event.type === "item.started") {
|
|
507
|
+
events.push({ kind: "tool_call", name: "shell", input: { command: item.command } });
|
|
508
|
+
}
|
|
509
|
+
break;
|
|
510
|
+
case "file_change":
|
|
511
|
+
if (event.type === "item.started" && Array.isArray(item.changes)) {
|
|
512
|
+
for (const change of item.changes) {
|
|
513
|
+
events.push({ kind: "tool_call", name: "file_change", input: { path: change.path, kind: change.kind } });
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
517
|
+
case "mcp_tool_call":
|
|
518
|
+
if (event.type === "item.started") {
|
|
519
|
+
const toolName = item.server && item.tool ? `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}` : item.tool || "mcp_tool";
|
|
520
|
+
const name = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : toolName;
|
|
521
|
+
events.push({ kind: "tool_call", name, input: item.arguments });
|
|
522
|
+
}
|
|
523
|
+
break;
|
|
524
|
+
case "collab_tool_call":
|
|
525
|
+
if (event.type === "item.started") {
|
|
526
|
+
events.push({ kind: "tool_call", name: "collab_tool_call", input: {} });
|
|
527
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
case "todo_list":
|
|
530
|
+
if (event.type === "item.started" || event.type === "item.updated") {
|
|
531
|
+
events.push({ kind: "thinking", text: item.title || "Planning\u2026" });
|
|
532
|
+
}
|
|
533
|
+
break;
|
|
534
|
+
case "web_search":
|
|
535
|
+
if (event.type === "item.started") {
|
|
536
|
+
events.push({ kind: "tool_call", name: "web_search", input: { query: item.query } });
|
|
537
|
+
}
|
|
538
|
+
break;
|
|
539
|
+
case "error":
|
|
540
|
+
if (item.message) {
|
|
541
|
+
events.push({ kind: "error", message: item.message });
|
|
542
|
+
}
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
case "turn.completed":
|
|
548
|
+
events.push({ kind: "turn_end" });
|
|
549
|
+
break;
|
|
550
|
+
case "turn.failed":
|
|
551
|
+
if (event.error?.message) {
|
|
552
|
+
events.push({ kind: "error", message: event.error.message });
|
|
553
|
+
}
|
|
554
|
+
events.push({ kind: "turn_end" });
|
|
555
|
+
break;
|
|
556
|
+
case "error":
|
|
557
|
+
events.push({ kind: "error", message: event.message || "Unknown error" });
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
return events;
|
|
561
|
+
}
|
|
562
|
+
encodeStdinMessage(_text, _sessionId) {
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
buildSystemPrompt(config, _agentId) {
|
|
566
|
+
return buildBaseSystemPrompt(config, {
|
|
567
|
+
toolPrefix: "",
|
|
568
|
+
extraCriticalRules: [
|
|
569
|
+
"- Do NOT use shell commands to send or receive messages. The MCP tools handle everything.",
|
|
570
|
+
"- ALWAYS call receive_message(block=true) after completing any task \u2014 this keeps you listening for new messages."
|
|
571
|
+
],
|
|
572
|
+
postStartupNotes: [
|
|
573
|
+
"**IMPORTANT**: Your process exits after each turn completes. You will be automatically restarted when new messages arrive. Always call receive_message(block=true) as your last action \u2014 if no messages are pending, you'll sleep and be woken when one arrives."
|
|
574
|
+
],
|
|
575
|
+
includeStdinNotificationSection: false
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
toolDisplayName(name) {
|
|
579
|
+
if (name.startsWith(this.mcpToolPrefix)) return "";
|
|
580
|
+
if (name === "shell" || name === "command_execution") return "Running command\u2026";
|
|
581
|
+
if (name === "file_change") return "Editing file\u2026";
|
|
582
|
+
if (name === "file_read") return "Reading file\u2026";
|
|
583
|
+
if (name === "file_write") return "Writing file\u2026";
|
|
584
|
+
if (name === "web_search") return "Searching web\u2026";
|
|
585
|
+
if (name === "collab_tool_call") return "Collaborating\u2026";
|
|
586
|
+
return `Using ${name.length > 20 ? name.slice(0, 20) + "\u2026" : name}\u2026`;
|
|
587
|
+
}
|
|
588
|
+
summarizeToolInput(name, input) {
|
|
589
|
+
if (!input || typeof input !== "object") return "";
|
|
590
|
+
try {
|
|
591
|
+
if (name === "shell" || name === "command_execution") {
|
|
592
|
+
const cmd = input.command || "";
|
|
593
|
+
return cmd.length > 100 ? cmd.slice(0, 100) + "\u2026" : cmd;
|
|
594
|
+
}
|
|
595
|
+
if (name === "file_change") return input.path || "";
|
|
596
|
+
if (name === "file_read") return input.path || input.file_path || "";
|
|
597
|
+
if (name === "file_write") return input.path || input.file_path || "";
|
|
598
|
+
if (name === "web_search") return input.query || "";
|
|
599
|
+
if (name === `${this.mcpToolPrefix}send_message`) {
|
|
600
|
+
return input.channel || (input.dm_to ? `DM:@${input.dm_to}` : "");
|
|
601
|
+
}
|
|
602
|
+
if (name === `${this.mcpToolPrefix}read_history`) return input.channel || "";
|
|
603
|
+
return "";
|
|
604
|
+
} catch {
|
|
605
|
+
return "";
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
// src/drivers/index.ts
|
|
611
|
+
var drivers = {
|
|
612
|
+
claude: new ClaudeDriver(),
|
|
613
|
+
codex: new CodexDriver()
|
|
614
|
+
};
|
|
615
|
+
function getDriver(runtimeId) {
|
|
616
|
+
const driver = drivers[runtimeId];
|
|
617
|
+
if (!driver) {
|
|
618
|
+
throw new Error(`Unknown runtime: ${runtimeId}. Available: ${Object.keys(drivers).join(", ")}`);
|
|
619
|
+
}
|
|
620
|
+
return driver;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/agentProcessManager.ts
|
|
624
|
+
var DATA_DIR = path2.join(os.homedir(), ".slock", "agents");
|
|
90
625
|
var MAX_TRAJECTORY_TEXT = 2e3;
|
|
91
626
|
var AgentProcessManager = class {
|
|
92
627
|
agents = /* @__PURE__ */ new Map();
|
|
@@ -100,9 +635,10 @@ var AgentProcessManager = class {
|
|
|
100
635
|
}
|
|
101
636
|
async startAgent(agentId, config, wakeMessage, unreadSummary) {
|
|
102
637
|
if (this.agents.has(agentId)) return;
|
|
103
|
-
const
|
|
638
|
+
const driver = getDriver(config.runtime || "claude");
|
|
639
|
+
const agentDataDir = path2.join(DATA_DIR, agentId);
|
|
104
640
|
await mkdir(agentDataDir, { recursive: true });
|
|
105
|
-
const memoryMdPath =
|
|
641
|
+
const memoryMdPath = path2.join(agentDataDir, "MEMORY.md");
|
|
106
642
|
try {
|
|
107
643
|
await access(memoryMdPath);
|
|
108
644
|
} catch {
|
|
@@ -120,7 +656,7 @@ ${config.description || "No role defined yet."}
|
|
|
120
656
|
`;
|
|
121
657
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
122
658
|
}
|
|
123
|
-
await mkdir(
|
|
659
|
+
await mkdir(path2.join(agentDataDir, "notes"), { recursive: true });
|
|
124
660
|
const isResume = !!config.sessionId;
|
|
125
661
|
let prompt;
|
|
126
662
|
if (isResume && wakeMessage) {
|
|
@@ -147,9 +683,12 @@ Use read_history to catch up, or respond to the message above first.`;
|
|
|
147
683
|
}
|
|
148
684
|
prompt += `
|
|
149
685
|
|
|
150
|
-
Respond as appropriate \u2014 reply using send_message, or take action as needed. Then call receive_message(block=true) to keep listening
|
|
686
|
+
Respond as appropriate \u2014 reply using send_message, or take action as needed. Then call receive_message(block=true) to keep listening.`;
|
|
687
|
+
if (driver.supportsStdinNotification) {
|
|
688
|
+
prompt += `
|
|
151
689
|
|
|
152
690
|
Note: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call receive_message to check.`;
|
|
691
|
+
}
|
|
153
692
|
} else if (isResume && unreadSummary && Object.keys(unreadSummary).length > 0) {
|
|
154
693
|
prompt = `You have unread messages from while you were offline:`;
|
|
155
694
|
for (const [ch, count] of Object.entries(unreadSummary)) {
|
|
@@ -158,58 +697,33 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
158
697
|
}
|
|
159
698
|
prompt += `
|
|
160
699
|
|
|
161
|
-
Use read_history to catch up on important channels, then call receive_message(block=true) to listen for new messages
|
|
700
|
+
Use read_history to catch up on important channels, then call receive_message(block=true) to listen for new messages.`;
|
|
701
|
+
if (driver.supportsStdinNotification) {
|
|
702
|
+
prompt += `
|
|
162
703
|
|
|
163
704
|
Note: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call receive_message to check.`;
|
|
705
|
+
}
|
|
164
706
|
} else if (isResume) {
|
|
165
|
-
prompt =
|
|
707
|
+
prompt = `No new messages while you were away. Call ${driver.mcpToolPrefix}receive_message(block=true) to listen for new messages.`;
|
|
708
|
+
if (driver.supportsStdinNotification) {
|
|
709
|
+
prompt += `
|
|
710
|
+
|
|
711
|
+
Note: While you are busy, you may receive [System notification: ...] messages about new messages. Finish your current step, then call receive_message to check.`;
|
|
712
|
+
}
|
|
166
713
|
} else {
|
|
167
|
-
prompt =
|
|
714
|
+
prompt = driver.buildSystemPrompt(config, agentId);
|
|
168
715
|
}
|
|
169
|
-
const
|
|
170
|
-
this.chatBridgePath,
|
|
171
|
-
"--agent-id",
|
|
716
|
+
const { process: proc } = driver.spawn({
|
|
172
717
|
agentId,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const isTsSource = this.chatBridgePath.endsWith(".ts");
|
|
179
|
-
const mcpConfig = JSON.stringify({
|
|
180
|
-
mcpServers: {
|
|
181
|
-
chat: {
|
|
182
|
-
command: isTsSource ? "npx" : "node",
|
|
183
|
-
args: isTsSource ? ["tsx", ...mcpArgs] : mcpArgs
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
const args2 = [
|
|
188
|
-
"--allow-dangerously-skip-permissions",
|
|
189
|
-
"--dangerously-skip-permissions",
|
|
190
|
-
"--verbose",
|
|
191
|
-
"--output-format",
|
|
192
|
-
"stream-json",
|
|
193
|
-
"--input-format",
|
|
194
|
-
"stream-json",
|
|
195
|
-
"--mcp-config",
|
|
196
|
-
mcpConfig,
|
|
197
|
-
"--model",
|
|
198
|
-
config.model || "sonnet"
|
|
199
|
-
];
|
|
200
|
-
if (config.sessionId) {
|
|
201
|
-
args2.push("--resume", config.sessionId);
|
|
202
|
-
}
|
|
203
|
-
const runtime = config.runtime || "claude";
|
|
204
|
-
const spawnEnv = { ...process.env, FORCE_COLOR: "0" };
|
|
205
|
-
delete spawnEnv.CLAUDECODE;
|
|
206
|
-
const proc = spawn(runtime, args2, {
|
|
207
|
-
cwd: agentDataDir,
|
|
208
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
209
|
-
env: spawnEnv
|
|
718
|
+
config,
|
|
719
|
+
prompt,
|
|
720
|
+
workingDirectory: agentDataDir,
|
|
721
|
+
chatBridgePath: this.chatBridgePath,
|
|
722
|
+
daemonApiKey: this.daemonApiKey
|
|
210
723
|
});
|
|
211
724
|
const agentProcess = {
|
|
212
725
|
process: proc,
|
|
726
|
+
driver,
|
|
213
727
|
inbox: [],
|
|
214
728
|
pendingReceive: null,
|
|
215
729
|
config,
|
|
@@ -219,7 +733,6 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
219
733
|
pendingNotificationCount: 0
|
|
220
734
|
};
|
|
221
735
|
this.agents.set(agentId, agentProcess);
|
|
222
|
-
this.writeStdinMessage(agentProcess, prompt);
|
|
223
736
|
let buffer = "";
|
|
224
737
|
proc.stdout?.on("data", (chunk) => {
|
|
225
738
|
buffer += chunk.toString();
|
|
@@ -227,16 +740,17 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
227
740
|
buffer = lines.pop() || "";
|
|
228
741
|
for (const line of lines) {
|
|
229
742
|
if (!line.trim()) continue;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
this.
|
|
233
|
-
} catch {
|
|
743
|
+
const events = driver.parseLine(line);
|
|
744
|
+
for (const event of events) {
|
|
745
|
+
this.handleParsedEvent(agentId, event, driver);
|
|
234
746
|
}
|
|
235
747
|
}
|
|
236
748
|
});
|
|
237
749
|
proc.stderr?.on("data", (chunk) => {
|
|
238
750
|
const text = chunk.toString().trim();
|
|
239
|
-
if (text)
|
|
751
|
+
if (!text) return;
|
|
752
|
+
if (/Reconnecting\.\.\.|Falling back from WebSockets/i.test(text)) return;
|
|
753
|
+
console.error(`[Agent ${agentId} stderr]: ${text}`);
|
|
240
754
|
});
|
|
241
755
|
proc.on("exit", (code) => {
|
|
242
756
|
console.log(`[Agent ${agentId}] Process exited with code ${code}`);
|
|
@@ -297,6 +811,7 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
297
811
|
} else {
|
|
298
812
|
ap.inbox.push(message);
|
|
299
813
|
}
|
|
814
|
+
if (!ap.driver.supportsStdinNotification) return;
|
|
300
815
|
if (ap.isInReceiveMessage) return;
|
|
301
816
|
if (!ap.sessionId) return;
|
|
302
817
|
ap.pendingNotificationCount++;
|
|
@@ -307,7 +822,7 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
307
822
|
}
|
|
308
823
|
}
|
|
309
824
|
async resetWorkspace(agentId) {
|
|
310
|
-
const agentDataDir =
|
|
825
|
+
const agentDataDir = path2.join(DATA_DIR, agentId);
|
|
311
826
|
try {
|
|
312
827
|
await rm(agentDataDir, { recursive: true, force: true });
|
|
313
828
|
console.log(`[Agent ${agentId}] Workspace deleted: ${agentDataDir}`);
|
|
@@ -333,14 +848,14 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
333
848
|
}
|
|
334
849
|
for (const entry of entries) {
|
|
335
850
|
if (!entry.isDirectory()) continue;
|
|
336
|
-
const dirPath =
|
|
851
|
+
const dirPath = path2.join(DATA_DIR, entry.name);
|
|
337
852
|
try {
|
|
338
853
|
const dirContents = await readdir(dirPath, { withFileTypes: true });
|
|
339
854
|
let totalSize = 0;
|
|
340
855
|
let latestMtime = /* @__PURE__ */ new Date(0);
|
|
341
856
|
let fileCount = 0;
|
|
342
857
|
for (const item of dirContents) {
|
|
343
|
-
const itemPath =
|
|
858
|
+
const itemPath = path2.join(dirPath, item.name);
|
|
344
859
|
try {
|
|
345
860
|
const info = await stat(itemPath);
|
|
346
861
|
if (item.isFile()) {
|
|
@@ -370,7 +885,7 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
370
885
|
if (directoryName.includes("/") || directoryName.includes("..") || directoryName.includes("\\")) {
|
|
371
886
|
return false;
|
|
372
887
|
}
|
|
373
|
-
const targetDir =
|
|
888
|
+
const targetDir = path2.join(DATA_DIR, directoryName);
|
|
374
889
|
try {
|
|
375
890
|
await rm(targetDir, { recursive: true, force: true });
|
|
376
891
|
console.log(`[Workspace] Deleted directory: ${targetDir}`);
|
|
@@ -382,7 +897,7 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
382
897
|
}
|
|
383
898
|
// Workspace file browsing
|
|
384
899
|
async getFileTree(agentId) {
|
|
385
|
-
const agentDir =
|
|
900
|
+
const agentDir = path2.join(DATA_DIR, agentId);
|
|
386
901
|
try {
|
|
387
902
|
await stat(agentDir);
|
|
388
903
|
} catch {
|
|
@@ -392,9 +907,9 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
392
907
|
return this.buildFileTree(agentDir, agentDir, count);
|
|
393
908
|
}
|
|
394
909
|
async readFile(agentId, filePath) {
|
|
395
|
-
const agentDir =
|
|
396
|
-
const resolved =
|
|
397
|
-
if (!resolved.startsWith(agentDir +
|
|
910
|
+
const agentDir = path2.join(DATA_DIR, agentId);
|
|
911
|
+
const resolved = path2.resolve(agentDir, filePath);
|
|
912
|
+
if (!resolved.startsWith(agentDir + path2.sep) && resolved !== agentDir) {
|
|
398
913
|
throw new Error("Access denied");
|
|
399
914
|
}
|
|
400
915
|
const info = await stat(resolved);
|
|
@@ -418,7 +933,7 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
418
933
|
".sh",
|
|
419
934
|
".py"
|
|
420
935
|
]);
|
|
421
|
-
const ext =
|
|
936
|
+
const ext = path2.extname(resolved).toLowerCase();
|
|
422
937
|
if (!TEXT_EXTENSIONS.has(ext) && ext !== "") {
|
|
423
938
|
return { content: null, binary: true };
|
|
424
939
|
}
|
|
@@ -427,17 +942,77 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
427
942
|
return { content, binary: false };
|
|
428
943
|
}
|
|
429
944
|
// Private methods
|
|
430
|
-
/**
|
|
431
|
-
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
945
|
+
/** Handle a single ParsedEvent from any runtime driver */
|
|
946
|
+
handleParsedEvent(agentId, event, driver) {
|
|
947
|
+
const trajectory = [];
|
|
948
|
+
let activity = "";
|
|
949
|
+
let detail = "";
|
|
950
|
+
const ap = this.agents.get(agentId);
|
|
951
|
+
switch (event.kind) {
|
|
952
|
+
case "session_init":
|
|
953
|
+
if (ap) ap.sessionId = event.sessionId;
|
|
954
|
+
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId });
|
|
955
|
+
break;
|
|
956
|
+
case "thinking": {
|
|
957
|
+
const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
|
|
958
|
+
trajectory.push({ kind: "thinking", text });
|
|
959
|
+
activity = "thinking";
|
|
960
|
+
if (ap) ap.isInReceiveMessage = false;
|
|
961
|
+
break;
|
|
962
|
+
}
|
|
963
|
+
case "text": {
|
|
964
|
+
const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
|
|
965
|
+
trajectory.push({ kind: "text", text });
|
|
966
|
+
activity = "thinking";
|
|
967
|
+
if (ap) ap.isInReceiveMessage = false;
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
case "tool_call": {
|
|
971
|
+
const toolName = event.name;
|
|
972
|
+
const inputSummary = driver.summarizeToolInput(toolName, event.input);
|
|
973
|
+
trajectory.push({ kind: "tool_start", toolName, toolInput: inputSummary });
|
|
974
|
+
if (toolName === `${driver.mcpToolPrefix}receive_message`) {
|
|
975
|
+
activity = "online";
|
|
976
|
+
if (ap) {
|
|
977
|
+
ap.isInReceiveMessage = true;
|
|
978
|
+
ap.pendingNotificationCount = 0;
|
|
979
|
+
if (ap.notificationTimer) {
|
|
980
|
+
clearTimeout(ap.notificationTimer);
|
|
981
|
+
ap.notificationTimer = null;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
} else if (toolName === `${driver.mcpToolPrefix}send_message`) {
|
|
985
|
+
activity = "working";
|
|
986
|
+
detail = "Sending message\u2026";
|
|
987
|
+
if (ap) ap.isInReceiveMessage = false;
|
|
988
|
+
} else {
|
|
989
|
+
activity = "working";
|
|
990
|
+
detail = driver.toolDisplayName(toolName);
|
|
991
|
+
if (ap) ap.isInReceiveMessage = false;
|
|
992
|
+
}
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
case "turn_end":
|
|
996
|
+
activity = "online";
|
|
997
|
+
if (ap) {
|
|
998
|
+
ap.isInReceiveMessage = false;
|
|
999
|
+
if (event.sessionId) ap.sessionId = event.sessionId;
|
|
1000
|
+
}
|
|
1001
|
+
if (event.sessionId) {
|
|
1002
|
+
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId });
|
|
1003
|
+
}
|
|
1004
|
+
break;
|
|
1005
|
+
case "error":
|
|
1006
|
+
trajectory.push({ kind: "text", text: `Error: ${event.message}` });
|
|
1007
|
+
break;
|
|
1008
|
+
}
|
|
1009
|
+
if (activity) {
|
|
1010
|
+
this.sendToServer({ type: "agent:activity", agentId, activity, detail });
|
|
1011
|
+
trajectory.push({ kind: "status", activity, detail });
|
|
1012
|
+
}
|
|
1013
|
+
if (trajectory.length > 0) {
|
|
1014
|
+
this.sendToServer({ type: "agent:trajectory", agentId, entries: trajectory });
|
|
1015
|
+
}
|
|
441
1016
|
}
|
|
442
1017
|
/** Send a batched notification to the agent via stdin about pending messages */
|
|
443
1018
|
sendStdinNotification(agentId) {
|
|
@@ -451,7 +1026,10 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
451
1026
|
if (!ap.sessionId) return;
|
|
452
1027
|
const notification = `[System notification: You have ${count} new message${count > 1 ? "s" : ""} waiting. Call receive_message to read ${count > 1 ? "them" : "it"} when you're ready.]`;
|
|
453
1028
|
console.log(`[Agent ${agentId}] Sending stdin notification: ${count} message(s)`);
|
|
454
|
-
|
|
1029
|
+
const encoded = ap.driver.encodeStdinMessage(notification, ap.sessionId);
|
|
1030
|
+
if (encoded) {
|
|
1031
|
+
ap.process.stdin?.write(encoded + "\n");
|
|
1032
|
+
}
|
|
455
1033
|
}
|
|
456
1034
|
async buildFileTree(dir, rootDir, count) {
|
|
457
1035
|
let entries;
|
|
@@ -469,8 +1047,8 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
469
1047
|
for (const entry of entries) {
|
|
470
1048
|
if (count.n >= 500) break;
|
|
471
1049
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
472
|
-
const fullPath =
|
|
473
|
-
const relativePath =
|
|
1050
|
+
const fullPath = path2.join(dir, entry.name);
|
|
1051
|
+
const relativePath = path2.relative(rootDir, fullPath);
|
|
474
1052
|
let info;
|
|
475
1053
|
try {
|
|
476
1054
|
info = await stat(fullPath);
|
|
@@ -487,279 +1065,12 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
487
1065
|
}
|
|
488
1066
|
return nodes;
|
|
489
1067
|
}
|
|
490
|
-
handleStreamEvent(agentId, event) {
|
|
491
|
-
const trajectory = [];
|
|
492
|
-
let activity = "";
|
|
493
|
-
let detail = "";
|
|
494
|
-
switch (event.type) {
|
|
495
|
-
case "system":
|
|
496
|
-
if (event.subtype === "init" && event.session_id) {
|
|
497
|
-
const ap = this.agents.get(agentId);
|
|
498
|
-
if (ap) ap.sessionId = event.session_id;
|
|
499
|
-
this.sendToServer({ type: "agent:session", agentId, sessionId: event.session_id });
|
|
500
|
-
}
|
|
501
|
-
break;
|
|
502
|
-
case "assistant": {
|
|
503
|
-
const content = event.message?.content;
|
|
504
|
-
if (Array.isArray(content)) {
|
|
505
|
-
for (const block of content) {
|
|
506
|
-
if (block.type === "thinking" && block.thinking) {
|
|
507
|
-
const text = block.thinking.length > MAX_TRAJECTORY_TEXT ? block.thinking.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : block.thinking;
|
|
508
|
-
trajectory.push({ kind: "thinking", text });
|
|
509
|
-
} else if (block.type === "text" && block.text) {
|
|
510
|
-
const text = block.text.length > MAX_TRAJECTORY_TEXT ? block.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : block.text;
|
|
511
|
-
trajectory.push({ kind: "text", text });
|
|
512
|
-
} else if (block.type === "tool_use") {
|
|
513
|
-
const toolName = block.name || "unknown_tool";
|
|
514
|
-
const inputSummary = this.summarizeToolInput(toolName, block.input);
|
|
515
|
-
trajectory.push({ kind: "tool_start", toolName, toolInput: inputSummary });
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
const ap = this.agents.get(agentId);
|
|
520
|
-
const toolUses = Array.isArray(content) ? content.filter((c) => c.type === "tool_use") : [];
|
|
521
|
-
if (toolUses.length > 0) {
|
|
522
|
-
const lastTool = toolUses[toolUses.length - 1];
|
|
523
|
-
const toolName = lastTool.name || "tool";
|
|
524
|
-
if (toolName === "mcp__chat__receive_message") {
|
|
525
|
-
activity = "online";
|
|
526
|
-
if (ap) {
|
|
527
|
-
ap.isInReceiveMessage = true;
|
|
528
|
-
ap.pendingNotificationCount = 0;
|
|
529
|
-
if (ap.notificationTimer) {
|
|
530
|
-
clearTimeout(ap.notificationTimer);
|
|
531
|
-
ap.notificationTimer = null;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
} else if (toolName === "mcp__chat__send_message") {
|
|
535
|
-
activity = "working";
|
|
536
|
-
detail = "Sending message\u2026";
|
|
537
|
-
if (ap) ap.isInReceiveMessage = false;
|
|
538
|
-
} else {
|
|
539
|
-
activity = "working";
|
|
540
|
-
detail = this.toolDisplayName(toolName);
|
|
541
|
-
if (ap) ap.isInReceiveMessage = false;
|
|
542
|
-
}
|
|
543
|
-
} else {
|
|
544
|
-
activity = "thinking";
|
|
545
|
-
if (ap) ap.isInReceiveMessage = false;
|
|
546
|
-
}
|
|
547
|
-
break;
|
|
548
|
-
}
|
|
549
|
-
case "result": {
|
|
550
|
-
activity = "online";
|
|
551
|
-
const apResult = this.agents.get(agentId);
|
|
552
|
-
if (apResult) {
|
|
553
|
-
apResult.isInReceiveMessage = false;
|
|
554
|
-
if (event.session_id) apResult.sessionId = event.session_id;
|
|
555
|
-
}
|
|
556
|
-
if (event.session_id) {
|
|
557
|
-
this.sendToServer({ type: "agent:session", agentId, sessionId: event.session_id });
|
|
558
|
-
}
|
|
559
|
-
break;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
if (activity) {
|
|
563
|
-
this.sendToServer({ type: "agent:activity", agentId, activity, detail });
|
|
564
|
-
trajectory.push({ kind: "status", activity, detail });
|
|
565
|
-
}
|
|
566
|
-
if (trajectory.length > 0) {
|
|
567
|
-
this.sendToServer({ type: "agent:trajectory", agentId, entries: trajectory });
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
/** Map raw tool names to user-friendly labels (all end with "…") */
|
|
571
|
-
toolDisplayName(toolName) {
|
|
572
|
-
if (toolName.startsWith("mcp__chat__")) return "";
|
|
573
|
-
if (toolName === "Read" || toolName === "read_file") return "Reading file\u2026";
|
|
574
|
-
if (toolName === "Write" || toolName === "write_file") return "Writing file\u2026";
|
|
575
|
-
if (toolName === "Edit" || toolName === "edit_file") return "Editing file\u2026";
|
|
576
|
-
if (toolName === "Bash" || toolName === "bash") return "Running command\u2026";
|
|
577
|
-
if (toolName === "Glob" || toolName === "glob") return "Searching files\u2026";
|
|
578
|
-
if (toolName === "Grep" || toolName === "grep") return "Searching code\u2026";
|
|
579
|
-
if (toolName === "WebFetch" || toolName === "web_fetch") return "Fetching web\u2026";
|
|
580
|
-
if (toolName === "WebSearch" || toolName === "web_search") return "Searching web\u2026";
|
|
581
|
-
if (toolName === "TodoWrite") return "Updating tasks\u2026";
|
|
582
|
-
return `Using ${toolName.length > 20 ? toolName.slice(0, 20) + "\u2026" : toolName}\u2026`;
|
|
583
|
-
}
|
|
584
|
-
/** Extract a short human-readable summary from tool input */
|
|
585
|
-
summarizeToolInput(toolName, input) {
|
|
586
|
-
if (!input || typeof input !== "object") return "";
|
|
587
|
-
try {
|
|
588
|
-
if (toolName === "Read" || toolName === "read_file") return input.file_path || input.path || "";
|
|
589
|
-
if (toolName === "Write" || toolName === "write_file") return input.file_path || input.path || "";
|
|
590
|
-
if (toolName === "Edit" || toolName === "edit_file") return input.file_path || input.path || "";
|
|
591
|
-
if (toolName === "Bash" || toolName === "bash") {
|
|
592
|
-
const cmd = input.command || "";
|
|
593
|
-
return cmd.length > 100 ? cmd.slice(0, 100) + "\u2026" : cmd;
|
|
594
|
-
}
|
|
595
|
-
if (toolName === "Glob" || toolName === "glob") return input.pattern || "";
|
|
596
|
-
if (toolName === "Grep" || toolName === "grep") return input.pattern || "";
|
|
597
|
-
if (toolName === "WebFetch" || toolName === "web_fetch") return input.url || "";
|
|
598
|
-
if (toolName === "WebSearch" || toolName === "web_search") return input.query || "";
|
|
599
|
-
if (toolName === "mcp__chat__send_message") {
|
|
600
|
-
return input.channel || (input.dm_to ? `DM:@${input.dm_to}` : "");
|
|
601
|
-
}
|
|
602
|
-
if (toolName === "mcp__chat__read_history") return input.channel || "";
|
|
603
|
-
return "";
|
|
604
|
-
} catch {
|
|
605
|
-
return "";
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
buildSystemPrompt(config, agentId) {
|
|
609
|
-
let systemPrompt = `You are "${config.displayName || config.name}", an AI agent in Slock \u2014 a collaborative platform for human-AI collaboration.
|
|
610
|
-
|
|
611
|
-
## Who you are
|
|
612
|
-
|
|
613
|
-
You are a **long-running, persistent agent**. You are NOT a one-shot assistant \u2014 you live across many sessions. You will be started, put to sleep when idle, and woken up again when someone sends you a message. Your process may restart, but your memory persists through files in your workspace directory. Think of yourself as a team member who is always available, accumulates knowledge over time, and develops expertise through interactions.
|
|
614
|
-
|
|
615
|
-
## Communication \u2014 MCP tools ONLY
|
|
616
|
-
|
|
617
|
-
You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
618
|
-
|
|
619
|
-
1. **mcp__chat__receive_message** \u2014 Call with block=true to wait for messages. This is your main loop.
|
|
620
|
-
2. **mcp__chat__send_message** \u2014 Send a message to a channel or DM.
|
|
621
|
-
3. **mcp__chat__list_server** \u2014 List all channels, agents, and humans in this server.
|
|
622
|
-
4. **mcp__chat__read_history** \u2014 Read past messages from a channel or DM.
|
|
623
|
-
|
|
624
|
-
CRITICAL RULES:
|
|
625
|
-
- Do NOT output text directly. ALL communication goes through mcp__chat__send_message.
|
|
626
|
-
- Do NOT use bash/curl/sqlite to send or receive messages. The MCP tools handle everything.
|
|
627
|
-
- Do NOT explore the filesystem looking for messaging scripts. The MCP tools are already available.
|
|
628
|
-
|
|
629
|
-
## Startup sequence
|
|
630
|
-
|
|
631
|
-
1. **Read MEMORY.md** (in your cwd). This is your memory index \u2014 it tells you what you know and where to find it.
|
|
632
|
-
2. Follow the instructions in MEMORY.md to read any other memory files you need (e.g. channel summaries, role definitions, user preferences).
|
|
633
|
-
3. Call mcp__chat__receive_message(block=true) to start listening.
|
|
634
|
-
4. When you receive a message, process it and reply with mcp__chat__send_message.
|
|
635
|
-
5. After replying, call mcp__chat__receive_message(block=true) again to keep listening.
|
|
636
|
-
|
|
637
|
-
## Messaging
|
|
638
|
-
|
|
639
|
-
Messages you receive look like:
|
|
640
|
-
- **Channel message from a human**: \`[#all] @richard: hello everyone\`
|
|
641
|
-
- **Channel message from an agent**: \`[#all] (agent) @Alice: hi there\`
|
|
642
|
-
- **DM from a human**: \`[DM:@richard] @richard: hey, can you help?\`
|
|
643
|
-
|
|
644
|
-
The \`[...]\` prefix identifies where the message came from. Reuse it as the \`channel\` parameter when replying.
|
|
645
|
-
|
|
646
|
-
### Sending messages
|
|
647
|
-
|
|
648
|
-
- **Reply to a channel**: \`send_message(channel="#channel-name", content="...")\`
|
|
649
|
-
- **Reply to a DM**: \`send_message(channel="DM:@peer-name", content="...")\` \u2014 reuse the channel value from the received message
|
|
650
|
-
- **Start a NEW DM**: \`send_message(dm_to="peer-name", content="...")\` \u2014 use the human's name from list_server (no @ prefix)
|
|
651
|
-
|
|
652
|
-
**IMPORTANT**: To reply to any message (channel or DM), always use \`channel\` with the exact identifier from the received message. Only use \`dm_to\` when you want to start a brand new DM that doesn't exist yet.
|
|
653
|
-
|
|
654
|
-
### Discovering people and channels
|
|
655
|
-
|
|
656
|
-
Call \`list_server\` to see all your channels, other agents, and humans in this server.
|
|
657
|
-
|
|
658
|
-
### Reading history
|
|
659
|
-
|
|
660
|
-
\`read_history(channel="#channel-name")\` or \`read_history(channel="DM:@peer-name")\`
|
|
661
|
-
|
|
662
|
-
## @Mentions
|
|
663
|
-
|
|
664
|
-
In channel group chats, you can @mention people by their unique name (e.g. "@alice" or "@bob").
|
|
665
|
-
- Every human and agent has a unique \`name\` \u2014 this is their stable identifier for @mentions.
|
|
666
|
-
- @mentions do not notify people outside the channel \u2014 channels are the isolation boundary.
|
|
667
|
-
|
|
668
|
-
## Communication style
|
|
669
|
-
|
|
670
|
-
Keep the user informed. They cannot see your internal reasoning, so:
|
|
671
|
-
- When you receive a task, acknowledge it and briefly outline your plan before starting.
|
|
672
|
-
- For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
|
|
673
|
-
- When done, summarize the result.
|
|
674
|
-
- Keep updates concise \u2014 one or two sentences. Don't flood the chat.
|
|
675
|
-
|
|
676
|
-
## Workspace & Memory
|
|
677
|
-
|
|
678
|
-
Your working directory (cwd) is your **persistent workspace**. Everything you write here survives across sessions.
|
|
679
|
-
|
|
680
|
-
### MEMORY.md \u2014 Your Memory Index (CRITICAL)
|
|
681
|
-
|
|
682
|
-
\`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. This file is called \`MEMORY.md\` (not tied to any specific runtime) \u2014 keep it updated after every significant interaction or learning.
|
|
683
|
-
|
|
684
|
-
\`\`\`markdown
|
|
685
|
-
# <Your Name>
|
|
686
|
-
|
|
687
|
-
## Role
|
|
688
|
-
<your role definition, evolved over time>
|
|
689
|
-
|
|
690
|
-
## Key Knowledge
|
|
691
|
-
- Read notes/user-preferences.md for user preferences and conventions
|
|
692
|
-
- Read notes/channels.md for what each channel is about and ongoing work
|
|
693
|
-
- Read notes/domain.md for domain-specific knowledge and conventions
|
|
694
|
-
- ...
|
|
695
|
-
|
|
696
|
-
## Active Context
|
|
697
|
-
- Currently working on: <brief summary>
|
|
698
|
-
- Last interaction: <brief summary>
|
|
699
|
-
\`\`\`
|
|
700
|
-
|
|
701
|
-
### What to memorize
|
|
702
|
-
|
|
703
|
-
**Actively observe and record** the following kinds of knowledge as you encounter them in conversations:
|
|
704
|
-
|
|
705
|
-
1. **User preferences** \u2014 How the user likes things done, communication style, coding conventions, tool preferences, recurring patterns in their requests.
|
|
706
|
-
2. **World/project context** \u2014 The project structure, tech stack, architectural decisions, team conventions, deployment patterns.
|
|
707
|
-
3. **Domain knowledge** \u2014 Domain-specific terminology, conventions, best practices you learn through tasks.
|
|
708
|
-
4. **Work history** \u2014 What has been done, decisions made and why, problems solved, approaches that worked or failed.
|
|
709
|
-
5. **Channel context** \u2014 What each channel is about, who participates, what's being discussed, ongoing tasks per channel.
|
|
710
|
-
6. **Other agents** \u2014 What other agents do, their specialties, collaboration patterns, how to work with them effectively.
|
|
711
|
-
|
|
712
|
-
### How to organize memory
|
|
713
|
-
|
|
714
|
-
- **MEMORY.md** is always the index. Keep it concise but comprehensive as a table of contents.
|
|
715
|
-
- Create a \`notes/\` directory for detailed knowledge files. Use descriptive names:
|
|
716
|
-
- \`notes/user-preferences.md\` \u2014 User's preferences and conventions
|
|
717
|
-
- \`notes/channels.md\` \u2014 Summary of each channel and its purpose
|
|
718
|
-
- \`notes/work-log.md\` \u2014 Important decisions and completed work
|
|
719
|
-
- \`notes/<domain>.md\` \u2014 Domain-specific knowledge
|
|
720
|
-
- You can also create any other files or directories for your work (scripts, notes, data, etc.)
|
|
721
|
-
- **Update notes proactively** \u2014 Don't wait to be asked. When you learn something important, write it down.
|
|
722
|
-
- **Keep MEMORY.md current** \u2014 After updating notes, update the index in MEMORY.md if new files were added.
|
|
723
|
-
|
|
724
|
-
### Compaction safety (CRITICAL)
|
|
725
|
-
|
|
726
|
-
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:
|
|
727
|
-
|
|
728
|
-
- **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.
|
|
729
|
-
- **Before a long task**, write a brief "Active Context" note in MEMORY.md so you can resume if interrupted mid-task.
|
|
730
|
-
- **After completing work**, update your notes and MEMORY.md index so nothing is lost.
|
|
731
|
-
- NEVER let context compression cause you to forget: which channel is about what, what tasks are in progress, what the user has asked for, or what other agents are doing.
|
|
732
|
-
|
|
733
|
-
## Capabilities
|
|
734
|
-
|
|
735
|
-
You can work with any files or tools on this computer \u2014 you are not confined to any directory.
|
|
736
|
-
You may develop a specialized role over time through your interactions. Embrace it.
|
|
737
|
-
|
|
738
|
-
## Message Notifications
|
|
739
|
-
|
|
740
|
-
While you are busy (executing tools, thinking, etc.), new messages may arrive. When this happens, you will receive a system notification like:
|
|
741
|
-
|
|
742
|
-
\`[System notification: You have N new message(s) waiting. Call receive_message to read them when you're ready.]\`
|
|
743
|
-
|
|
744
|
-
How to handle these:
|
|
745
|
-
- **Do NOT interrupt your current work.** Finish what you're doing first.
|
|
746
|
-
- After completing your current step, call \`mcp__chat__receive_message(block=false)\` to check for messages.
|
|
747
|
-
- Do not ignore notifications for too long \u2014 acknowledge new messages in a timely manner.
|
|
748
|
-
- These notifications are batched (you won't get one per message), so the count tells you how many are waiting.`;
|
|
749
|
-
if (config.description) {
|
|
750
|
-
systemPrompt += `
|
|
751
|
-
|
|
752
|
-
## Initial role
|
|
753
|
-
${config.description}. This may evolve.`;
|
|
754
|
-
}
|
|
755
|
-
return systemPrompt;
|
|
756
|
-
}
|
|
757
1068
|
};
|
|
758
1069
|
|
|
759
1070
|
// ../shared/src/index.ts
|
|
760
1071
|
var RUNTIMES = [
|
|
761
1072
|
{ id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
|
|
762
|
-
{ id: "codex", displayName: "Codex CLI", binary: "codex", supported:
|
|
1073
|
+
{ id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
|
|
763
1074
|
{ id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: false },
|
|
764
1075
|
{ id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: false }
|
|
765
1076
|
];
|
|
@@ -769,7 +1080,7 @@ function detectRuntimes() {
|
|
|
769
1080
|
const detected = [];
|
|
770
1081
|
for (const rt of RUNTIMES) {
|
|
771
1082
|
try {
|
|
772
|
-
|
|
1083
|
+
execSync2(`which ${rt.binary}`, { stdio: "pipe" });
|
|
773
1084
|
detected.push(rt.id);
|
|
774
1085
|
} catch {
|
|
775
1086
|
}
|
|
@@ -787,12 +1098,12 @@ if (!serverUrl || !apiKey) {
|
|
|
787
1098
|
console.error("Usage: slock-daemon --server-url <url> --api-key <key>");
|
|
788
1099
|
process.exit(1);
|
|
789
1100
|
}
|
|
790
|
-
var __dirname =
|
|
791
|
-
var chatBridgePath =
|
|
1101
|
+
var __dirname = path3.dirname(fileURLToPath(import.meta.url));
|
|
1102
|
+
var chatBridgePath = path3.resolve(__dirname, "chat-bridge.js");
|
|
792
1103
|
try {
|
|
793
1104
|
accessSync(chatBridgePath);
|
|
794
1105
|
} catch {
|
|
795
|
-
chatBridgePath =
|
|
1106
|
+
chatBridgePath = path3.resolve(__dirname, "chat-bridge.ts");
|
|
796
1107
|
}
|
|
797
1108
|
var connection;
|
|
798
1109
|
var agentManager = new AgentProcessManager(chatBridgePath, (msg) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slock-ai/daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"slock-daemon": "dist/index.js"
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
21
|
-
"ws": "^8.
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
21
|
+
"ws": "^8.19.0",
|
|
22
22
|
"zod": "^4.3.6"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@types/node": "^
|
|
26
|
-
"@types/ws": "^8.
|
|
25
|
+
"@types/node": "^25.3.0",
|
|
26
|
+
"@types/ws": "^8.18.1",
|
|
27
27
|
"tsup": "^8.5.1",
|
|
28
|
-
"typescript": "^5.
|
|
28
|
+
"typescript": "^5.9.3",
|
|
29
29
|
"@slock-ai/shared": "0.1.0"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|