@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.
Files changed (2) hide show
  1. package/dist/index.js +664 -353
  2. 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 path2 from "path";
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 path from "path";
86
+ import path2 from "path";
88
87
  import os from "os";
89
- var DATA_DIR = path.join(os.homedir(), ".slock", "agents");
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 agentDataDir = path.join(DATA_DIR, agentId);
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 = path.join(agentDataDir, "MEMORY.md");
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(path.join(agentDataDir, "notes"), { recursive: true });
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 = "No new messages while you were away. Call mcp__chat__receive_message(block=true) to listen for new messages.\n\nNote: While you are busy, you may receive [System notification: ...] messages about new messages. Finish your current step, then call receive_message to check.";
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 = this.buildSystemPrompt(config, agentId);
714
+ prompt = driver.buildSystemPrompt(config, agentId);
168
715
  }
169
- const mcpArgs = [
170
- this.chatBridgePath,
171
- "--agent-id",
716
+ const { process: proc } = driver.spawn({
172
717
  agentId,
173
- "--server-url",
174
- config.serverUrl,
175
- "--auth-token",
176
- config.authToken || this.daemonApiKey
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
- try {
231
- const event = JSON.parse(line);
232
- this.handleStreamEvent(agentId, event);
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) console.error(`[Agent ${agentId} stderr]: ${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 = path.join(DATA_DIR, agentId);
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 = path.join(DATA_DIR, entry.name);
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 = path.join(dirPath, item.name);
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 = path.join(DATA_DIR, directoryName);
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 = path.join(DATA_DIR, agentId);
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 = path.join(DATA_DIR, agentId);
396
- const resolved = path.resolve(agentDir, filePath);
397
- if (!resolved.startsWith(agentDir + path.sep) && resolved !== 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 = path.extname(resolved).toLowerCase();
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
- /** Write a stream-json user message to the agent's stdin */
431
- writeStdinMessage(ap, text) {
432
- const stdinMsg = JSON.stringify({
433
- type: "user",
434
- message: {
435
- role: "user",
436
- content: [{ type: "text", text }]
437
- },
438
- ...ap.sessionId ? { session_id: ap.sessionId } : {}
439
- });
440
- ap.process.stdin?.write(stdinMsg + "\n");
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
- this.writeStdinMessage(ap, notification);
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 = path.join(dir, entry.name);
473
- const relativePath = path.relative(rootDir, fullPath);
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: false },
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
- execSync(`which ${rt.binary}`, { stdio: "pipe" });
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 = path2.dirname(fileURLToPath(import.meta.url));
791
- var chatBridgePath = path2.resolve(__dirname, "chat-bridge.js");
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 = path2.resolve(__dirname, "chat-bridge.ts");
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.2.3",
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.12.1",
21
- "ws": "^8.18.0",
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": "^22.12.0",
26
- "@types/ws": "^8.5.13",
25
+ "@types/node": "^25.3.0",
26
+ "@types/ws": "^8.18.1",
27
27
  "tsup": "^8.5.1",
28
- "typescript": "^5.7.3",
28
+ "typescript": "^5.9.3",
29
29
  "@slock-ai/shared": "0.1.0"
30
30
  },
31
31
  "scripts": {