@slock-ai/daemon 0.20.0 → 0.22.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
@@ -255,6 +255,8 @@ Keep the user informed. They cannot see your internal reasoning, so:
255
255
 
256
256
  Never output raw HTML tags in your messages. Use plain-text @mentions (e.g. \`@alice\`) and #channel references (e.g. \`#general\`, \`#t1\`). Do NOT wrap them in \`<a>\` tags or any other HTML.
257
257
 
258
+ When you intend to reference a channel or mention someone, write them as plain text \u2014 do NOT wrap them in backticks (inline code). Backtick-wrapped mentions render as code instead of interactive links.
259
+
258
260
  ### Formatting \u2014 URLs in non-English text
259
261
 
260
262
  When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.), always wrap the URL in angle brackets or use markdown link syntax. Otherwise the punctuation may be rendered as part of the URL.
@@ -570,7 +572,7 @@ var CodexDriver = class {
570
572
  "-c",
571
573
  "mcp_servers.chat.startup_timeout_sec=30",
572
574
  "-c",
573
- "mcp_servers.chat.tool_timeout_sec=120",
575
+ "mcp_servers.chat.tool_timeout_sec=300",
574
576
  "-c",
575
577
  "mcp_servers.chat.enabled=true",
576
578
  "-c",
@@ -803,6 +805,8 @@ var AgentProcessManager = class {
803
805
  agents = /* @__PURE__ */ new Map();
804
806
  agentsStarting = /* @__PURE__ */ new Set();
805
807
  // Prevent concurrent starts of same agent
808
+ /** Cached configs for agents whose process exited normally — enables auto-restart on next message */
809
+ idleAgentConfigs = /* @__PURE__ */ new Map();
806
810
  chatBridgePath;
807
811
  sendToServer;
808
812
  daemonApiKey;
@@ -948,9 +952,13 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
948
952
  }
949
953
  this.agents.delete(agentId);
950
954
  if (code === 0) {
951
- this.sendToServer({ type: "agent:status", agentId, status: "sleeping" });
952
- this.sendToServer({ type: "agent:activity", agentId, activity: "sleeping", detail: "" });
955
+ this.idleAgentConfigs.set(agentId, {
956
+ config: { ...ap.config, sessionId: ap.sessionId },
957
+ sessionId: ap.sessionId
958
+ });
959
+ this.sendToServer({ type: "agent:activity", agentId, activity: "online", detail: "" });
953
960
  } else {
961
+ this.idleAgentConfigs.delete(agentId);
954
962
  const reason = code === null ? "killed by signal" : `exit code ${code}`;
955
963
  console.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
956
964
  this.sendToServer({ type: "agent:status", agentId, status: "inactive" });
@@ -965,7 +973,8 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
965
973
  throw err;
966
974
  }
967
975
  }
968
- async stopAgent(agentId) {
976
+ async stopAgent(agentId, { wait = false, silent = false } = {}) {
977
+ this.idleAgentConfigs.delete(agentId);
969
978
  const ap = this.agents.get(agentId);
970
979
  if (!ap) return;
971
980
  if (ap.pendingReceive) {
@@ -977,27 +986,43 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
977
986
  }
978
987
  this.agents.delete(agentId);
979
988
  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([]);
989
+ if (!silent) {
990
+ this.sendToServer({ type: "agent:status", agentId, status: "inactive" });
991
+ this.sendToServer({ type: "agent:activity", agentId, activity: "offline", detail: "" });
991
992
  }
992
- if (ap.notificationTimer) {
993
- clearTimeout(ap.notificationTimer);
993
+ if (wait) {
994
+ await new Promise((resolve) => {
995
+ const forceKillTimer = setTimeout(() => {
996
+ try {
997
+ ap.process.kill("SIGKILL");
998
+ } catch {
999
+ }
1000
+ resolve();
1001
+ }, 5e3);
1002
+ ap.process.on("exit", () => {
1003
+ clearTimeout(forceKillTimer);
1004
+ resolve();
1005
+ });
1006
+ if (ap.process.exitCode !== null || ap.process.signalCode !== null) {
1007
+ clearTimeout(forceKillTimer);
1008
+ resolve();
1009
+ }
1010
+ });
994
1011
  }
995
- this.agents.delete(agentId);
996
- ap.process.kill("SIGTERM");
997
1012
  }
998
1013
  deliverMessage(agentId, message) {
999
1014
  const ap = this.agents.get(agentId);
1000
- if (!ap) return;
1015
+ if (!ap) {
1016
+ const cached = this.idleAgentConfigs.get(agentId);
1017
+ if (cached) {
1018
+ console.log(`[Agent ${agentId}] Auto-restarting idle process for incoming message`);
1019
+ this.idleAgentConfigs.delete(agentId);
1020
+ this.startAgent(agentId, cached.config, message).catch((err) => {
1021
+ console.error(`[Agent ${agentId}] Failed to auto-restart:`, err);
1022
+ });
1023
+ }
1024
+ return;
1025
+ }
1001
1026
  if (ap.pendingReceive) {
1002
1027
  clearTimeout(ap.pendingReceive.timer);
1003
1028
  ap.pendingReceive.resolve([message]);
@@ -1025,8 +1050,9 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1025
1050
  }
1026
1051
  }
1027
1052
  async stopAll() {
1053
+ this.idleAgentConfigs.clear();
1028
1054
  const ids = [...this.agents.keys()];
1029
- await Promise.all(ids.map((id) => this.stopAgent(id)));
1055
+ await Promise.all(ids.map((id) => this.stopAgent(id, { wait: true, silent: true })));
1030
1056
  }
1031
1057
  getRunningAgentIds() {
1032
1058
  return [...this.agents.keys()];
@@ -1157,39 +1183,51 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1157
1183
  case "thinking": {
1158
1184
  const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
1159
1185
  trajectory.push({ kind: "thinking", text });
1160
- activity = "thinking";
1161
- if (ap) ap.isInReceiveMessage = false;
1186
+ if (ap?.isInReceiveMessage) {
1187
+ ap.isInReceiveMessage = false;
1188
+ } else {
1189
+ activity = "thinking";
1190
+ }
1162
1191
  break;
1163
1192
  }
1164
1193
  case "text": {
1165
1194
  const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
1166
1195
  trajectory.push({ kind: "text", text });
1167
- activity = "thinking";
1168
- if (ap) ap.isInReceiveMessage = false;
1196
+ if (ap?.isInReceiveMessage) {
1197
+ ap.isInReceiveMessage = false;
1198
+ } else {
1199
+ activity = "thinking";
1200
+ }
1169
1201
  break;
1170
1202
  }
1171
1203
  case "tool_call": {
1172
1204
  const toolName = event.name;
1173
1205
  const inputSummary = driver.summarizeToolInput(toolName, event.input);
1174
- trajectory.push({ kind: "tool_start", toolName, toolInput: inputSummary });
1175
1206
  if (toolName === `${driver.mcpToolPrefix}receive_message`) {
1176
- const isBlocking = event.input?.block !== false;
1207
+ const isBlocking = event.input?.block === true || event.input?.block === "true";
1177
1208
  if (isBlocking) {
1178
1209
  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;
1210
+ if (ap) {
1211
+ ap.isInReceiveMessage = true;
1212
+ ap.pendingNotificationCount = 0;
1213
+ if (ap.notificationTimer) {
1214
+ clearTimeout(ap.notificationTimer);
1215
+ ap.notificationTimer = null;
1216
+ }
1186
1217
  }
1218
+ } else {
1219
+ trajectory.push({ kind: "tool_start", toolName, toolInput: inputSummary });
1220
+ activity = "working";
1221
+ detail = "Checking messages\u2026";
1222
+ if (ap) ap.isInReceiveMessage = false;
1187
1223
  }
1188
1224
  } else if (toolName === `${driver.mcpToolPrefix}send_message`) {
1225
+ trajectory.push({ kind: "tool_start", toolName, toolInput: inputSummary });
1189
1226
  activity = "working";
1190
1227
  detail = "Sending message\u2026";
1191
1228
  if (ap) ap.isInReceiveMessage = false;
1192
1229
  } else {
1230
+ trajectory.push({ kind: "tool_start", toolName, toolInput: inputSummary });
1193
1231
  activity = "working";
1194
1232
  detail = driver.toolDisplayName(toolName);
1195
1233
  if (ap) ap.isInReceiveMessage = false;
@@ -1212,7 +1250,10 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1212
1250
  }
1213
1251
  if (activity) {
1214
1252
  this.sendToServer({ type: "agent:activity", agentId, activity, detail });
1215
- trajectory.push({ kind: "status", activity, detail });
1253
+ const hasToolStart = trajectory.some((e) => e.kind === "tool_start");
1254
+ if (!hasToolStart) {
1255
+ trajectory.push({ kind: "status", activity, detail });
1256
+ }
1216
1257
  }
1217
1258
  if (trajectory.length > 0) {
1218
1259
  this.sendToServer({ type: "agent:trajectory", agentId, entries: trajectory });
@@ -1377,10 +1418,6 @@ connection = new DaemonConnection({
1377
1418
  console.log(`[Daemon] Stopping agent ${msg.agentId}`);
1378
1419
  agentManager.stopAgent(msg.agentId);
1379
1420
  break;
1380
- case "agent:sleep":
1381
- console.log(`[Daemon] Sleeping agent ${msg.agentId}`);
1382
- agentManager.sleepAgent(msg.agentId);
1383
- break;
1384
1421
  case "agent:reset-workspace":
1385
1422
  console.log(`[Daemon] Resetting workspace for agent ${msg.agentId}`);
1386
1423
  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.22.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"