@slock-ai/daemon 0.4.1 → 0.6.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 +147 -113
- 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
|
@@ -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);
|
|
@@ -896,15 +920,22 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
896
920
|
}
|
|
897
921
|
}
|
|
898
922
|
// Workspace file browsing
|
|
899
|
-
async getFileTree(agentId) {
|
|
923
|
+
async getFileTree(agentId, dirPath) {
|
|
900
924
|
const agentDir = path2.join(DATA_DIR, agentId);
|
|
901
925
|
try {
|
|
902
926
|
await stat(agentDir);
|
|
903
927
|
} catch {
|
|
904
928
|
return [];
|
|
905
929
|
}
|
|
906
|
-
|
|
907
|
-
|
|
930
|
+
let targetDir = agentDir;
|
|
931
|
+
if (dirPath) {
|
|
932
|
+
const resolved = path2.resolve(agentDir, dirPath);
|
|
933
|
+
if (!resolved.startsWith(agentDir + path2.sep) && resolved !== agentDir) {
|
|
934
|
+
return [];
|
|
935
|
+
}
|
|
936
|
+
targetDir = resolved;
|
|
937
|
+
}
|
|
938
|
+
return this.listDirectoryChildren(targetDir, agentDir);
|
|
908
939
|
}
|
|
909
940
|
async readFile(agentId, filePath) {
|
|
910
941
|
const agentDir = path2.join(DATA_DIR, agentId);
|
|
@@ -1031,7 +1062,8 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1031
1062
|
ap.process.stdin?.write(encoded + "\n");
|
|
1032
1063
|
}
|
|
1033
1064
|
}
|
|
1034
|
-
|
|
1065
|
+
/** List ONE level of a directory — directories returned without children (lazy-loaded on demand) */
|
|
1066
|
+
async listDirectoryChildren(dir, rootDir) {
|
|
1035
1067
|
let entries;
|
|
1036
1068
|
try {
|
|
1037
1069
|
entries = await readdir(dir, { withFileTypes: true });
|
|
@@ -1045,7 +1077,6 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1045
1077
|
});
|
|
1046
1078
|
const nodes = [];
|
|
1047
1079
|
for (const entry of entries) {
|
|
1048
|
-
if (count.n >= 500) break;
|
|
1049
1080
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
1050
1081
|
const fullPath = path2.join(dir, entry.name);
|
|
1051
1082
|
const relativePath = path2.relative(rootDir, fullPath);
|
|
@@ -1055,10 +1086,8 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1055
1086
|
} catch {
|
|
1056
1087
|
continue;
|
|
1057
1088
|
}
|
|
1058
|
-
count.n++;
|
|
1059
1089
|
if (entry.isDirectory()) {
|
|
1060
|
-
|
|
1061
|
-
nodes.push({ name: entry.name, path: relativePath, isDirectory: true, size: 0, modifiedAt: info.mtime.toISOString(), children });
|
|
1090
|
+
nodes.push({ name: entry.name, path: relativePath, isDirectory: true, size: 0, modifiedAt: info.mtime.toISOString() });
|
|
1062
1091
|
} else {
|
|
1063
1092
|
nodes.push({ name: entry.name, path: relativePath, isDirectory: false, size: info.size, modifiedAt: info.mtime.toISOString() });
|
|
1064
1093
|
}
|
|
@@ -1117,7 +1146,12 @@ connection = new DaemonConnection({
|
|
|
1117
1146
|
switch (msg.type) {
|
|
1118
1147
|
case "agent:start":
|
|
1119
1148
|
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)
|
|
1149
|
+
agentManager.startAgent(msg.agentId, msg.config, msg.wakeMessage, msg.unreadSummary).catch((err) => {
|
|
1150
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1151
|
+
console.error(`[Daemon] Failed to start agent ${msg.agentId}:`, reason);
|
|
1152
|
+
connection.send({ type: "agent:status", agentId: msg.agentId, status: "inactive" });
|
|
1153
|
+
connection.send({ type: "agent:activity", agentId: msg.agentId, activity: "offline", detail: `Start failed: ${reason}` });
|
|
1154
|
+
});
|
|
1121
1155
|
break;
|
|
1122
1156
|
case "agent:stop":
|
|
1123
1157
|
console.log(`[Daemon] Stopping agent ${msg.agentId}`);
|
|
@@ -1137,8 +1171,8 @@ connection = new DaemonConnection({
|
|
|
1137
1171
|
connection.send({ type: "agent:deliver:ack", agentId: msg.agentId, seq: msg.seq });
|
|
1138
1172
|
break;
|
|
1139
1173
|
case "agent:workspace:list":
|
|
1140
|
-
agentManager.getFileTree(msg.agentId).then((files) => {
|
|
1141
|
-
connection.send({ type: "agent:workspace:file_tree", agentId: msg.agentId, files });
|
|
1174
|
+
agentManager.getFileTree(msg.agentId, msg.dirPath).then((files) => {
|
|
1175
|
+
connection.send({ type: "agent:workspace:file_tree", agentId: msg.agentId, files, dirPath: msg.dirPath });
|
|
1142
1176
|
});
|
|
1143
1177
|
break;
|
|
1144
1178
|
case "agent:workspace:read":
|