@slock-ai/daemon 0.1.0 → 0.2.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/package.json +4 -4
- package/src/agentProcessManager.ts +39 -8
- package/src/chat-bridge.ts +41 -15
- package/src/index.ts +6 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slock-ai/daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"slock-daemon": "src/index.ts"
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"start": "tsx src/index.ts",
|
|
34
34
|
"build": "tsc",
|
|
35
35
|
"typecheck": "tsc --noEmit",
|
|
36
|
-
"release:patch": "npm version patch && git push && git push --tags",
|
|
37
|
-
"release:minor": "npm version minor && git push && git push --tags",
|
|
38
|
-
"release:major": "npm version major && git push && git push --tags"
|
|
36
|
+
"release:patch": "npm version patch --no-git-tag-version && cd ../.. && git add packages/daemon/package.json && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p require('./packages/daemon/package.json').version)\" && git tag daemon-v$(node -p require('./packages/daemon/package.json').version) && git push && git push --tags",
|
|
37
|
+
"release:minor": "npm version minor --no-git-tag-version && cd ../.. && git add packages/daemon/package.json && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p require('./packages/daemon/package.json').version)\" && git tag daemon-v$(node -p require('./packages/daemon/package.json').version) && git push && git push --tags",
|
|
38
|
+
"release:major": "npm version major --no-git-tag-version && cd ../.. && git add packages/daemon/package.json && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p require('./packages/daemon/package.json').version)\" && git tag daemon-v$(node -p require('./packages/daemon/package.json').version) && git push && git push --tags"
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -35,7 +35,7 @@ export class AgentProcessManager {
|
|
|
35
35
|
this.daemonApiKey = daemonApiKey;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
async startAgent(agentId: string, config: AgentConfig) {
|
|
38
|
+
async startAgent(agentId: string, config: AgentConfig, wakeMessage?: AgentMessage, unreadSummary?: Record<string, number>) {
|
|
39
39
|
if (this.agents.has(agentId)) return;
|
|
40
40
|
|
|
41
41
|
const agentDataDir = path.join(DATA_DIR, agentId);
|
|
@@ -55,9 +55,40 @@ export class AgentProcessManager {
|
|
|
55
55
|
|
|
56
56
|
// Build prompt
|
|
57
57
|
const isResume = !!config.sessionId;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
let prompt: string;
|
|
59
|
+
if (isResume && wakeMessage) {
|
|
60
|
+
// Format the wake message inline so the agent sees it immediately
|
|
61
|
+
const channelLabel = wakeMessage.channel_type === "dm"
|
|
62
|
+
? `DM:@${wakeMessage.channel_name}` : `#${wakeMessage.channel_name}`;
|
|
63
|
+
const senderPrefix = wakeMessage.sender_type === "agent" ? "(agent) " : "";
|
|
64
|
+
const formatted = `[${channelLabel}] ${senderPrefix}@${wakeMessage.sender_name}: ${wakeMessage.content}`;
|
|
65
|
+
prompt = `New message received:\n\n${formatted}`;
|
|
66
|
+
|
|
67
|
+
// Add unread summary for other channels (exclude the wake message's channel)
|
|
68
|
+
if (unreadSummary && Object.keys(unreadSummary).length > 0) {
|
|
69
|
+
const otherUnread = Object.entries(unreadSummary).filter(([key]) => key !== channelLabel);
|
|
70
|
+
if (otherUnread.length > 0) {
|
|
71
|
+
prompt += `\n\nYou also have unread messages in other channels:`;
|
|
72
|
+
for (const [ch, count] of otherUnread) {
|
|
73
|
+
prompt += `\n- ${ch}: ${count} unread`;
|
|
74
|
+
}
|
|
75
|
+
prompt += `\n\nUse read_history to catch up, or respond to the message above first.`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
prompt += `\n\nRespond as appropriate — reply using send_message, or take action as needed. Then call receive_message(block=true) to keep listening.\n\nNote: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call receive_message to check.`;
|
|
80
|
+
} else if (isResume && unreadSummary && Object.keys(unreadSummary).length > 0) {
|
|
81
|
+
// Resuming with unread messages (e.g. agent was stopped, messages arrived, then restarted)
|
|
82
|
+
prompt = `You have unread messages from while you were offline:`;
|
|
83
|
+
for (const [ch, count] of Object.entries(unreadSummary)) {
|
|
84
|
+
prompt += `\n- ${ch}: ${count} unread`;
|
|
85
|
+
}
|
|
86
|
+
prompt += `\n\nUse read_history to catch up on important channels, then call receive_message(block=true) to listen for new messages.\n\nNote: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call receive_message to check.`;
|
|
87
|
+
} else if (isResume) {
|
|
88
|
+
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.";
|
|
89
|
+
} else {
|
|
90
|
+
prompt = this.buildSystemPrompt(config, agentId);
|
|
91
|
+
}
|
|
61
92
|
|
|
62
93
|
// Build MCP config — point to remote server
|
|
63
94
|
// Use daemon's own API key for chat-bridge authentication with /internal/* routes
|
|
@@ -535,7 +566,7 @@ export class AgentProcessManager {
|
|
|
535
566
|
}
|
|
536
567
|
|
|
537
568
|
private buildSystemPrompt(config: AgentConfig, agentId: string): string {
|
|
538
|
-
let systemPrompt = `You are "${config.displayName || config.name}", an AI agent in Slock — a collaborative
|
|
569
|
+
let systemPrompt = `You are "${config.displayName || config.name}", an AI agent in Slock — a collaborative platform for human-AI collaboration.
|
|
539
570
|
|
|
540
571
|
## Who you are
|
|
541
572
|
|
|
@@ -547,7 +578,7 @@ You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
|
547
578
|
|
|
548
579
|
1. **mcp__chat__receive_message** — Call with block=true to wait for messages. This is your main loop.
|
|
549
580
|
2. **mcp__chat__send_message** — Send a message to a channel or DM.
|
|
550
|
-
3. **
|
|
581
|
+
3. **mcp__chat__list_server** — List all channels, agents, and humans in this server.
|
|
551
582
|
4. **mcp__chat__read_history** — Read past messages from a channel or DM.
|
|
552
583
|
|
|
553
584
|
CRITICAL RULES:
|
|
@@ -576,13 +607,13 @@ The \`[...]\` prefix identifies where the message came from. Reuse it as the \`c
|
|
|
576
607
|
|
|
577
608
|
- **Reply to a channel**: \`send_message(channel="#channel-name", content="...")\`
|
|
578
609
|
- **Reply to a DM**: \`send_message(channel="DM:@peer-name", content="...")\` — reuse the channel value from the received message
|
|
579
|
-
- **Start a NEW DM**: \`send_message(dm_to="peer-name", content="...")\` — use the human's name from
|
|
610
|
+
- **Start a NEW DM**: \`send_message(dm_to="peer-name", content="...")\` — use the human's name from list_server (no @ prefix)
|
|
580
611
|
|
|
581
612
|
**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.
|
|
582
613
|
|
|
583
614
|
### Discovering people and channels
|
|
584
615
|
|
|
585
|
-
Call \`
|
|
616
|
+
Call \`list_server\` to see all your channels, other agents, and humans in this server.
|
|
586
617
|
|
|
587
618
|
### Reading history
|
|
588
619
|
|
package/src/chat-bridge.ts
CHANGED
|
@@ -135,20 +135,20 @@ server.tool(
|
|
|
135
135
|
}
|
|
136
136
|
);
|
|
137
137
|
|
|
138
|
-
//
|
|
138
|
+
// list_server tool
|
|
139
139
|
server.tool(
|
|
140
|
-
"
|
|
141
|
-
"List all channels you are in, all agents, and all humans in
|
|
140
|
+
"list_server",
|
|
141
|
+
"List all channels you are in, all agents, and all humans in this server. Use this to discover who and where you can message.",
|
|
142
142
|
{},
|
|
143
143
|
async () => {
|
|
144
144
|
try {
|
|
145
145
|
const res = await fetch(
|
|
146
|
-
`${serverUrl}/internal/agent/${agentId}/
|
|
146
|
+
`${serverUrl}/internal/agent/${agentId}/server`,
|
|
147
147
|
{ method: "GET", headers: commonHeaders }
|
|
148
148
|
);
|
|
149
149
|
const data = await res.json();
|
|
150
150
|
|
|
151
|
-
let text = "##
|
|
151
|
+
let text = "## Server\n\n";
|
|
152
152
|
|
|
153
153
|
text += "### Your Channels\n";
|
|
154
154
|
text += "Use `#channel-name` with send_message to post in a channel.\n";
|
|
@@ -161,7 +161,7 @@ server.tool(
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
text += "\n### Agents\n";
|
|
164
|
-
text += "Other AI agents in this
|
|
164
|
+
text += "Other AI agents in this server.\n";
|
|
165
165
|
if (data.agents?.length > 0) {
|
|
166
166
|
for (const a of data.agents) {
|
|
167
167
|
text += ` - @${a.name} (${a.status})\n`;
|
|
@@ -174,7 +174,7 @@ server.tool(
|
|
|
174
174
|
text += "To start a new DM: send_message(dm_to=\"<name>\"). To reply in an existing DM: reuse channel from the received message.\n";
|
|
175
175
|
if (data.humans?.length > 0) {
|
|
176
176
|
for (const u of data.humans) {
|
|
177
|
-
text += ` -
|
|
177
|
+
text += ` - @${u.name}\n`;
|
|
178
178
|
}
|
|
179
179
|
} else {
|
|
180
180
|
text += " (none)\n";
|
|
@@ -194,19 +194,29 @@ server.tool(
|
|
|
194
194
|
// read_history tool
|
|
195
195
|
server.tool(
|
|
196
196
|
"read_history",
|
|
197
|
-
"Read message history for a channel or DM. Use #channel-name for channels or DM:@name for DMs.",
|
|
197
|
+
"Read message history for a channel or DM. Use #channel-name for channels or DM:@name for DMs. Supports pagination: use 'before' to load older messages, 'after' to load messages after a seq number (e.g. to catch up on unread).",
|
|
198
198
|
{
|
|
199
199
|
channel: z.string().describe("The channel to read history from — e.g. '#all', '#general', 'DM:@richard'"),
|
|
200
200
|
limit: z
|
|
201
201
|
.number()
|
|
202
202
|
.default(50)
|
|
203
203
|
.describe("Max number of messages to return (default 50, max 100)"),
|
|
204
|
+
before: z
|
|
205
|
+
.number()
|
|
206
|
+
.optional()
|
|
207
|
+
.describe("Return messages before this seq number (for backward pagination). Omit for latest messages."),
|
|
208
|
+
after: z
|
|
209
|
+
.number()
|
|
210
|
+
.optional()
|
|
211
|
+
.describe("Return messages after this seq number (for catching up on unread). Returns oldest-first."),
|
|
204
212
|
},
|
|
205
|
-
async ({ channel, limit }) => {
|
|
213
|
+
async ({ channel, limit, before, after }) => {
|
|
206
214
|
try {
|
|
207
215
|
const params = new URLSearchParams();
|
|
208
216
|
params.set("channel", channel);
|
|
209
|
-
|
|
217
|
+
params.set("limit", String(Math.min(limit, 100)));
|
|
218
|
+
if (before) params.set("before", String(before));
|
|
219
|
+
if (after) params.set("after", String(after));
|
|
210
220
|
|
|
211
221
|
const res = await fetch(
|
|
212
222
|
`${serverUrl}/internal/agent/${agentId}/history?${params}`,
|
|
@@ -231,17 +241,33 @@ server.tool(
|
|
|
231
241
|
}
|
|
232
242
|
|
|
233
243
|
const formatted = data.messages
|
|
234
|
-
.map(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
)
|
|
244
|
+
.map((m: any) => {
|
|
245
|
+
const senderPrefix = m.senderType === "agent" ? "(agent) " : "";
|
|
246
|
+
return `[seq:${m.seq}] ${senderPrefix}@${m.senderName}: ${m.content}`;
|
|
247
|
+
})
|
|
238
248
|
.join("\n");
|
|
239
249
|
|
|
250
|
+
let footer = "";
|
|
251
|
+
if (data.has_more && data.messages.length > 0) {
|
|
252
|
+
if (after) {
|
|
253
|
+
const maxSeq = data.messages[data.messages.length - 1].seq;
|
|
254
|
+
footer = `\n\n--- ${data.messages.length} messages shown. Use after=${maxSeq} to load more recent messages. ---`;
|
|
255
|
+
} else {
|
|
256
|
+
const minSeq = data.messages[0].seq;
|
|
257
|
+
footer = `\n\n--- ${data.messages.length} messages shown. Use before=${minSeq} to load older messages. ---`;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let header = `## Message History for ${channel} (${data.messages.length} messages)`;
|
|
262
|
+
if (data.last_read_seq > 0 && !after && !before) {
|
|
263
|
+
header += `\nYour last read position: seq ${data.last_read_seq}. Use read_history(channel="${channel}", after=${data.last_read_seq}) to see only unread messages.`;
|
|
264
|
+
}
|
|
265
|
+
|
|
240
266
|
return {
|
|
241
267
|
content: [
|
|
242
268
|
{
|
|
243
269
|
type: "text" as const,
|
|
244
|
-
text:
|
|
270
|
+
text: `${header}\n\n${formatted}${footer}`,
|
|
245
271
|
},
|
|
246
272
|
],
|
|
247
273
|
};
|
package/src/index.ts
CHANGED
|
@@ -5,15 +5,15 @@ import { execSync } from "node:child_process";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { DaemonConnection } from "./connection.js";
|
|
7
7
|
import { AgentProcessManager } from "./agentProcessManager.js";
|
|
8
|
-
import type
|
|
8
|
+
import { RUNTIMES, type ServerToMachineMessage } from "@slock-ai/shared";
|
|
9
9
|
|
|
10
10
|
/** Detect which CLI runtimes are installed on this machine */
|
|
11
11
|
function detectRuntimes(): string[] {
|
|
12
12
|
const detected: string[] = [];
|
|
13
|
-
for (const
|
|
13
|
+
for (const rt of RUNTIMES) {
|
|
14
14
|
try {
|
|
15
|
-
execSync(`which ${
|
|
16
|
-
detected.push(
|
|
15
|
+
execSync(`which ${rt.binary}`, { stdio: "pipe" });
|
|
16
|
+
detected.push(rt.id);
|
|
17
17
|
} catch { /* not installed */ }
|
|
18
18
|
}
|
|
19
19
|
return detected;
|
|
@@ -52,8 +52,8 @@ connection = new DaemonConnection({
|
|
|
52
52
|
console.log(`[Daemon] Received: ${msg.type}`, msg.type === "ping" ? "" : JSON.stringify(msg).slice(0, 200));
|
|
53
53
|
switch (msg.type) {
|
|
54
54
|
case "agent:start":
|
|
55
|
-
console.log(`[Daemon] Starting agent ${msg.agentId} (model: ${msg.config.model}, session: ${msg.config.sessionId || "new"})`);
|
|
56
|
-
agentManager.startAgent(msg.agentId, msg.config);
|
|
55
|
+
console.log(`[Daemon] Starting agent ${msg.agentId} (model: ${msg.config.model}, session: ${msg.config.sessionId || "new"}${msg.wakeMessage ? ", with wake message" : ""})`);
|
|
56
|
+
agentManager.startAgent(msg.agentId, msg.config, msg.wakeMessage, msg.unreadSummary);
|
|
57
57
|
break;
|
|
58
58
|
|
|
59
59
|
case "agent:stop":
|