@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.
- package/dist/chat-bridge.js +10 -2
- package/dist/index.js +133 -104
- package/package.json +1 -1
package/dist/chat-bridge.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
"--
|
|
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
|
-
|
|
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
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
const
|
|
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
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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
|
-
|
|
676
|
-
|
|
686
|
+
for (const [ch, count] of otherUnread) {
|
|
687
|
+
prompt += `
|
|
677
688
|
- ${ch}: ${count} unread`;
|
|
678
|
-
|
|
679
|
-
|
|
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
|
-
|
|
688
|
-
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
-
|
|
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
|
-
|
|
702
|
-
|
|
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
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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
|
-
|
|
764
|
-
|
|
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
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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}`);
|