@slock-ai/daemon 0.4.0 → 0.5.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.
@@ -4,6 +4,12 @@
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
6
  import { z } from "zod";
7
+ function toLocalTime(iso) {
8
+ const d = new Date(iso);
9
+ if (isNaN(d.getTime())) return iso;
10
+ const pad = (n) => String(n).padStart(2, "0");
11
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
12
+ }
7
13
  var args = process.argv.slice(2);
8
14
  var agentId = "";
9
15
  var serverUrl = "http://localhost:3001";
@@ -92,7 +98,8 @@ server.tool(
92
98
  const formatted = data.messages.map((m) => {
93
99
  const channel = m.channel_type === "dm" ? `DM:@${m.channel_name}` : `#${m.channel_name}`;
94
100
  const senderPrefix = m.sender_type === "agent" ? "(agent) " : "";
95
- return `[${channel}] ${senderPrefix}@${m.sender_name}: ${m.content}`;
101
+ const time = m.timestamp ? ` (${toLocalTime(m.timestamp)})` : "";
102
+ return `[${channel}]${time} ${senderPrefix}@${m.sender_name}: ${m.content}`;
96
103
  }).join("\n");
97
104
  return {
98
105
  content: [{ type: "text", text: formatted }]
@@ -193,7 +200,8 @@ server.tool(
193
200
  }
194
201
  const formatted = data.messages.map((m) => {
195
202
  const senderPrefix = m.senderType === "agent" ? "(agent) " : "";
196
- return `[seq:${m.seq}] ${senderPrefix}@${m.senderName}: ${m.content}`;
203
+ const time = m.createdAt ? ` (${toLocalTime(m.createdAt)})` : "";
204
+ return `[seq:${m.seq}]${time} ${senderPrefix}@${m.senderName}: ${m.content}`;
197
205
  }).join("\n");
198
206
  let footer = "";
199
207
  if (data.historyLimited) {
package/dist/index.js CHANGED
@@ -437,7 +437,7 @@ var CodexDriver = class {
437
437
  args2.push("resume", ctx.config.sessionId);
438
438
  }
439
439
  args2.push(
440
- "--full-auto",
440
+ "--dangerously-bypass-approvals-and-sandbox",
441
441
  "--json"
442
442
  );
443
443
  args2.push(
@@ -622,9 +622,17 @@ function getDriver(runtimeId) {
622
622
 
623
623
  // src/agentProcessManager.ts
624
624
  var DATA_DIR = path2.join(os.homedir(), ".slock", "agents");
625
+ function toLocalTime(iso) {
626
+ const d = new Date(iso);
627
+ if (isNaN(d.getTime())) return iso;
628
+ const pad = (n) => String(n).padStart(2, "0");
629
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
630
+ }
625
631
  var MAX_TRAJECTORY_TEXT = 2e3;
626
632
  var AgentProcessManager = class {
627
633
  agents = /* @__PURE__ */ new Map();
634
+ agentsStarting = /* @__PURE__ */ new Set();
635
+ // Prevent concurrent starts of same agent
628
636
  chatBridgePath;
629
637
  sendToServer;
630
638
  daemonApiKey;
@@ -634,16 +642,18 @@ var AgentProcessManager = class {
634
642
  this.daemonApiKey = daemonApiKey;
635
643
  }
636
644
  async startAgent(agentId, config, wakeMessage, unreadSummary) {
637
- if (this.agents.has(agentId)) return;
638
- const driver = getDriver(config.runtime || "claude");
639
- const agentDataDir = path2.join(DATA_DIR, agentId);
640
- await mkdir(agentDataDir, { recursive: true });
641
- const memoryMdPath = path2.join(agentDataDir, "MEMORY.md");
645
+ if (this.agents.has(agentId) || this.agentsStarting.has(agentId)) return;
646
+ this.agentsStarting.add(agentId);
642
647
  try {
643
- await access(memoryMdPath);
644
- } catch {
645
- const agentName = config.displayName || config.name;
646
- const initialMemoryMd = `# ${agentName}
648
+ const driver = getDriver(config.runtime || "claude");
649
+ const agentDataDir = path2.join(DATA_DIR, agentId);
650
+ await mkdir(agentDataDir, { recursive: true });
651
+ const memoryMdPath = path2.join(agentDataDir, "MEMORY.md");
652
+ try {
653
+ await access(memoryMdPath);
654
+ } catch {
655
+ const agentName = config.displayName || config.name;
656
+ const initialMemoryMd = `# ${agentName}
647
657
 
648
658
  ## Role
649
659
  ${config.description || "No role defined yet."}
@@ -654,122 +664,136 @@ ${config.description || "No role defined yet."}
654
664
  ## Active Context
655
665
  - First startup.
656
666
  `;
657
- await writeFile(memoryMdPath, initialMemoryMd);
658
- }
659
- await mkdir(path2.join(agentDataDir, "notes"), { recursive: true });
660
- const isResume = !!config.sessionId;
661
- let prompt;
662
- if (isResume && wakeMessage) {
663
- const channelLabel = wakeMessage.channel_type === "dm" ? `DM:@${wakeMessage.channel_name}` : `#${wakeMessage.channel_name}`;
664
- const senderPrefix = wakeMessage.sender_type === "agent" ? "(agent) " : "";
665
- const formatted = `[${channelLabel}] ${senderPrefix}@${wakeMessage.sender_name}: ${wakeMessage.content}`;
666
- prompt = `New message received:
667
+ await writeFile(memoryMdPath, initialMemoryMd);
668
+ }
669
+ await mkdir(path2.join(agentDataDir, "notes"), { recursive: true });
670
+ const isResume = !!config.sessionId;
671
+ let prompt;
672
+ if (isResume && wakeMessage) {
673
+ const channelLabel = wakeMessage.channel_type === "dm" ? `DM:@${wakeMessage.channel_name}` : `#${wakeMessage.channel_name}`;
674
+ const senderPrefix = wakeMessage.sender_type === "agent" ? "(agent) " : "";
675
+ const time = wakeMessage.timestamp ? ` (${toLocalTime(wakeMessage.timestamp)})` : "";
676
+ const formatted = `[${channelLabel}]${time} ${senderPrefix}@${wakeMessage.sender_name}: ${wakeMessage.content}`;
677
+ prompt = `New message received:
667
678
 
668
679
  ${formatted}`;
669
- if (unreadSummary && Object.keys(unreadSummary).length > 0) {
670
- const otherUnread = Object.entries(unreadSummary).filter(([key]) => key !== channelLabel);
671
- if (otherUnread.length > 0) {
672
- prompt += `
680
+ if (unreadSummary && Object.keys(unreadSummary).length > 0) {
681
+ const otherUnread = Object.entries(unreadSummary).filter(([key]) => key !== channelLabel);
682
+ if (otherUnread.length > 0) {
683
+ prompt += `
673
684
 
674
685
  You also have unread messages in other channels:`;
675
- for (const [ch, count] of otherUnread) {
676
- prompt += `
686
+ for (const [ch, count] of otherUnread) {
687
+ prompt += `
677
688
  - ${ch}: ${count} unread`;
678
- }
679
- prompt += `
689
+ }
690
+ prompt += `
680
691
 
681
692
  Use read_history to catch up, or respond to the message above first.`;
693
+ }
682
694
  }
683
- }
684
- prompt += `
695
+ prompt += `
685
696
 
686
697
  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 += `
698
+ if (driver.supportsStdinNotification) {
699
+ prompt += `
689
700
 
690
701
  Note: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call receive_message to check.`;
691
- }
692
- } else if (isResume && unreadSummary && Object.keys(unreadSummary).length > 0) {
693
- prompt = `You have unread messages from while you were offline:`;
694
- for (const [ch, count] of Object.entries(unreadSummary)) {
695
- prompt += `
702
+ }
703
+ } else if (isResume && unreadSummary && Object.keys(unreadSummary).length > 0) {
704
+ prompt = `You have unread messages from while you were offline:`;
705
+ for (const [ch, count] of Object.entries(unreadSummary)) {
706
+ prompt += `
696
707
  - ${ch}: ${count} unread`;
697
- }
698
- prompt += `
708
+ }
709
+ prompt += `
699
710
 
700
711
  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 += `
712
+ if (driver.supportsStdinNotification) {
713
+ prompt += `
703
714
 
704
715
  Note: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call receive_message to check.`;
705
- }
706
- } else if (isResume) {
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 += `
716
+ }
717
+ } else if (isResume) {
718
+ prompt = `No new messages while you were away. Call ${driver.mcpToolPrefix}receive_message(block=true) to listen for new messages.`;
719
+ if (driver.supportsStdinNotification) {
720
+ prompt += `
710
721
 
711
722
  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
- }
713
- } else {
714
- prompt = driver.buildSystemPrompt(config, agentId);
715
- }
716
- const { process: proc } = driver.spawn({
717
- agentId,
718
- config,
719
- prompt,
720
- workingDirectory: agentDataDir,
721
- chatBridgePath: this.chatBridgePath,
722
- daemonApiKey: this.daemonApiKey
723
- });
724
- const agentProcess = {
725
- process: proc,
726
- driver,
727
- inbox: [],
728
- pendingReceive: null,
729
- config,
730
- sessionId: config.sessionId || null,
731
- isInReceiveMessage: false,
732
- notificationTimer: null,
733
- pendingNotificationCount: 0
734
- };
735
- this.agents.set(agentId, agentProcess);
736
- let buffer = "";
737
- proc.stdout?.on("data", (chunk) => {
738
- buffer += chunk.toString();
739
- const lines = buffer.split("\n");
740
- buffer = lines.pop() || "";
741
- for (const line of lines) {
742
- if (!line.trim()) continue;
743
- const events = driver.parseLine(line);
744
- for (const event of events) {
745
- this.handleParsedEvent(agentId, event, driver);
746
723
  }
724
+ } else {
725
+ prompt = driver.buildSystemPrompt(config, agentId);
747
726
  }
748
- });
749
- proc.stderr?.on("data", (chunk) => {
750
- const text = chunk.toString().trim();
751
- if (!text) return;
752
- if (/Reconnecting\.\.\.|Falling back from WebSockets/i.test(text)) return;
753
- console.error(`[Agent ${agentId} stderr]: ${text}`);
754
- });
755
- proc.on("exit", (code) => {
756
- console.log(`[Agent ${agentId}] Process exited with code ${code}`);
757
- if (this.agents.has(agentId)) {
758
- const ap = this.agents.get(agentId);
759
- if (ap.pendingReceive) {
760
- clearTimeout(ap.pendingReceive.timer);
761
- ap.pendingReceive.resolve([]);
727
+ const { process: proc } = driver.spawn({
728
+ agentId,
729
+ config,
730
+ prompt,
731
+ workingDirectory: agentDataDir,
732
+ chatBridgePath: this.chatBridgePath,
733
+ daemonApiKey: this.daemonApiKey
734
+ });
735
+ const agentProcess = {
736
+ process: proc,
737
+ driver,
738
+ inbox: [],
739
+ pendingReceive: null,
740
+ config,
741
+ sessionId: config.sessionId || null,
742
+ isInReceiveMessage: false,
743
+ notificationTimer: null,
744
+ pendingNotificationCount: 0
745
+ };
746
+ this.agents.set(agentId, agentProcess);
747
+ this.agentsStarting.delete(agentId);
748
+ let buffer = "";
749
+ proc.stdout?.on("data", (chunk) => {
750
+ buffer += chunk.toString();
751
+ const lines = buffer.split("\n");
752
+ buffer = lines.pop() || "";
753
+ for (const line of lines) {
754
+ if (!line.trim()) continue;
755
+ const events = driver.parseLine(line);
756
+ for (const event of events) {
757
+ this.handleParsedEvent(agentId, event, driver);
758
+ }
762
759
  }
763
- if (ap.notificationTimer) {
764
- clearTimeout(ap.notificationTimer);
760
+ });
761
+ proc.stderr?.on("data", (chunk) => {
762
+ const text = chunk.toString().trim();
763
+ if (!text) return;
764
+ if (/Reconnecting\.\.\.|Falling back from WebSockets/i.test(text)) return;
765
+ console.error(`[Agent ${agentId} stderr]: ${text}`);
766
+ });
767
+ proc.on("exit", (code) => {
768
+ console.log(`[Agent ${agentId}] Process exited with code ${code}`);
769
+ if (this.agents.has(agentId)) {
770
+ const ap = this.agents.get(agentId);
771
+ if (ap.process !== proc) return;
772
+ if (ap.pendingReceive) {
773
+ clearTimeout(ap.pendingReceive.timer);
774
+ ap.pendingReceive.resolve([]);
775
+ }
776
+ if (ap.notificationTimer) {
777
+ clearTimeout(ap.notificationTimer);
778
+ }
779
+ this.agents.delete(agentId);
780
+ if (code === 0) {
781
+ this.sendToServer({ type: "agent:status", agentId, status: "sleeping" });
782
+ this.sendToServer({ type: "agent:activity", agentId, activity: "sleeping", detail: "" });
783
+ } else {
784
+ const reason = code === null ? "killed by signal" : `exit code ${code}`;
785
+ console.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
786
+ this.sendToServer({ type: "agent:status", agentId, status: "inactive" });
787
+ this.sendToServer({ type: "agent:activity", agentId, activity: "offline", detail: `Crashed (${reason})` });
788
+ }
765
789
  }
766
- this.agents.delete(agentId);
767
- this.sendToServer({ type: "agent:status", agentId, status: "sleeping" });
768
- this.sendToServer({ type: "agent:activity", agentId, activity: "sleeping", detail: "" });
769
- }
770
- });
771
- this.sendToServer({ type: "agent:status", agentId, status: "active" });
772
- this.sendToServer({ type: "agent:activity", agentId, activity: "working", detail: "Starting\u2026" });
790
+ });
791
+ this.sendToServer({ type: "agent:status", agentId, status: "active" });
792
+ this.sendToServer({ type: "agent:activity", agentId, activity: "working", detail: "Starting\u2026" });
793
+ } catch (err) {
794
+ this.agentsStarting.delete(agentId);
795
+ throw err;
796
+ }
773
797
  }
774
798
  async stopAgent(agentId) {
775
799
  const ap = this.agents.get(agentId);
@@ -1117,7 +1141,12 @@ connection = new DaemonConnection({
1117
1141
  switch (msg.type) {
1118
1142
  case "agent:start":
1119
1143
  console.log(`[Daemon] Starting agent ${msg.agentId} (model: ${msg.config.model}, session: ${msg.config.sessionId || "new"}${msg.wakeMessage ? ", with wake message" : ""})`);
1120
- agentManager.startAgent(msg.agentId, msg.config, msg.wakeMessage, msg.unreadSummary);
1144
+ agentManager.startAgent(msg.agentId, msg.config, msg.wakeMessage, msg.unreadSummary).catch((err) => {
1145
+ const reason = err instanceof Error ? err.message : String(err);
1146
+ console.error(`[Daemon] Failed to start agent ${msg.agentId}:`, reason);
1147
+ connection.send({ type: "agent:status", agentId: msg.agentId, status: "inactive" });
1148
+ connection.send({ type: "agent:activity", agentId: msg.agentId, activity: "offline", detail: `Start failed: ${reason}` });
1149
+ });
1121
1150
  break;
1122
1151
  case "agent:stop":
1123
1152
  console.log(`[Daemon] Stopping agent ${msg.agentId}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"