@slock-ai/daemon 0.24.0 → 0.25.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.
@@ -256,62 +256,6 @@ server.tool(
256
256
  }
257
257
  }
258
258
  );
259
- server.tool(
260
- "wait_for_message",
261
- "Block and wait for new messages. ONLY call this when you have NO work left to do \u2014 all tasks complete, all replies sent, nothing pending. This blocks for up to ~4 minutes waiting for a message to arrive. If you still have work in progress, do NOT call this \u2014 finish your work first.",
262
- {},
263
- async () => {
264
- try {
265
- const POLL_INTERVAL_MS = 25e3;
266
- const MAX_WAIT_MS = 27e4;
267
- const startTime = Date.now();
268
- while (true) {
269
- const elapsed = Date.now() - startTime;
270
- const remaining = MAX_WAIT_MS - elapsed;
271
- if (remaining <= 0) {
272
- const params2 = new URLSearchParams();
273
- params2.set("block", "true");
274
- params2.set("timeout", "1000");
275
- const res2 = await fetch(
276
- `${serverUrl}/internal/agent/${agentId}/receive?${params2}`,
277
- { method: "GET", headers: commonHeaders }
278
- );
279
- if (!res2.ok) {
280
- const errData = await res2.json().catch(() => ({}));
281
- return { content: [{ type: "text", text: `Error: ${errData.error || res2.statusText}` }] };
282
- }
283
- const data2 = await res2.json();
284
- if (data2.messages?.length > 0) {
285
- return { content: [{ type: "text", text: formatMessages(data2.messages) }] };
286
- }
287
- return {
288
- content: [{ type: "text", text: "No new messages. Take a rest \u2014 new messages will be delivered to you directly. Do not take any further action until notified." }]
289
- };
290
- }
291
- const thisPoll = Math.min(POLL_INTERVAL_MS, remaining);
292
- const params = new URLSearchParams();
293
- params.set("block", "true");
294
- params.set("timeout", String(thisPoll));
295
- const res = await fetch(
296
- `${serverUrl}/internal/agent/${agentId}/receive?${params}`,
297
- { method: "GET", headers: commonHeaders }
298
- );
299
- if (!res.ok) {
300
- const errData = await res.json().catch(() => ({}));
301
- return { content: [{ type: "text", text: `Error: ${errData.error || res.statusText}` }] };
302
- }
303
- const data = await res.json();
304
- if (data.messages?.length > 0) {
305
- return { content: [{ type: "text", text: formatMessages(data.messages) }] };
306
- }
307
- }
308
- } catch (err) {
309
- return {
310
- content: [{ type: "text", text: `Error: ${err.message}` }]
311
- };
312
- }
313
- }
314
- );
315
259
  function formatMessages(messages) {
316
260
  return messages.map((m) => {
317
261
  const target = formatTarget(m);
package/dist/index.js CHANGED
@@ -106,9 +106,9 @@ function buildBaseSystemPrompt(config, opts) {
106
106
  const startupSteps = [
107
107
  `1. **Read MEMORY.md** (in your cwd). This is your memory index \u2014 it tells you what you know and where to find it.`,
108
108
  `2. Follow the instructions in MEMORY.md to read any other memory files you need (e.g. channel summaries, role definitions, user preferences).`,
109
- `3. Call ${t("wait_for_message")}() to start listening.`,
109
+ `3. Stop and wait. New messages will be delivered to you automatically via stdin.`,
110
110
  `4. When you receive a message, process it and reply with ${t("send_message")}.`,
111
- `5. **Only when you have NO more work to do**, call ${t("wait_for_message")}() to wait for new messages. NEVER call wait_for_message while you still have work in progress \u2014 finish all steps first, report your results, then go back to listening.`
111
+ `5. **Complete ALL your work before stopping.** If a task requires multi-step work (research, code changes, testing), finish everything, report results, then stop. New messages arrive automatically \u2014 you do not need to poll or wait for them.`
112
112
  ];
113
113
  let prompt = `You are "${config.displayName || config.name}", an AI agent in Slock \u2014 a collaborative platform for human-AI collaboration.
114
114
 
@@ -120,18 +120,17 @@ You are a **long-running, persistent agent**. You are NOT a one-shot assistant \
120
120
 
121
121
  You have MCP tools from the "chat" server. Use ONLY these for communication:
122
122
 
123
- 1. **${t("wait_for_message")}** \u2014 Block and wait for new messages. ONLY use when you have NO work left to do. This is your idle loop.
124
- 2. **${t("check_messages")}** \u2014 Non-blocking check for new messages. Use freely during work \u2014 at natural breakpoints or after notifications.
125
- 3. **${t("send_message")}** \u2014 Send a message to a channel or DM.
126
- 4. **${t("list_server")}** \u2014 List all channels in this server, which ones you have joined, plus all agents and humans.
127
- 5. **${t("read_history")}** \u2014 Read past messages from a channel or DM.
128
- 6. **${t("list_tasks")}** \u2014 View a channel's task board.
129
- 7. **${t("create_tasks")}** \u2014 Create tasks on a channel's task board (supports batch).
130
- 8. **${t("claim_tasks")}** \u2014 Claim tasks by number (supports batch, handles conflicts).
131
- 9. **${t("unclaim_task")}** \u2014 Release your claim on a task.
132
- 10. **${t("update_task_status")}** \u2014 Change a task's status (e.g. to in_review or done).
133
- 11. **${t("upload_file")}** \u2014 Upload an image file to attach to a message. Returns an attachment ID to pass to send_message.
134
- 12. **${t("view_file")}** \u2014 Download an attached image by its attachment ID so you can view it. Use when messages contain image attachments.
123
+ 1. **${t("check_messages")}** \u2014 Non-blocking check for new messages. Use freely during work \u2014 at natural breakpoints or after notifications.
124
+ 2. **${t("send_message")}** \u2014 Send a message to a channel or DM.
125
+ 3. **${t("list_server")}** \u2014 List all channels in this server, which ones you have joined, plus all agents and humans.
126
+ 4. **${t("read_history")}** \u2014 Read past messages from a channel or DM.
127
+ 5. **${t("list_tasks")}** \u2014 View a channel's task board.
128
+ 6. **${t("create_tasks")}** \u2014 Create tasks on a channel's task board (supports batch).
129
+ 7. **${t("claim_tasks")}** \u2014 Claim tasks by number (supports batch, handles conflicts).
130
+ 8. **${t("unclaim_task")}** \u2014 Release your claim on a task.
131
+ 9. **${t("update_task_status")}** \u2014 Change a task's status (e.g. to in_review or done).
132
+ 10. **${t("upload_file")}** \u2014 Upload an image file to attach to a message. Returns an attachment ID to pass to send_message.
133
+ 11. **${t("view_file")}** \u2014 Download an attached image by its attachment ID so you can view it. Use when messages contain image attachments.
135
134
 
136
135
  CRITICAL RULES:
137
136
  ${criticalRules.join("\n")}
@@ -340,7 +339,7 @@ While you are busy (executing tools, thinking, etc.), new messages may arrive. W
340
339
  How to handle these:
341
340
  - Call \`${t("check_messages")}()\` to check for new messages. You are encouraged to do this frequently \u2014 at natural breakpoints in your work, or whenever you see a notification.
342
341
  - If the new message is higher priority, you may pivot to it. If not, continue your current work.
343
- - **IMPORTANT: \`${t("check_messages")}\` and \`${t("wait_for_message")}\` are very different tools.** \`check_messages\` returns instantly with any pending messages (or "no new messages"). \`wait_for_message\` blocks for up to ~4 minutes \u2014 ONLY use it when you have absolutely NO work left to do.`;
342
+ - \`check_messages\` returns instantly with any pending messages (or "no new messages"). It is always safe to call.`;
344
343
  }
345
344
  if (config.description) {
346
345
  prompt += `
@@ -718,11 +717,10 @@ var CodexDriver = class {
718
717
  return buildBaseSystemPrompt(config, {
719
718
  toolPrefix: "",
720
719
  extraCriticalRules: [
721
- "- Do NOT use shell commands to send or receive messages. The MCP tools handle everything.",
722
- "- ALWAYS call wait_for_message after completing any task \u2014 this keeps you listening for new messages."
720
+ "- Do NOT use shell commands to send or receive messages. The MCP tools handle everything."
723
721
  ],
724
722
  postStartupNotes: [
725
- "**IMPORTANT**: Your process exits after each turn completes. You will be automatically restarted when new messages arrive. Always call wait_for_message as your last action \u2014 if no messages are pending, you'll sleep and be woken when one arrives."
723
+ "**IMPORTANT**: Your process exits after each turn completes. You will be automatically restarted when new messages arrive. Complete all your work, then stop \u2014 new messages will wake you up."
726
724
  ],
727
725
  includeStdinNotificationSection: false
728
726
  });
@@ -872,9 +870,9 @@ Use read_history to catch up, or respond to the message above first.`;
872
870
  }
873
871
  prompt += `
874
872
 
875
- Respond as appropriate \u2014 reply using send_message, or take action as needed. Then call wait_for_message to keep listening.
873
+ Respond as appropriate \u2014 reply using send_message, or take action as needed. Complete ALL your work before stopping.
876
874
 
877
- IMPORTANT: If the message requires multi-step work (e.g. research, code changes, testing), complete ALL steps before calling wait_for_message. Sending a progress update does NOT mean your task is done \u2014 only call wait_for_message when you have NO more work to do.`;
875
+ IMPORTANT: If the message requires multi-step work (e.g. research, code changes, testing), complete ALL steps before stopping. Sending a progress update does NOT mean your task is done \u2014 only stop when you have NO more work to do. New messages will be delivered to you automatically via stdin.`;
878
876
  if (driver.supportsStdinNotification) {
879
877
  prompt += `
880
878
 
@@ -888,14 +886,14 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
888
886
  }
889
887
  prompt += `
890
888
 
891
- Use read_history to catch up on important channels, then call wait_for_message to listen for new messages.`;
889
+ Use read_history to catch up on important channels, then stop. New messages will be delivered to you automatically.`;
892
890
  if (driver.supportsStdinNotification) {
893
891
  prompt += `
894
892
 
895
893
  Note: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call check_messages to check for messages.`;
896
894
  }
897
895
  } else if (isResume) {
898
- prompt = `No new messages while you were away. Call ${driver.mcpToolPrefix}wait_for_message to listen for new messages.`;
896
+ prompt = `No new messages while you were away. Nothing to do \u2014 just stop. New messages will be delivered to you automatically via stdin.`;
899
897
  if (driver.supportsStdinNotification) {
900
898
  prompt += `
901
899
 
@@ -916,10 +914,9 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
916
914
  process: proc,
917
915
  driver,
918
916
  inbox: [],
919
- pendingReceive: null,
920
917
  config,
921
918
  sessionId: config.sessionId || null,
922
- isInReceiveMessage: false,
919
+ isIdle: false,
923
920
  notificationTimer: null,
924
921
  pendingNotificationCount: 0,
925
922
  activityHeartbeat: null,
@@ -952,10 +949,6 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
952
949
  if (this.agents.has(agentId)) {
953
950
  const ap = this.agents.get(agentId);
954
951
  if (ap.process !== proc) return;
955
- if (ap.pendingReceive) {
956
- clearTimeout(ap.pendingReceive.timer);
957
- ap.pendingReceive.resolve([]);
958
- }
959
952
  if (ap.notificationTimer) {
960
953
  clearTimeout(ap.notificationTimer);
961
954
  }
@@ -989,10 +982,6 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
989
982
  this.idleAgentConfigs.delete(agentId);
990
983
  const ap = this.agents.get(agentId);
991
984
  if (!ap) return;
992
- if (ap.pendingReceive) {
993
- clearTimeout(ap.pendingReceive.timer);
994
- ap.pendingReceive.resolve([]);
995
- }
996
985
  if (ap.notificationTimer) {
997
986
  clearTimeout(ap.notificationTimer);
998
987
  }
@@ -1038,19 +1027,14 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1038
1027
  }
1039
1028
  return;
1040
1029
  }
1041
- if (ap.isInReceiveMessage) {
1042
- ap.isInReceiveMessage = false;
1030
+ if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
1031
+ ap.isIdle = false;
1043
1032
  this.broadcastActivity(agentId, "working", "Message received");
1033
+ this.deliverMessageViaStdin(agentId, ap, message);
1034
+ return;
1044
1035
  }
1045
- if (ap.pendingReceive) {
1046
- clearTimeout(ap.pendingReceive.timer);
1047
- ap.pendingReceive.resolve([message]);
1048
- ap.pendingReceive = null;
1049
- } else {
1050
- ap.inbox.push(message);
1051
- }
1036
+ ap.inbox.push(message);
1052
1037
  if (!ap.driver.supportsStdinNotification) return;
1053
- if (ap.isInReceiveMessage) return;
1054
1038
  if (!ap.sessionId) return;
1055
1039
  ap.pendingNotificationCount++;
1056
1040
  if (!ap.notificationTimer) {
@@ -1229,40 +1213,34 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1229
1213
  const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
1230
1214
  const extra = text ? [{ kind: "thinking", text }] : [];
1231
1215
  this.broadcastActivity(agentId, "thinking", "", extra);
1232
- if (ap) ap.isInReceiveMessage = false;
1216
+ if (ap) ap.isIdle = false;
1233
1217
  break;
1234
1218
  }
1235
1219
  case "text": {
1236
1220
  const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
1237
1221
  this.broadcastActivity(agentId, "thinking", "", [{ kind: "text", text }]);
1238
- if (ap) ap.isInReceiveMessage = false;
1222
+ if (ap) ap.isIdle = false;
1239
1223
  break;
1240
1224
  }
1241
1225
  case "tool_call": {
1242
1226
  const toolName = event.name;
1243
1227
  const inputSummary = driver.summarizeToolInput(toolName, event.input);
1244
- if (toolName === `${driver.mcpToolPrefix}wait_for_message`) {
1245
- this.broadcastActivity(agentId, "online", "Idle");
1246
- if (ap) {
1247
- ap.isInReceiveMessage = true;
1248
- ap.pendingNotificationCount = 0;
1249
- if (ap.notificationTimer) {
1250
- clearTimeout(ap.notificationTimer);
1251
- ap.notificationTimer = null;
1252
- }
1253
- }
1254
- } else {
1255
- const detail = toolName === `${driver.mcpToolPrefix}check_messages` ? "Checking messages\u2026" : toolName === `${driver.mcpToolPrefix}send_message` ? "Sending message\u2026" : driver.toolDisplayName(toolName);
1256
- this.broadcastActivity(agentId, "working", detail, [{ kind: "tool_start", toolName, toolInput: inputSummary }]);
1257
- if (ap) ap.isInReceiveMessage = false;
1258
- }
1228
+ const detail = toolName === `${driver.mcpToolPrefix}check_messages` ? "Checking messages\u2026" : toolName === `${driver.mcpToolPrefix}send_message` ? "Sending message\u2026" : driver.toolDisplayName(toolName);
1229
+ this.broadcastActivity(agentId, "working", detail, [{ kind: "tool_start", toolName, toolInput: inputSummary }]);
1230
+ if (ap) ap.isIdle = false;
1259
1231
  break;
1260
1232
  }
1261
1233
  case "turn_end":
1262
- this.broadcastActivity(agentId, "online", "Turn complete");
1263
1234
  if (ap) {
1264
- ap.isInReceiveMessage = false;
1265
1235
  if (event.sessionId) ap.sessionId = event.sessionId;
1236
+ if (ap.inbox.length > 0 && ap.driver.supportsStdinNotification && ap.sessionId) {
1237
+ const nextMessage = ap.inbox.shift();
1238
+ this.broadcastActivity(agentId, "working", "Message received");
1239
+ this.deliverMessageViaStdin(agentId, ap, nextMessage);
1240
+ } else {
1241
+ ap.isIdle = true;
1242
+ this.broadcastActivity(agentId, "online", "Idle");
1243
+ }
1266
1244
  }
1267
1245
  if (event.sessionId) {
1268
1246
  this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId });
@@ -1284,7 +1262,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1284
1262
  ap.pendingNotificationCount = 0;
1285
1263
  ap.notificationTimer = null;
1286
1264
  if (count === 0) return;
1287
- if (ap.isInReceiveMessage) return;
1265
+ if (ap.isIdle) return;
1288
1266
  if (!ap.sessionId) return;
1289
1267
  const notification = `[System notification: You have ${count} new message${count > 1 ? "s" : ""} waiting. Call check_messages to read ${count > 1 ? "them" : "it"} when you're ready.]`;
1290
1268
  console.log(`[Agent ${agentId}] Sending stdin notification: ${count} message(s)`);
@@ -1293,6 +1271,23 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1293
1271
  ap.process.stdin?.write(encoded + "\n");
1294
1272
  }
1295
1273
  }
1274
+ /** Deliver a message to an agent via stdin, formatting it the same way as the MCP bridge */
1275
+ deliverMessageViaStdin(agentId, ap, message) {
1276
+ const channelLabel = message.channel_type === "dm" ? `DM:@${message.channel_name}` : `#${message.channel_name}`;
1277
+ const senderPrefix = message.sender_type === "agent" ? "(agent) " : "";
1278
+ const time = message.timestamp ? ` (${toLocalTime(message.timestamp)})` : "";
1279
+ const formatted = `[${channelLabel}]${time} ${senderPrefix}@${message.sender_name}: ${message.content}`;
1280
+ const prompt = `New message received:
1281
+
1282
+ ${formatted}
1283
+
1284
+ Respond as appropriate. Complete all your work before stopping.`;
1285
+ const encoded = ap.driver.encodeStdinMessage(prompt, ap.sessionId);
1286
+ if (encoded) {
1287
+ console.log(`[Agent ${agentId}] Delivering message via stdin from @${message.sender_name}`);
1288
+ ap.process.stdin?.write(encoded + "\n");
1289
+ }
1290
+ }
1296
1291
  /** List ONE level of a directory — directories returned without children (lazy-loaded on demand) */
1297
1292
  async listDirectoryChildren(dir, rootDir) {
1298
1293
  let entries;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"