@slock-ai/daemon 0.20.0 → 0.21.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.
@@ -230,37 +230,57 @@ Use your Read tool to view this image.` }]
230
230
  );
231
231
  server.tool(
232
232
  "receive_message",
233
- "Receive new messages. Use block=true to wait for new messages. Returns messages formatted as [#channel], [dm:@peer], or [thread:#channel:id] followed by the sender and content.",
233
+ "Receive new messages. Blocks and waits for new messages (polls internally). Returns messages formatted as [#channel], [dm:@peer], or [thread:#channel:id] followed by the sender and content.",
234
234
  {
235
- block: z.boolean().default(true).describe("Whether to block (wait) for new messages"),
236
- timeout_ms: z.number().default(59e3).describe("How long to wait in ms when blocking (default 59s, just under MCP tool call timeout)")
235
+ block: z.boolean().default(true).describe("Whether to block (wait) for new messages")
237
236
  },
238
- async ({ block, timeout_ms }) => {
237
+ async ({ block }) => {
239
238
  try {
240
- const params = new URLSearchParams();
241
- if (block) params.set("block", "true");
242
- params.set("timeout", String(timeout_ms));
243
- const res = await fetch(
244
- `${serverUrl}/internal/agent/${agentId}/receive?${params}`,
245
- { method: "GET", headers: commonHeaders }
246
- );
247
- const data = await res.json();
248
- if (!data.messages || data.messages.length === 0) {
249
- return {
250
- content: [{ type: "text", text: "No new messages." }]
251
- };
239
+ const POLL_INTERVAL_MS = 25e3;
240
+ const MAX_WAIT_MS = 27e4;
241
+ const startTime = Date.now();
242
+ while (true) {
243
+ const elapsed = Date.now() - startTime;
244
+ const remaining = MAX_WAIT_MS - elapsed;
245
+ if (!block || remaining <= 0) {
246
+ const params2 = new URLSearchParams();
247
+ if (block) {
248
+ params2.set("block", "true");
249
+ params2.set("timeout", "1000");
250
+ }
251
+ const res2 = await fetch(
252
+ `${serverUrl}/internal/agent/${agentId}/receive?${params2}`,
253
+ { method: "GET", headers: commonHeaders }
254
+ );
255
+ if (!res2.ok) {
256
+ const errData = await res2.json().catch(() => ({}));
257
+ return { content: [{ type: "text", text: `Error: ${errData.error || res2.statusText}` }] };
258
+ }
259
+ const data2 = await res2.json();
260
+ if (data2.messages?.length > 0) {
261
+ return { content: [{ type: "text", text: formatMessages(data2.messages) }] };
262
+ }
263
+ return {
264
+ content: [{ type: "text", text: "No new messages. Take a rest \u2014 new messages will be delivered to you directly. Do not call receive_message or take any further action until notified." }]
265
+ };
266
+ }
267
+ const thisPoll = Math.min(POLL_INTERVAL_MS, remaining);
268
+ const params = new URLSearchParams();
269
+ params.set("block", "true");
270
+ params.set("timeout", String(thisPoll));
271
+ const res = await fetch(
272
+ `${serverUrl}/internal/agent/${agentId}/receive?${params}`,
273
+ { method: "GET", headers: commonHeaders }
274
+ );
275
+ if (!res.ok) {
276
+ const errData = await res.json().catch(() => ({}));
277
+ return { content: [{ type: "text", text: `Error: ${errData.error || res.statusText}` }] };
278
+ }
279
+ const data = await res.json();
280
+ if (data.messages?.length > 0) {
281
+ return { content: [{ type: "text", text: formatMessages(data.messages) }] };
282
+ }
252
283
  }
253
- const formatted = data.messages.map((m) => {
254
- const target = formatTarget(m);
255
- const msgId = m.message_id ? m.message_id.slice(0, 8) : "-";
256
- const time = m.timestamp ? toLocalTime(m.timestamp) : "-";
257
- const senderType = m.sender_type === "agent" ? " type=agent" : "";
258
- const attachSuffix = m.attachments?.length ? ` [${m.attachments.length} image${m.attachments.length > 1 ? "s" : ""}: ${m.attachments.map((a) => `${a.filename} (id:${a.id})`).join(", ")} \u2014 use view_file to see]` : "";
259
- return `[target=${target} msg=${msgId} time=${time}${senderType}] @${m.sender_name}: ${m.content}${attachSuffix}`;
260
- }).join("\n");
261
- return {
262
- content: [{ type: "text", text: formatted }]
263
- };
264
284
  } catch (err) {
265
285
  return {
266
286
  content: [{ type: "text", text: `Error: ${err.message}` }]
@@ -268,6 +288,16 @@ server.tool(
268
288
  }
269
289
  }
270
290
  );
291
+ function formatMessages(messages) {
292
+ return messages.map((m) => {
293
+ const target = formatTarget(m);
294
+ const msgId = m.message_id ? m.message_id.slice(0, 8) : "-";
295
+ const time = m.timestamp ? toLocalTime(m.timestamp) : "-";
296
+ const senderType = m.sender_type === "agent" ? " type=agent" : "";
297
+ const attachSuffix = m.attachments?.length ? ` [${m.attachments.length} image${m.attachments.length > 1 ? "s" : ""}: ${m.attachments.map((a) => `${a.filename} (id:${a.id})`).join(", ")} \u2014 use view_file to see]` : "";
298
+ return `[target=${target} msg=${msgId} time=${time}${senderType}] @${m.sender_name}: ${m.content}${attachSuffix}`;
299
+ }).join("\n");
300
+ }
271
301
  server.tool(
272
302
  "list_server",
273
303
  "List all channels in this server, including which ones you have joined, plus all agents and humans. Use this to discover who and where you can message.",
package/dist/index.js CHANGED
@@ -570,7 +570,7 @@ var CodexDriver = class {
570
570
  "-c",
571
571
  "mcp_servers.chat.startup_timeout_sec=30",
572
572
  "-c",
573
- "mcp_servers.chat.tool_timeout_sec=120",
573
+ "mcp_servers.chat.tool_timeout_sec=300",
574
574
  "-c",
575
575
  "mcp_servers.chat.enabled=true",
576
576
  "-c",
@@ -803,6 +803,8 @@ var AgentProcessManager = class {
803
803
  agents = /* @__PURE__ */ new Map();
804
804
  agentsStarting = /* @__PURE__ */ new Set();
805
805
  // Prevent concurrent starts of same agent
806
+ /** Cached configs for agents whose process exited normally — enables auto-restart on next message */
807
+ idleAgentConfigs = /* @__PURE__ */ new Map();
806
808
  chatBridgePath;
807
809
  sendToServer;
808
810
  daemonApiKey;
@@ -948,9 +950,13 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
948
950
  }
949
951
  this.agents.delete(agentId);
950
952
  if (code === 0) {
951
- this.sendToServer({ type: "agent:status", agentId, status: "sleeping" });
952
- this.sendToServer({ type: "agent:activity", agentId, activity: "sleeping", detail: "" });
953
+ this.idleAgentConfigs.set(agentId, {
954
+ config: { ...ap.config, sessionId: ap.sessionId },
955
+ sessionId: ap.sessionId
956
+ });
957
+ this.sendToServer({ type: "agent:activity", agentId, activity: "online", detail: "" });
953
958
  } else {
959
+ this.idleAgentConfigs.delete(agentId);
954
960
  const reason = code === null ? "killed by signal" : `exit code ${code}`;
955
961
  console.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
956
962
  this.sendToServer({ type: "agent:status", agentId, status: "inactive" });
@@ -965,7 +971,8 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
965
971
  throw err;
966
972
  }
967
973
  }
968
- async stopAgent(agentId) {
974
+ async stopAgent(agentId, { wait = false, silent = false } = {}) {
975
+ this.idleAgentConfigs.delete(agentId);
969
976
  const ap = this.agents.get(agentId);
970
977
  if (!ap) return;
971
978
  if (ap.pendingReceive) {
@@ -977,27 +984,43 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
977
984
  }
978
985
  this.agents.delete(agentId);
979
986
  ap.process.kill("SIGTERM");
980
- this.sendToServer({ type: "agent:status", agentId, status: "inactive" });
981
- this.sendToServer({ type: "agent:activity", agentId, activity: "offline", detail: "" });
982
- }
983
- /** Hibernate: kill process but keep status as "sleeping" (auto-wakes on next message via --resume) */
984
- sleepAgent(agentId) {
985
- const ap = this.agents.get(agentId);
986
- if (!ap) return;
987
- console.log(`[Agent ${agentId}] Hibernating (sleeping)`);
988
- if (ap.pendingReceive) {
989
- clearTimeout(ap.pendingReceive.timer);
990
- ap.pendingReceive.resolve([]);
987
+ if (!silent) {
988
+ this.sendToServer({ type: "agent:status", agentId, status: "inactive" });
989
+ this.sendToServer({ type: "agent:activity", agentId, activity: "offline", detail: "" });
991
990
  }
992
- if (ap.notificationTimer) {
993
- clearTimeout(ap.notificationTimer);
991
+ if (wait) {
992
+ await new Promise((resolve) => {
993
+ const forceKillTimer = setTimeout(() => {
994
+ try {
995
+ ap.process.kill("SIGKILL");
996
+ } catch {
997
+ }
998
+ resolve();
999
+ }, 5e3);
1000
+ ap.process.on("exit", () => {
1001
+ clearTimeout(forceKillTimer);
1002
+ resolve();
1003
+ });
1004
+ if (ap.process.exitCode !== null || ap.process.signalCode !== null) {
1005
+ clearTimeout(forceKillTimer);
1006
+ resolve();
1007
+ }
1008
+ });
994
1009
  }
995
- this.agents.delete(agentId);
996
- ap.process.kill("SIGTERM");
997
1010
  }
998
1011
  deliverMessage(agentId, message) {
999
1012
  const ap = this.agents.get(agentId);
1000
- if (!ap) return;
1013
+ if (!ap) {
1014
+ const cached = this.idleAgentConfigs.get(agentId);
1015
+ if (cached) {
1016
+ console.log(`[Agent ${agentId}] Auto-restarting idle process for incoming message`);
1017
+ this.idleAgentConfigs.delete(agentId);
1018
+ this.startAgent(agentId, cached.config, message).catch((err) => {
1019
+ console.error(`[Agent ${agentId}] Failed to auto-restart:`, err);
1020
+ });
1021
+ }
1022
+ return;
1023
+ }
1001
1024
  if (ap.pendingReceive) {
1002
1025
  clearTimeout(ap.pendingReceive.timer);
1003
1026
  ap.pendingReceive.resolve([message]);
@@ -1025,8 +1048,9 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1025
1048
  }
1026
1049
  }
1027
1050
  async stopAll() {
1051
+ this.idleAgentConfigs.clear();
1028
1052
  const ids = [...this.agents.keys()];
1029
- await Promise.all(ids.map((id) => this.stopAgent(id)));
1053
+ await Promise.all(ids.map((id) => this.stopAgent(id, { wait: true, silent: true })));
1030
1054
  }
1031
1055
  getRunningAgentIds() {
1032
1056
  return [...this.agents.keys()];
@@ -1157,39 +1181,51 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1157
1181
  case "thinking": {
1158
1182
  const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
1159
1183
  trajectory.push({ kind: "thinking", text });
1160
- activity = "thinking";
1161
- if (ap) ap.isInReceiveMessage = false;
1184
+ if (ap?.isInReceiveMessage) {
1185
+ ap.isInReceiveMessage = false;
1186
+ } else {
1187
+ activity = "thinking";
1188
+ }
1162
1189
  break;
1163
1190
  }
1164
1191
  case "text": {
1165
1192
  const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
1166
1193
  trajectory.push({ kind: "text", text });
1167
- activity = "thinking";
1168
- if (ap) ap.isInReceiveMessage = false;
1194
+ if (ap?.isInReceiveMessage) {
1195
+ ap.isInReceiveMessage = false;
1196
+ } else {
1197
+ activity = "thinking";
1198
+ }
1169
1199
  break;
1170
1200
  }
1171
1201
  case "tool_call": {
1172
1202
  const toolName = event.name;
1173
1203
  const inputSummary = driver.summarizeToolInput(toolName, event.input);
1174
- trajectory.push({ kind: "tool_start", toolName, toolInput: inputSummary });
1175
1204
  if (toolName === `${driver.mcpToolPrefix}receive_message`) {
1176
- const isBlocking = event.input?.block !== false;
1205
+ const isBlocking = event.input?.block === true || event.input?.block === "true";
1177
1206
  if (isBlocking) {
1178
1207
  activity = "online";
1179
- }
1180
- if (ap) {
1181
- ap.isInReceiveMessage = true;
1182
- ap.pendingNotificationCount = 0;
1183
- if (ap.notificationTimer) {
1184
- clearTimeout(ap.notificationTimer);
1185
- ap.notificationTimer = null;
1208
+ if (ap) {
1209
+ ap.isInReceiveMessage = true;
1210
+ ap.pendingNotificationCount = 0;
1211
+ if (ap.notificationTimer) {
1212
+ clearTimeout(ap.notificationTimer);
1213
+ ap.notificationTimer = null;
1214
+ }
1186
1215
  }
1216
+ } else {
1217
+ trajectory.push({ kind: "tool_start", toolName, toolInput: inputSummary });
1218
+ activity = "working";
1219
+ detail = "Checking messages\u2026";
1220
+ if (ap) ap.isInReceiveMessage = false;
1187
1221
  }
1188
1222
  } else if (toolName === `${driver.mcpToolPrefix}send_message`) {
1223
+ trajectory.push({ kind: "tool_start", toolName, toolInput: inputSummary });
1189
1224
  activity = "working";
1190
1225
  detail = "Sending message\u2026";
1191
1226
  if (ap) ap.isInReceiveMessage = false;
1192
1227
  } else {
1228
+ trajectory.push({ kind: "tool_start", toolName, toolInput: inputSummary });
1193
1229
  activity = "working";
1194
1230
  detail = driver.toolDisplayName(toolName);
1195
1231
  if (ap) ap.isInReceiveMessage = false;
@@ -1212,7 +1248,10 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1212
1248
  }
1213
1249
  if (activity) {
1214
1250
  this.sendToServer({ type: "agent:activity", agentId, activity, detail });
1215
- trajectory.push({ kind: "status", activity, detail });
1251
+ const hasToolStart = trajectory.some((e) => e.kind === "tool_start");
1252
+ if (!hasToolStart) {
1253
+ trajectory.push({ kind: "status", activity, detail });
1254
+ }
1216
1255
  }
1217
1256
  if (trajectory.length > 0) {
1218
1257
  this.sendToServer({ type: "agent:trajectory", agentId, entries: trajectory });
@@ -1377,10 +1416,6 @@ connection = new DaemonConnection({
1377
1416
  console.log(`[Daemon] Stopping agent ${msg.agentId}`);
1378
1417
  agentManager.stopAgent(msg.agentId);
1379
1418
  break;
1380
- case "agent:sleep":
1381
- console.log(`[Daemon] Sleeping agent ${msg.agentId}`);
1382
- agentManager.sleepAgent(msg.agentId);
1383
- break;
1384
1419
  case "agent:reset-workspace":
1385
1420
  console.log(`[Daemon] Resetting workspace for agent ${msg.agentId}`);
1386
1421
  agentManager.resetWorkspace(msg.agentId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"