@slock-ai/daemon 0.19.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
@@ -372,8 +372,14 @@ var ClaudeDriver = class {
372
372
  }
373
373
  }
374
374
  });
375
- const mcpConfigPath = path.join(ctx.workingDirectory, ".slock-claude-mcp.json");
376
- writeFileSync(mcpConfigPath, mcpConfig, "utf8");
375
+ let mcpConfigArg;
376
+ if (process.platform === "win32") {
377
+ const mcpConfigPath = path.join(ctx.workingDirectory, ".slock-claude-mcp.json");
378
+ writeFileSync(mcpConfigPath, mcpConfig, "utf8");
379
+ mcpConfigArg = mcpConfigPath;
380
+ } else {
381
+ mcpConfigArg = mcpConfig;
382
+ }
377
383
  const args2 = [
378
384
  "--allow-dangerously-skip-permissions",
379
385
  "--dangerously-skip-permissions",
@@ -383,7 +389,7 @@ var ClaudeDriver = class {
383
389
  "--input-format",
384
390
  "stream-json",
385
391
  "--mcp-config",
386
- mcpConfigPath,
392
+ mcpConfigArg,
387
393
  "--model",
388
394
  ctx.config.model || "sonnet",
389
395
  "--disallowed-tools",
@@ -564,7 +570,7 @@ var CodexDriver = class {
564
570
  "-c",
565
571
  "mcp_servers.chat.startup_timeout_sec=30",
566
572
  "-c",
567
- "mcp_servers.chat.tool_timeout_sec=120",
573
+ "mcp_servers.chat.tool_timeout_sec=300",
568
574
  "-c",
569
575
  "mcp_servers.chat.enabled=true",
570
576
  "-c",
@@ -578,11 +584,36 @@ var CodexDriver = class {
578
584
  }
579
585
  args2.push(ctx.prompt);
580
586
  const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1", ...ctx.config.envVars || {} };
581
- const proc = spawn2("codex", args2, {
587
+ let spawnCmd = "codex";
588
+ let spawnArgs = args2;
589
+ if (process.platform === "win32") {
590
+ let codexEntry = null;
591
+ try {
592
+ const globalRoot = execSync("npm root -g", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
593
+ const candidate = path2.join(globalRoot, "@openai", "codex", "bin", "codex.js");
594
+ if (existsSync(candidate)) codexEntry = candidate;
595
+ } catch {
596
+ }
597
+ if (!codexEntry) {
598
+ try {
599
+ const cmdPath = execSync("where codex", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim().split(/\r?\n/)[0];
600
+ const candidate = path2.join(path2.dirname(cmdPath), "node_modules", "@openai", "codex", "bin", "codex.js");
601
+ if (existsSync(candidate)) codexEntry = candidate;
602
+ } catch {
603
+ }
604
+ }
605
+ if (!codexEntry) {
606
+ throw new Error(
607
+ "Cannot resolve Codex CLI entry point on Windows. Ensure @openai/codex is installed globally via npm (npm i -g @openai/codex)."
608
+ );
609
+ }
610
+ spawnCmd = process.execPath;
611
+ spawnArgs = [codexEntry, ...args2];
612
+ }
613
+ const proc = spawn2(spawnCmd, spawnArgs, {
582
614
  cwd: ctx.workingDirectory,
583
615
  stdio: ["pipe", "pipe", "pipe"],
584
- env: spawnEnv,
585
- shell: process.platform === "win32"
616
+ env: spawnEnv
586
617
  });
587
618
  return { process: proc };
588
619
  }
@@ -772,6 +803,8 @@ var AgentProcessManager = class {
772
803
  agents = /* @__PURE__ */ new Map();
773
804
  agentsStarting = /* @__PURE__ */ new Set();
774
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();
775
808
  chatBridgePath;
776
809
  sendToServer;
777
810
  daemonApiKey;
@@ -917,9 +950,13 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
917
950
  }
918
951
  this.agents.delete(agentId);
919
952
  if (code === 0) {
920
- this.sendToServer({ type: "agent:status", agentId, status: "sleeping" });
921
- 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: "" });
922
958
  } else {
959
+ this.idleAgentConfigs.delete(agentId);
923
960
  const reason = code === null ? "killed by signal" : `exit code ${code}`;
924
961
  console.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
925
962
  this.sendToServer({ type: "agent:status", agentId, status: "inactive" });
@@ -934,7 +971,8 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
934
971
  throw err;
935
972
  }
936
973
  }
937
- async stopAgent(agentId) {
974
+ async stopAgent(agentId, { wait = false, silent = false } = {}) {
975
+ this.idleAgentConfigs.delete(agentId);
938
976
  const ap = this.agents.get(agentId);
939
977
  if (!ap) return;
940
978
  if (ap.pendingReceive) {
@@ -946,27 +984,43 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
946
984
  }
947
985
  this.agents.delete(agentId);
948
986
  ap.process.kill("SIGTERM");
949
- this.sendToServer({ type: "agent:status", agentId, status: "inactive" });
950
- this.sendToServer({ type: "agent:activity", agentId, activity: "offline", detail: "" });
951
- }
952
- /** Hibernate: kill process but keep status as "sleeping" (auto-wakes on next message via --resume) */
953
- sleepAgent(agentId) {
954
- const ap = this.agents.get(agentId);
955
- if (!ap) return;
956
- console.log(`[Agent ${agentId}] Hibernating (sleeping)`);
957
- if (ap.pendingReceive) {
958
- clearTimeout(ap.pendingReceive.timer);
959
- 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: "" });
960
990
  }
961
- if (ap.notificationTimer) {
962
- 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
+ });
963
1009
  }
964
- this.agents.delete(agentId);
965
- ap.process.kill("SIGTERM");
966
1010
  }
967
1011
  deliverMessage(agentId, message) {
968
1012
  const ap = this.agents.get(agentId);
969
- 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
+ }
970
1024
  if (ap.pendingReceive) {
971
1025
  clearTimeout(ap.pendingReceive.timer);
972
1026
  ap.pendingReceive.resolve([message]);
@@ -994,8 +1048,9 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
994
1048
  }
995
1049
  }
996
1050
  async stopAll() {
1051
+ this.idleAgentConfigs.clear();
997
1052
  const ids = [...this.agents.keys()];
998
- await Promise.all(ids.map((id) => this.stopAgent(id)));
1053
+ await Promise.all(ids.map((id) => this.stopAgent(id, { wait: true, silent: true })));
999
1054
  }
1000
1055
  getRunningAgentIds() {
1001
1056
  return [...this.agents.keys()];
@@ -1126,39 +1181,51 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1126
1181
  case "thinking": {
1127
1182
  const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
1128
1183
  trajectory.push({ kind: "thinking", text });
1129
- activity = "thinking";
1130
- if (ap) ap.isInReceiveMessage = false;
1184
+ if (ap?.isInReceiveMessage) {
1185
+ ap.isInReceiveMessage = false;
1186
+ } else {
1187
+ activity = "thinking";
1188
+ }
1131
1189
  break;
1132
1190
  }
1133
1191
  case "text": {
1134
1192
  const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
1135
1193
  trajectory.push({ kind: "text", text });
1136
- activity = "thinking";
1137
- if (ap) ap.isInReceiveMessage = false;
1194
+ if (ap?.isInReceiveMessage) {
1195
+ ap.isInReceiveMessage = false;
1196
+ } else {
1197
+ activity = "thinking";
1198
+ }
1138
1199
  break;
1139
1200
  }
1140
1201
  case "tool_call": {
1141
1202
  const toolName = event.name;
1142
1203
  const inputSummary = driver.summarizeToolInput(toolName, event.input);
1143
- trajectory.push({ kind: "tool_start", toolName, toolInput: inputSummary });
1144
1204
  if (toolName === `${driver.mcpToolPrefix}receive_message`) {
1145
- const isBlocking = event.input?.block !== false;
1205
+ const isBlocking = event.input?.block === true || event.input?.block === "true";
1146
1206
  if (isBlocking) {
1147
1207
  activity = "online";
1148
- }
1149
- if (ap) {
1150
- ap.isInReceiveMessage = true;
1151
- ap.pendingNotificationCount = 0;
1152
- if (ap.notificationTimer) {
1153
- clearTimeout(ap.notificationTimer);
1154
- 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
+ }
1155
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;
1156
1221
  }
1157
1222
  } else if (toolName === `${driver.mcpToolPrefix}send_message`) {
1223
+ trajectory.push({ kind: "tool_start", toolName, toolInput: inputSummary });
1158
1224
  activity = "working";
1159
1225
  detail = "Sending message\u2026";
1160
1226
  if (ap) ap.isInReceiveMessage = false;
1161
1227
  } else {
1228
+ trajectory.push({ kind: "tool_start", toolName, toolInput: inputSummary });
1162
1229
  activity = "working";
1163
1230
  detail = driver.toolDisplayName(toolName);
1164
1231
  if (ap) ap.isInReceiveMessage = false;
@@ -1181,7 +1248,10 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1181
1248
  }
1182
1249
  if (activity) {
1183
1250
  this.sendToServer({ type: "agent:activity", agentId, activity, detail });
1184
- 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
+ }
1185
1255
  }
1186
1256
  if (trajectory.length > 0) {
1187
1257
  this.sendToServer({ type: "agent:trajectory", agentId, entries: trajectory });
@@ -1346,10 +1416,6 @@ connection = new DaemonConnection({
1346
1416
  console.log(`[Daemon] Stopping agent ${msg.agentId}`);
1347
1417
  agentManager.stopAgent(msg.agentId);
1348
1418
  break;
1349
- case "agent:sleep":
1350
- console.log(`[Daemon] Sleeping agent ${msg.agentId}`);
1351
- agentManager.sleepAgent(msg.agentId);
1352
- break;
1353
1419
  case "agent:reset-workspace":
1354
1420
  console.log(`[Daemon] Resetting workspace for agent ${msg.agentId}`);
1355
1421
  agentManager.resetWorkspace(msg.agentId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"