@slock-ai/daemon 0.29.1-alpha.0 → 0.30.1-alpha.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/dist/index.js CHANGED
@@ -1,16 +1,42 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import path4 from "path";
4
+ import path5 from "path";
5
5
  import os2 from "os";
6
6
  import { createRequire } from "module";
7
7
  import { execSync as execSync2 } from "child_process";
8
8
  import { accessSync } from "fs";
9
- import { readFile as readFile2, readdir as readdir2, stat as stat2, mkdir as mkdir2, appendFile } from "fs/promises";
10
9
  import { fileURLToPath } from "url";
11
10
 
12
11
  // src/connection.ts
13
12
  import WebSocket from "ws";
13
+
14
+ // src/logger.ts
15
+ function timestamp() {
16
+ const d = /* @__PURE__ */ new Date();
17
+ const pad = (n) => String(n).padStart(2, "0");
18
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
19
+ }
20
+ function format(level, msg) {
21
+ return `${timestamp()} [${level}] ${msg}`;
22
+ }
23
+ var logger = {
24
+ info(msg) {
25
+ console.log(format("INFO", msg));
26
+ },
27
+ warn(msg) {
28
+ console.warn(format("WARN", msg));
29
+ },
30
+ error(msg, err) {
31
+ if (err) {
32
+ console.error(format("ERROR", msg), err);
33
+ } else {
34
+ console.error(format("ERROR", msg));
35
+ }
36
+ }
37
+ };
38
+
39
+ // src/connection.ts
14
40
  var systemClock = {
15
41
  now: () => Date.now(),
16
42
  setTimeout: (fn, ms) => setTimeout(fn, ms),
@@ -47,7 +73,7 @@ var DaemonConnection = class {
47
73
  this.reconnectTimer = null;
48
74
  }
49
75
  if (this.ws) {
50
- console.log("[Daemon] Disconnect requested");
76
+ logger.info("[Daemon] Disconnect requested");
51
77
  this.ws.close();
52
78
  this.ws = null;
53
79
  }
@@ -60,7 +86,7 @@ var DaemonConnection = class {
60
86
  const now = this.clock.now();
61
87
  if (now - this.lastDroppedSendLogAt > 5e3) {
62
88
  this.lastDroppedSendLogAt = now;
63
- console.warn(`[Daemon] Dropping outbound message while disconnected: ${msg.type}`);
89
+ logger.warn(`[Daemon] Dropping outbound message while disconnected: ${msg.type}`);
64
90
  }
65
91
  }
66
92
  get connected() {
@@ -70,13 +96,13 @@ var DaemonConnection = class {
70
96
  if (!this.shouldConnect) return;
71
97
  if (this.ws && this.ws.readyState !== WebSocket.CLOSED) return;
72
98
  const wsUrl = this.options.serverUrl.replace(/^http/, "ws") + `/daemon/connect?key=${this.options.apiKey}`;
73
- console.log(`[Daemon] Connecting to ${this.options.serverUrl}...`);
99
+ logger.info(`[Daemon] Connecting to ${this.options.serverUrl}...`);
74
100
  const ws = this.options.wsFactory ? this.options.wsFactory(wsUrl) : new WebSocket(wsUrl);
75
101
  this.ws = ws;
76
102
  ws.on("open", () => {
77
103
  if (this.ws !== ws) return;
78
104
  if (!this.shouldConnect) return;
79
- console.log("[Daemon] Connected to server");
105
+ logger.info("[Daemon] Connected to server");
80
106
  this.reconnectAttempt = 0;
81
107
  this.reconnectDelay = this.options.minReconnectDelayMs ?? 1e3;
82
108
  this.resetWatchdog();
@@ -89,7 +115,7 @@ var DaemonConnection = class {
89
115
  const msg = JSON.parse(data.toString());
90
116
  this.options.onMessage(msg);
91
117
  } catch (err) {
92
- console.error("[Daemon] Invalid message from server:", err);
118
+ logger.error("[Daemon] Invalid message from server", err);
93
119
  }
94
120
  });
95
121
  ws.on("close", (code, reasonBuffer) => {
@@ -97,7 +123,7 @@ var DaemonConnection = class {
97
123
  this.ws = null;
98
124
  this.clearWatchdog();
99
125
  const reason = reasonBuffer.toString("utf8");
100
- console.log(
126
+ logger.warn(
101
127
  `[Daemon] Disconnected from server (code=${code}, reason=${JSON.stringify(reason)}, reconnecting=${this.shouldConnect})`
102
128
  );
103
129
  this.options.onDisconnect();
@@ -105,14 +131,14 @@ var DaemonConnection = class {
105
131
  });
106
132
  ws.on("error", (err) => {
107
133
  if (this.ws !== ws) return;
108
- console.error("[Daemon] WebSocket error:", err.message);
134
+ logger.error(`[Daemon] WebSocket error: ${err.message}`);
109
135
  });
110
136
  }
111
137
  scheduleReconnect() {
112
138
  if (!this.shouldConnect) return;
113
139
  if (this.reconnectTimer) return;
114
140
  this.reconnectAttempt += 1;
115
- console.log(`[Daemon] Reconnecting to server in ${this.reconnectDelay}ms (attempt ${this.reconnectAttempt})`);
141
+ logger.info(`[Daemon] Reconnecting to server in ${this.reconnectDelay}ms (attempt ${this.reconnectAttempt})`);
116
142
  this.reconnectTimer = this.clock.setTimeout(() => {
117
143
  this.reconnectTimer = null;
118
144
  this.doConnect();
@@ -123,7 +149,7 @@ var DaemonConnection = class {
123
149
  this.clearWatchdog();
124
150
  const ms = this.options.inboundWatchdogMs ?? INBOUND_WATCHDOG_MS;
125
151
  this.watchdogTimer = this.clock.setTimeout(() => {
126
- console.warn(`[Daemon] No inbound traffic for ${ms / 1e3}s \u2014 forcing reconnect`);
152
+ logger.warn(`[Daemon] No inbound traffic for ${ms / 1e3}s \u2014 forcing reconnect`);
127
153
  try {
128
154
  this.ws?.terminate();
129
155
  } catch {
@@ -140,7 +166,7 @@ var DaemonConnection = class {
140
166
 
141
167
  // src/agentProcessManager.ts
142
168
  import { mkdir, writeFile, access, readdir, stat, readFile, rm } from "fs/promises";
143
- import path3 from "path";
169
+ import path4 from "path";
144
170
  import os from "os";
145
171
 
146
172
  // src/drivers/claude.ts
@@ -183,7 +209,7 @@ You have MCP tools from the "chat" server. Use ONLY these for communication:
183
209
  3. **${t("list_server")}** \u2014 List all channels in this server, which ones you have joined, plus all agents and humans.
184
210
  4. **${t("read_history")}** \u2014 Read past messages from a channel or DM.
185
211
  5. **${t("list_tasks")}** \u2014 View a channel's task board.
186
- 6. **${t("create_tasks")}** \u2014 Create tasks on a channel's task board (supports batch).
212
+ 6. **${t("create_tasks")}** \u2014 Create new task-messages in a channel (supports batch; equivalent to sending a new message and publishing it as a task-message, not claiming it for yourself).
187
213
  7. **${t("claim_tasks")}** \u2014 Claim tasks by number (supports batch, handles conflicts).
188
214
  8. **${t("unclaim_task")}** \u2014 Release your claim on a task.
189
215
  9. **${t("update_task_status")}** \u2014 Change a task's status (e.g. to in_review or done).
@@ -267,6 +293,8 @@ When someone sends a message that asks you to do something \u2014 fix a bug, wri
267
293
  - A regular message (no task suffix): \`@Alice: Can someone look into the login bug?\`
268
294
  - A system notification about task changes: \`\u{1F4CB} Alice converted a message to task #3 "Fix the login bug"\`
269
295
 
296
+ Only top-level channel / DM messages can become tasks. Messages inside threads are discussion context: reply there, but do not claim or convert them as tasks.
297
+
270
298
  \`read_history\` shows messages in their current state. If a message was later converted to a task, it will show the \`[task #N ...]\` suffix.
271
299
 
272
300
  **Status flow:** \`todo\` \u2192 \`in_progress\` \u2192 \`in_review\` \u2192 \`done\`
@@ -280,6 +308,21 @@ When someone sends a message that asks you to do something \u2014 fix a bug, wri
280
308
  4. When done, set status to \`in_review\` so a human can validate
281
309
  5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
282
310
 
311
+ **What \`${t("create_tasks")}\` really means:**
312
+ - Tasks live in the same chat flow as messages. A task is just a message with task metadata, not a separate source of truth.
313
+ - \`${t("create_tasks")}\` is a convenience helper for a specific sequence: create a brand-new message, then publish that new message as a task-message.
314
+ - \`${t("create_tasks")}\` does NOT claim the new task for you. If you want to own it, still call \`${t("claim_tasks")}\` afterward.
315
+ - Typical uses for \`${t("create_tasks")}\` are breaking down a larger task into parallel subtasks, or batch-creating genuinely new work for others to claim.
316
+ - If someone already sent the small work item as a message, you usually do NOT need \`${t("create_tasks")}\` \u2014 just claim that existing message/task.
317
+ - If the work already exists as a message, do NOT use \`${t("create_tasks")}\` for it. Reuse the existing message via \`${t("claim_tasks")}\` with \`message_ids\`.
318
+
319
+ **Creating new tasks:**
320
+ - The task system exists to prevent duplicate work. If you see an existing task for the work, either claim that task or leave it alone.
321
+ - If a message already shows a \`[task #N ...]\` suffix, do NOT create a second message/task for the same work. Claim \`#N\` if it is yours to take; otherwise move on.
322
+ - Before calling \`${t("create_tasks")}\`, first check whether the work already exists as a message-backed task on the task board or is already being handled by someone else.
323
+ - Do NOT create a second task that merely restates an existing request, message, or task. Reuse the existing task/thread instead.
324
+ - Use \`${t("create_tasks")}\` only for genuinely new subtasks or follow-up work that does not already have a canonical task.
325
+
283
326
  ### Splitting tasks for parallel execution
284
327
 
285
328
  When you need to break down a large task into subtasks, structure them so agents can work **in parallel**:
@@ -553,7 +596,7 @@ var ClaudeDriver = class {
553
596
  }
554
597
  return events;
555
598
  }
556
- encodeStdinMessage(text, sessionId) {
599
+ encodeStdinMessage(text, sessionId, _opts) {
557
600
  return JSON.stringify({
558
601
  type: "user",
559
602
  message: {
@@ -808,7 +851,7 @@ var CodexDriver = class {
808
851
  }
809
852
  return events;
810
853
  }
811
- encodeStdinMessage(_text, _sessionId) {
854
+ encodeStdinMessage(_text, _sessionId, _opts) {
812
855
  return null;
813
856
  }
814
857
  buildSystemPrompt(config, _agentId) {
@@ -878,21 +921,247 @@ var CodexDriver = class {
878
921
  }
879
922
  };
880
923
 
924
+ // src/drivers/kimi.ts
925
+ import { randomUUID } from "crypto";
926
+ import { spawn as spawn3 } from "child_process";
927
+ import { existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
928
+ import path3 from "path";
929
+ var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
930
+ var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
931
+ var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
932
+ var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
933
+ function parseToolArguments(raw) {
934
+ if (typeof raw !== "string") return raw;
935
+ try {
936
+ return JSON.parse(raw);
937
+ } catch {
938
+ return raw;
939
+ }
940
+ }
941
+ var KimiDriver = class {
942
+ id = "kimi";
943
+ supportsStdinNotification = true;
944
+ mcpToolPrefix = "";
945
+ deliverMessageDirectlyWhileBusy = true;
946
+ sessionId = null;
947
+ sessionAnnounced = false;
948
+ promptRequestId = null;
949
+ spawn(ctx) {
950
+ const isResume = !!ctx.config.sessionId;
951
+ this.sessionId = ctx.config.sessionId || randomUUID();
952
+ this.sessionAnnounced = false;
953
+ this.promptRequestId = randomUUID();
954
+ const isTsSource = ctx.chatBridgePath.endsWith(".ts");
955
+ const command = isTsSource ? "npx" : "node";
956
+ const bridgeArgs = isTsSource ? ["tsx", ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey] : [ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey];
957
+ const systemPromptPath = path3.join(ctx.workingDirectory, KIMI_SYSTEM_PROMPT_FILE);
958
+ const agentFilePath = path3.join(ctx.workingDirectory, KIMI_AGENT_FILE);
959
+ const mcpConfigPath = path3.join(ctx.workingDirectory, KIMI_MCP_FILE);
960
+ if (!isResume || !existsSync2(systemPromptPath)) {
961
+ writeFileSync2(systemPromptPath, ctx.prompt, "utf8");
962
+ }
963
+ writeFileSync2(agentFilePath, [
964
+ "version: 1",
965
+ "agent:",
966
+ " extend: default",
967
+ ` system_prompt_path: ./${KIMI_SYSTEM_PROMPT_FILE}`,
968
+ ""
969
+ ].join("\n"), "utf8");
970
+ writeFileSync2(mcpConfigPath, JSON.stringify({
971
+ mcpServers: {
972
+ chat: {
973
+ command,
974
+ args: bridgeArgs
975
+ }
976
+ }
977
+ }), "utf8");
978
+ const args2 = [
979
+ "--wire",
980
+ "--yolo",
981
+ "--agent-file",
982
+ agentFilePath,
983
+ "--mcp-config-file",
984
+ mcpConfigPath,
985
+ "--session",
986
+ this.sessionId
987
+ ];
988
+ if (ctx.config.model && ctx.config.model !== "default") {
989
+ args2.push("--model", ctx.config.model);
990
+ }
991
+ const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" };
992
+ const proc = spawn3("kimi", args2, {
993
+ cwd: ctx.workingDirectory,
994
+ stdio: ["pipe", "pipe", "pipe"],
995
+ env: spawnEnv,
996
+ shell: process.platform === "win32"
997
+ });
998
+ proc.stdin?.write(JSON.stringify({
999
+ jsonrpc: "2.0",
1000
+ id: randomUUID(),
1001
+ method: "initialize",
1002
+ params: {
1003
+ protocol_version: KIMI_WIRE_PROTOCOL_VERSION,
1004
+ client: { name: "slock-daemon", version: "1.0.0" },
1005
+ capabilities: {
1006
+ supports_question: false,
1007
+ supports_plan_mode: false
1008
+ }
1009
+ }
1010
+ }) + "\n");
1011
+ proc.stdin?.write(JSON.stringify({
1012
+ jsonrpc: "2.0",
1013
+ id: this.promptRequestId,
1014
+ method: "prompt",
1015
+ params: {
1016
+ user_input: isResume ? ctx.prompt : "Your system prompt contains your standing instructions. Follow it now and begin listening for messages."
1017
+ }
1018
+ }) + "\n");
1019
+ return { process: proc };
1020
+ }
1021
+ parseLine(line) {
1022
+ let message;
1023
+ try {
1024
+ message = JSON.parse(line);
1025
+ } catch {
1026
+ return [];
1027
+ }
1028
+ const events = [];
1029
+ if (!this.sessionAnnounced && this.sessionId) {
1030
+ events.push({ kind: "session_init", sessionId: this.sessionId });
1031
+ this.sessionAnnounced = true;
1032
+ }
1033
+ if ("method" in message && message.method === "event") {
1034
+ const eventType = message.params?.type;
1035
+ const payload = message.params?.payload || {};
1036
+ switch (eventType) {
1037
+ case "StepBegin":
1038
+ events.push({ kind: "thinking", text: "" });
1039
+ break;
1040
+ case "ContentPart":
1041
+ if (payload.type === "think" && payload.think) {
1042
+ events.push({ kind: "thinking", text: payload.think });
1043
+ } else if (payload.type === "text" && payload.text) {
1044
+ events.push({ kind: "text", text: payload.text });
1045
+ }
1046
+ break;
1047
+ case "ToolCall":
1048
+ events.push({
1049
+ kind: "tool_call",
1050
+ name: payload.function?.name || "unknown_tool",
1051
+ input: parseToolArguments(payload.function?.arguments)
1052
+ });
1053
+ break;
1054
+ case "TurnEnd":
1055
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
1056
+ break;
1057
+ case "StepInterrupted":
1058
+ events.push({ kind: "error", message: "Turn interrupted" });
1059
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
1060
+ break;
1061
+ }
1062
+ return events;
1063
+ }
1064
+ if ("error" in message) {
1065
+ events.push({ kind: "error", message: message.error?.message || "Unknown Kimi error" });
1066
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
1067
+ }
1068
+ return events;
1069
+ }
1070
+ encodeStdinMessage(_text, _sessionId, opts) {
1071
+ const mode = opts?.mode || "busy";
1072
+ if (mode === "idle") {
1073
+ return JSON.stringify({
1074
+ jsonrpc: "2.0",
1075
+ id: randomUUID(),
1076
+ method: "prompt",
1077
+ params: {
1078
+ user_input: _text
1079
+ }
1080
+ });
1081
+ }
1082
+ return JSON.stringify({
1083
+ jsonrpc: "2.0",
1084
+ id: randomUUID(),
1085
+ method: "steer",
1086
+ params: {
1087
+ user_input: _text
1088
+ }
1089
+ });
1090
+ }
1091
+ buildSystemPrompt(config, _agentId) {
1092
+ return buildBaseSystemPrompt(config, {
1093
+ toolPrefix: "",
1094
+ extraCriticalRules: [
1095
+ "- Do NOT use shell commands to send or receive messages. The MCP tools handle everything."
1096
+ ],
1097
+ postStartupNotes: [],
1098
+ includeStdinNotificationSection: true
1099
+ });
1100
+ }
1101
+ toolDisplayName(name) {
1102
+ if (name === "list_tasks") return "Viewing task board\u2026";
1103
+ if (name === "create_tasks") return "Creating tasks\u2026";
1104
+ if (name === "claim_tasks") return "Claiming tasks\u2026";
1105
+ if (name === "unclaim_task") return "Unclaiming task\u2026";
1106
+ if (name === "update_task_status") return "Updating task\u2026";
1107
+ if (name === "send_message" || name === "receive_message" || name === "read_history" || name === "list_server") return "";
1108
+ if (name === "Shell") return "Running command\u2026";
1109
+ if (name === "ReadFile") return "Reading file\u2026";
1110
+ if (name === "WriteFile" || name === "StrReplaceFile") return "Editing file\u2026";
1111
+ if (name === "Glob" || name === "Grep") return "Searching code\u2026";
1112
+ if (name === "SearchWeb") return "Searching web\u2026";
1113
+ if (name === "FetchURL") return "Fetching web\u2026";
1114
+ if (name === "SetTodoList") return "Updating tasks\u2026";
1115
+ return `Using ${name.length > 20 ? name.slice(0, 20) + "\u2026" : name}\u2026`;
1116
+ }
1117
+ summarizeToolInput(name, input) {
1118
+ if (!input || typeof input !== "object") return "";
1119
+ try {
1120
+ if (name === "Shell") {
1121
+ const cmd = input.command || "";
1122
+ return cmd.length > 100 ? cmd.slice(0, 100) + "\u2026" : cmd;
1123
+ }
1124
+ if (name === "ReadFile" || name === "WriteFile" || name === "StrReplaceFile") {
1125
+ return input.path || "";
1126
+ }
1127
+ if (name === "Glob" || name === "Grep") return input.pattern || input.query || "";
1128
+ if (name === "SearchWeb") return input.query || "";
1129
+ if (name === "FetchURL") return input.url || "";
1130
+ if (name === "send_message") return input.target || input.channel || "";
1131
+ if (name === "read_history") return input.target || input.channel || "";
1132
+ if (name === "list_tasks") return input.channel || "";
1133
+ if (name === "create_tasks") return input.channel || "";
1134
+ if (name === "claim_tasks") {
1135
+ const nums = input.task_numbers;
1136
+ return input.channel ? `${input.channel} #t${Array.isArray(nums) ? nums.join(",#t") : nums}` : "";
1137
+ }
1138
+ if (name === "unclaim_task" || name === "update_task_status") {
1139
+ return input.channel ? `${input.channel} #t${input.task_number}` : "";
1140
+ }
1141
+ return "";
1142
+ } catch {
1143
+ return "";
1144
+ }
1145
+ }
1146
+ };
1147
+
881
1148
  // src/drivers/index.ts
882
- var drivers = {
883
- claude: new ClaudeDriver(),
884
- codex: new CodexDriver()
1149
+ var driverFactories = {
1150
+ claude: () => new ClaudeDriver(),
1151
+ codex: () => new CodexDriver(),
1152
+ kimi: () => new KimiDriver()
885
1153
  };
886
1154
  function getDriver(runtimeId) {
887
- const driver = drivers[runtimeId];
1155
+ const createDriver = driverFactories[runtimeId];
1156
+ const driver = createDriver?.();
888
1157
  if (!driver) {
889
- throw new Error(`Unknown runtime: ${runtimeId}. Available: ${Object.keys(drivers).join(", ")}`);
1158
+ throw new Error(`Unknown runtime: ${runtimeId}. Available: ${Object.keys(driverFactories).join(", ")}`);
890
1159
  }
891
1160
  return driver;
892
1161
  }
893
1162
 
894
1163
  // src/agentProcessManager.ts
895
- var DATA_DIR = path3.join(os.homedir(), ".slock", "agents");
1164
+ var DATA_DIR = path4.join(os.homedir(), ".slock", "agents");
896
1165
  function toLocalTime(iso) {
897
1166
  const d = new Date(iso);
898
1167
  if (isNaN(d.getTime())) return iso;
@@ -902,6 +1171,28 @@ function toLocalTime(iso) {
902
1171
  function formatChannelLabel(message) {
903
1172
  return message.channel_type === "dm" ? `DM:@${message.channel_name}` : `#${message.channel_name}`;
904
1173
  }
1174
+ function formatMessageTarget(message) {
1175
+ if (message.channel_type === "thread" && message.parent_channel_name) {
1176
+ const shortId = message.channel_name.startsWith("thread-") ? message.channel_name.slice(7) : message.channel_name;
1177
+ if (message.parent_channel_type === "dm") {
1178
+ return `dm:@${message.parent_channel_name}:${shortId}`;
1179
+ }
1180
+ return `#${message.parent_channel_name}:${shortId}`;
1181
+ }
1182
+ if (message.channel_type === "dm") {
1183
+ return `dm:@${message.channel_name}`;
1184
+ }
1185
+ return `#${message.channel_name}`;
1186
+ }
1187
+ function formatIncomingMessage(message) {
1188
+ const target = formatMessageTarget(message);
1189
+ const msgId = message.message_id ? message.message_id.slice(0, 8) : "-";
1190
+ const time = message.timestamp ? toLocalTime(message.timestamp) : "-";
1191
+ const senderType = message.sender_type === "agent" ? " type=agent" : "";
1192
+ const attachSuffix = message.attachments?.length ? ` [${message.attachments.length} image${message.attachments.length > 1 ? "s" : ""}: ${message.attachments.map((a) => `${a.filename} (id:${a.id})`).join(", ")} \u2014 use view_file to see]` : "";
1193
+ const taskSuffix = message.task_status ? ` [task #${message.task_number} status=${message.task_status}${message.task_assignee_id ? ` assignee=${message.task_assignee_type}:${message.task_assignee_id}` : ""}]` : "";
1194
+ return `[target=${target} msg=${msgId} time=${time}${senderType}] @${message.sender_name}: ${message.content}${attachSuffix}${taskSuffix}`;
1195
+ }
905
1196
  function buildUnreadSummary(messages, excludeChannel) {
906
1197
  const summary = /* @__PURE__ */ new Map();
907
1198
  for (const message of messages) {
@@ -912,6 +1203,7 @@ function buildUnreadSummary(messages, excludeChannel) {
912
1203
  return summary.size > 0 ? Object.fromEntries(summary) : void 0;
913
1204
  }
914
1205
  var MAX_TRAJECTORY_TEXT = 2e3;
1206
+ var TRAJECTORY_COALESCE_MS = 350;
915
1207
  var ACTIVITY_HEARTBEAT_MS = 6e4;
916
1208
  var MAX_STDOUT_LINES = 8;
917
1209
  var MAX_STDOUT_LINE_LENGTH = 240;
@@ -989,21 +1281,21 @@ var AgentProcessManager = class _AgentProcessManager {
989
1281
  this.dataDir = opts.dataDir || DATA_DIR;
990
1282
  this.driverResolver = opts.driverResolver || getDriver;
991
1283
  }
992
- async startAgent(agentId, config, wakeMessage, unreadSummary, resumePrompt) {
1284
+ async startAgent(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
993
1285
  if (this.agents.has(agentId)) {
994
- console.log(`[Agent ${agentId}] Start ignored (already running)`);
1286
+ logger.info(`[Agent ${agentId}] Start ignored (already running)`);
995
1287
  return;
996
1288
  }
997
1289
  if (this.agentsStarting.has(agentId)) {
998
- console.log(`[Agent ${agentId}] Start ignored (startup in progress)`);
1290
+ logger.info(`[Agent ${agentId}] Start ignored (startup in progress)`);
999
1291
  return;
1000
1292
  }
1001
1293
  this.agentsStarting.add(agentId);
1002
1294
  try {
1003
1295
  const driver = this.driverResolver(config.runtime || "claude");
1004
- const agentDataDir = path3.join(this.dataDir, agentId);
1296
+ const agentDataDir = path4.join(this.dataDir, agentId);
1005
1297
  await mkdir(agentDataDir, { recursive: true });
1006
- const memoryMdPath = path3.join(agentDataDir, "MEMORY.md");
1298
+ const memoryMdPath = path4.join(agentDataDir, "MEMORY.md");
1007
1299
  try {
1008
1300
  await access(memoryMdPath);
1009
1301
  } catch {
@@ -1021,7 +1313,7 @@ ${config.description || "No role defined yet."}
1021
1313
  `;
1022
1314
  await writeFile(memoryMdPath, initialMemoryMd);
1023
1315
  }
1024
- await mkdir(path3.join(agentDataDir, "notes"), { recursive: true });
1316
+ await mkdir(path4.join(agentDataDir, "notes"), { recursive: true });
1025
1317
  const isResume = !!config.sessionId;
1026
1318
  let prompt;
1027
1319
  if (isResume && resumePrompt) {
@@ -1031,14 +1323,11 @@ ${config.description || "No role defined yet."}
1031
1323
 
1032
1324
  Note: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call check_messages to check for messages.`;
1033
1325
  }
1034
- } else if (isResume && wakeMessage) {
1035
- const channelLabel = wakeMessage.channel_type === "dm" ? `DM:@${wakeMessage.channel_name}` : `#${wakeMessage.channel_name}`;
1036
- const senderPrefix = wakeMessage.sender_type === "agent" ? "(agent) " : "";
1037
- const time = wakeMessage.timestamp ? ` (${toLocalTime(wakeMessage.timestamp)})` : "";
1038
- const formatted = `[${channelLabel}]${time} ${senderPrefix}@${wakeMessage.sender_name}: ${wakeMessage.content}`;
1326
+ } else if (wakeMessage) {
1327
+ const channelLabel = formatChannelLabel(wakeMessage);
1039
1328
  prompt = `New message received:
1040
1329
 
1041
- ${formatted}`;
1330
+ ${formatIncomingMessage(wakeMessage)}`;
1042
1331
  if (unreadSummary && Object.keys(unreadSummary).length > 0) {
1043
1332
  const otherUnread = Object.entries(unreadSummary).filter(([key]) => key !== channelLabel);
1044
1333
  if (otherUnread.length > 0) {
@@ -1097,6 +1386,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1097
1386
  inbox: this.startingInboxes.get(agentId) || [],
1098
1387
  config,
1099
1388
  sessionId: config.sessionId || null,
1389
+ launchId: launchId || null,
1100
1390
  isIdle: false,
1101
1391
  notificationTimer: null,
1102
1392
  pendingNotificationCount: 0,
@@ -1108,7 +1398,8 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1108
1398
  lastRuntimeError: null,
1109
1399
  spawnError: null,
1110
1400
  exitCode: null,
1111
- exitSignal: null
1401
+ exitSignal: null,
1402
+ pendingTrajectory: null
1112
1403
  };
1113
1404
  this.startingInboxes.delete(agentId);
1114
1405
  this.agents.set(agentId, agentProcess);
@@ -1139,12 +1430,12 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1139
1430
  if (current) {
1140
1431
  current.recentStderr = pushRecentStderr(current.recentStderr, text);
1141
1432
  }
1142
- console.error(`[Agent ${agentId} stderr]: ${text}`);
1433
+ logger.error(`[Agent ${agentId} stderr]: ${text}`);
1143
1434
  });
1144
1435
  proc.on("error", (err) => {
1145
1436
  const current = this.agents.get(agentId);
1146
1437
  if (current) current.spawnError = err.message;
1147
- console.error(`[Agent ${agentId}] Process error: ${err.message}`);
1438
+ logger.error(`[Agent ${agentId}] Process error: ${err.message}`);
1148
1439
  });
1149
1440
  proc.on("exit", (code, signal) => {
1150
1441
  const current = this.agents.get(agentId);
@@ -1152,7 +1443,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1152
1443
  current.exitCode = code;
1153
1444
  current.exitSignal = signal;
1154
1445
  }
1155
- console.log(`[Agent ${agentId}] Process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
1446
+ logger.info(`[Agent ${agentId}] Process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
1156
1447
  });
1157
1448
  proc.on("close", (code, signal) => {
1158
1449
  if (this.agents.has(agentId)) {
@@ -1161,6 +1452,9 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1161
1452
  if (ap.notificationTimer) {
1162
1453
  clearTimeout(ap.notificationTimer);
1163
1454
  }
1455
+ if (ap.pendingTrajectory?.timer) {
1456
+ clearTimeout(ap.pendingTrajectory.timer);
1457
+ }
1164
1458
  if (ap.activityHeartbeat) {
1165
1459
  clearInterval(ap.activityHeartbeat);
1166
1460
  }
@@ -1171,19 +1465,21 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1171
1465
  const queuedWakeMessage = !ap.driver.supportsStdinNotification ? ap.inbox.shift() : void 0;
1172
1466
  const unreadSummary2 = queuedWakeMessage ? buildUnreadSummary(ap.inbox, formatChannelLabel(queuedWakeMessage)) : void 0;
1173
1467
  if (queuedWakeMessage) {
1174
- console.log(`[Agent ${agentId}] Turn completed; restarting immediately for queued message`);
1468
+ logger.info(`[Agent ${agentId}] Turn completed; restarting immediately for queued message`);
1175
1469
  const nextConfig = { ...ap.config, sessionId: ap.sessionId };
1176
1470
  this.idleAgentConfigs.set(agentId, {
1177
1471
  config: nextConfig,
1178
- sessionId: ap.sessionId
1472
+ sessionId: ap.sessionId,
1473
+ launchId: ap.launchId
1179
1474
  });
1180
1475
  this.broadcastActivity(agentId, "working", "Message received");
1181
1476
  this.idleAgentConfigs.delete(agentId);
1182
- this.startAgent(agentId, nextConfig, queuedWakeMessage, unreadSummary2).catch((err) => {
1183
- console.error(`[Agent ${agentId}] Failed to continue with queued message:`, err);
1477
+ this.startAgent(agentId, nextConfig, queuedWakeMessage, unreadSummary2, void 0, ap.launchId || void 0).catch((err) => {
1478
+ logger.error(`[Agent ${agentId}] Failed to continue with queued message`, err);
1184
1479
  this.idleAgentConfigs.set(agentId, {
1185
1480
  config: nextConfig,
1186
- sessionId: ap.sessionId
1481
+ sessionId: ap.sessionId,
1482
+ launchId: ap.launchId
1187
1483
  });
1188
1484
  this.broadcastActivity(agentId, "online", "Process idle");
1189
1485
  });
@@ -1191,10 +1487,11 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1191
1487
  }
1192
1488
  this.idleAgentConfigs.set(agentId, {
1193
1489
  config: { ...ap.config, sessionId: ap.sessionId },
1194
- sessionId: ap.sessionId
1490
+ sessionId: ap.sessionId,
1491
+ launchId: ap.launchId
1195
1492
  });
1196
1493
  if (!ap.driver.supportsStdinNotification) {
1197
- console.log(`[Agent ${agentId}] Turn completed; cached idle state for future restart`);
1494
+ logger.info(`[Agent ${agentId}] Turn completed; cached idle state for future restart`);
1198
1495
  }
1199
1496
  this.broadcastActivity(agentId, "online", "Process idle");
1200
1497
  } else {
@@ -1204,7 +1501,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1204
1501
  if (isMissingResumeSession(ap)) {
1205
1502
  const staleSessionId = ap.sessionId;
1206
1503
  const restartConfig = { ...ap.config, sessionId: null };
1207
- console.warn(
1504
+ logger.warn(
1208
1505
  `[Agent ${agentId}] Stored Claude session ${staleSessionId} is unavailable locally; falling back to cold start`
1209
1506
  );
1210
1507
  this.broadcastActivity(
@@ -1213,20 +1510,20 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1213
1510
  "Stored Claude session missing; cold-starting a new session\u2026",
1214
1511
  [{ kind: "text", text: `Stored Claude session ${staleSessionId} was not found locally. Falling back to a cold start.` }]
1215
1512
  );
1216
- this.startAgent(agentId, restartConfig).catch((err) => {
1217
- console.error(`[Agent ${agentId}] Cold start recovery failed:`, err);
1218
- this.sendToServer({ type: "agent:status", agentId, status: "inactive" });
1513
+ this.startAgent(agentId, restartConfig, void 0, void 0, void 0, ap.launchId || void 0).catch((err) => {
1514
+ logger.error(`[Agent ${agentId}] Cold start recovery failed`, err);
1515
+ this.sendAgentStatus(agentId, "inactive", ap.launchId);
1219
1516
  this.broadcastActivity(agentId, "offline", `Crashed (${summary})`);
1220
1517
  });
1221
1518
  return;
1222
1519
  }
1223
- console.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
1224
- this.sendToServer({ type: "agent:status", agentId, status: "inactive" });
1520
+ logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
1521
+ this.sendAgentStatus(agentId, "inactive", ap.launchId);
1225
1522
  this.broadcastActivity(agentId, "offline", `Crashed (${summary})`);
1226
1523
  }
1227
1524
  }
1228
1525
  });
1229
- this.sendToServer({ type: "agent:status", agentId, status: "active" });
1526
+ this.sendAgentStatus(agentId, "active", launchId || null);
1230
1527
  this.broadcastActivity(agentId, "working", "Starting\u2026");
1231
1528
  } catch (err) {
1232
1529
  this.agentsStarting.delete(agentId);
@@ -1238,7 +1535,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1238
1535
  const ap = this.agents.get(agentId);
1239
1536
  if (!ap) {
1240
1537
  if (!silent) {
1241
- console.log(`[Agent ${agentId}] Stop requested but no running process was found`);
1538
+ logger.info(`[Agent ${agentId}] Stop requested but no running process was found`);
1242
1539
  }
1243
1540
  return;
1244
1541
  }
@@ -1251,15 +1548,15 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1251
1548
  this.agents.delete(agentId);
1252
1549
  ap.process.kill("SIGTERM");
1253
1550
  if (!silent) {
1254
- this.sendToServer({ type: "agent:status", agentId, status: "inactive" });
1551
+ this.sendAgentStatus(agentId, "inactive", ap.launchId);
1255
1552
  this.broadcastActivity(agentId, "offline", "Stopped");
1256
- console.log(`[Agent ${agentId}] Stopped by request`);
1553
+ logger.info(`[Agent ${agentId}] Stopped by request`);
1257
1554
  }
1258
1555
  if (wait) {
1259
1556
  await new Promise((resolve) => {
1260
1557
  const forceKillTimer = setTimeout(() => {
1261
1558
  if (!silent) {
1262
- console.warn(`[Agent ${agentId}] Stop timed out; force killing`);
1559
+ logger.warn(`[Agent ${agentId}] Stop timed out; force killing`);
1263
1560
  }
1264
1561
  try {
1265
1562
  ap.process.kill("SIGKILL");
@@ -1289,10 +1586,10 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1289
1586
  }
1290
1587
  const cached = this.idleAgentConfigs.get(agentId);
1291
1588
  if (cached) {
1292
- console.log(`[Agent ${agentId}] Starting from idle state for new message`);
1589
+ logger.info(`[Agent ${agentId}] Starting from idle state for new message`);
1293
1590
  this.idleAgentConfigs.delete(agentId);
1294
- this.startAgent(agentId, cached.config, message).catch((err) => {
1295
- console.error(`[Agent ${agentId}] Failed to auto-restart:`, err);
1591
+ this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).catch((err) => {
1592
+ logger.error(`[Agent ${agentId}] Failed to auto-restart`, err);
1296
1593
  });
1297
1594
  }
1298
1595
  return;
@@ -1300,7 +1597,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1300
1597
  if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
1301
1598
  ap.isIdle = false;
1302
1599
  this.broadcastActivity(agentId, "working", "Message received");
1303
- this.deliverMessageViaStdin(agentId, ap, message);
1600
+ this.deliverMessagesViaStdin(agentId, ap, [message], "idle");
1304
1601
  return;
1305
1602
  }
1306
1603
  ap.inbox.push(message);
@@ -1314,12 +1611,12 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1314
1611
  }
1315
1612
  }
1316
1613
  async resetWorkspace(agentId) {
1317
- const agentDataDir = path3.join(this.dataDir, agentId);
1614
+ const agentDataDir = path4.join(this.dataDir, agentId);
1318
1615
  try {
1319
1616
  await rm(agentDataDir, { recursive: true, force: true });
1320
- console.log(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
1617
+ logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
1321
1618
  } catch (err) {
1322
- console.error(`[Agent ${agentId}] Workspace reset failed:`, err);
1619
+ logger.error(`[Agent ${agentId}] Workspace reset failed`, err);
1323
1620
  }
1324
1621
  }
1325
1622
  async stopAll() {
@@ -1330,16 +1627,16 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1330
1627
  getRunningAgentIds() {
1331
1628
  return [...this.agents.keys()];
1332
1629
  }
1333
- getDataDir() {
1334
- return this.dataDir;
1335
- }
1336
1630
  getAgentSessionId(agentId) {
1337
1631
  return this.agents.get(agentId)?.sessionId ?? null;
1338
1632
  }
1633
+ getAgentLaunchId(agentId) {
1634
+ return this.agents.get(agentId)?.launchId ?? null;
1635
+ }
1339
1636
  getIdleAgentSessionIds() {
1340
1637
  const result = [];
1341
- for (const [agentId, { sessionId }] of this.idleAgentConfigs) {
1342
- if (sessionId) result.push({ agentId, sessionId });
1638
+ for (const [agentId, { sessionId, launchId }] of this.idleAgentConfigs) {
1639
+ if (sessionId) result.push({ agentId, sessionId, launchId });
1343
1640
  }
1344
1641
  return result;
1345
1642
  }
@@ -1354,14 +1651,14 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1354
1651
  }
1355
1652
  for (const entry of entries) {
1356
1653
  if (!entry.isDirectory()) continue;
1357
- const dirPath = path3.join(this.dataDir, entry.name);
1654
+ const dirPath = path4.join(this.dataDir, entry.name);
1358
1655
  try {
1359
1656
  const dirContents = await readdir(dirPath, { withFileTypes: true });
1360
1657
  let totalSize = 0;
1361
1658
  let latestMtime = /* @__PURE__ */ new Date(0);
1362
1659
  let fileCount = 0;
1363
1660
  for (const item of dirContents) {
1364
- const itemPath = path3.join(dirPath, item.name);
1661
+ const itemPath = path4.join(dirPath, item.name);
1365
1662
  try {
1366
1663
  const info = await stat(itemPath);
1367
1664
  if (item.isFile()) {
@@ -1391,19 +1688,19 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1391
1688
  if (directoryName.includes("/") || directoryName.includes("..") || directoryName.includes("\\")) {
1392
1689
  return false;
1393
1690
  }
1394
- const targetDir = path3.join(this.dataDir, directoryName);
1691
+ const targetDir = path4.join(this.dataDir, directoryName);
1395
1692
  try {
1396
1693
  await rm(targetDir, { recursive: true, force: true });
1397
- console.log(`[Workspace] Deleted directory: ${targetDir}`);
1694
+ logger.info(`[Workspace] Deleted directory: ${targetDir}`);
1398
1695
  return true;
1399
1696
  } catch (err) {
1400
- console.error(`[Workspace] Failed to delete directory ${targetDir}:`, err);
1697
+ logger.error(`[Workspace] Failed to delete directory ${targetDir}`, err);
1401
1698
  return false;
1402
1699
  }
1403
1700
  }
1404
1701
  // Workspace file browsing
1405
1702
  async getFileTree(agentId, dirPath) {
1406
- const agentDir = path3.join(this.dataDir, agentId);
1703
+ const agentDir = path4.join(this.dataDir, agentId);
1407
1704
  try {
1408
1705
  await stat(agentDir);
1409
1706
  } catch {
@@ -1411,8 +1708,8 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1411
1708
  }
1412
1709
  let targetDir = agentDir;
1413
1710
  if (dirPath) {
1414
- const resolved = path3.resolve(agentDir, dirPath);
1415
- if (!resolved.startsWith(agentDir + path3.sep) && resolved !== agentDir) {
1711
+ const resolved = path4.resolve(agentDir, dirPath);
1712
+ if (!resolved.startsWith(agentDir + path4.sep) && resolved !== agentDir) {
1416
1713
  return [];
1417
1714
  }
1418
1715
  targetDir = resolved;
@@ -1420,9 +1717,9 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1420
1717
  return this.listDirectoryChildren(targetDir, agentDir);
1421
1718
  }
1422
1719
  async readFile(agentId, filePath) {
1423
- const agentDir = path3.join(this.dataDir, agentId);
1424
- const resolved = path3.resolve(agentDir, filePath);
1425
- if (!resolved.startsWith(agentDir + path3.sep) && resolved !== agentDir) {
1720
+ const agentDir = path4.join(this.dataDir, agentId);
1721
+ const resolved = path4.resolve(agentDir, filePath);
1722
+ if (!resolved.startsWith(agentDir + path4.sep) && resolved !== agentDir) {
1426
1723
  throw new Error("Access denied");
1427
1724
  }
1428
1725
  const info = await stat(resolved);
@@ -1446,7 +1743,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1446
1743
  ".sh",
1447
1744
  ".py"
1448
1745
  ]);
1449
- const ext = path3.extname(resolved).toLowerCase();
1746
+ const ext = path4.extname(resolved).toLowerCase();
1450
1747
  if (!TEXT_EXTENSIONS.has(ext) && ext !== "") {
1451
1748
  return { content: null, binary: true };
1452
1749
  }
@@ -1473,13 +1770,13 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1473
1770
  const agent = this.agents.get(agentId);
1474
1771
  const runtime = runtimeHint || agent?.config.runtime || "claude";
1475
1772
  const home = os.homedir();
1476
- const workspaceDir = path3.join(this.dataDir, agentId);
1773
+ const workspaceDir = path4.join(this.dataDir, agentId);
1477
1774
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
1478
1775
  const globalResults = await Promise.all(
1479
- paths.global.map((p) => this.scanSkillsDir(path3.join(home, p)))
1776
+ paths.global.map((p) => this.scanSkillsDir(path4.join(home, p)))
1480
1777
  );
1481
1778
  const workspaceResults = await Promise.all(
1482
- paths.workspace.map((p) => this.scanSkillsDir(path3.join(workspaceDir, p)))
1779
+ paths.workspace.map((p) => this.scanSkillsDir(path4.join(workspaceDir, p)))
1483
1780
  );
1484
1781
  const dedup = (skills) => {
1485
1782
  const seen = /* @__PURE__ */ new Set();
@@ -1508,7 +1805,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1508
1805
  const skills = [];
1509
1806
  for (const entry of entries) {
1510
1807
  if (entry.isDirectory() || entry.isSymbolicLink()) {
1511
- const skillMd = path3.join(dir, entry.name, "SKILL.md");
1808
+ const skillMd = path4.join(dir, entry.name, "SKILL.md");
1512
1809
  try {
1513
1810
  const content = await readFile(skillMd, "utf-8");
1514
1811
  const skill = this.parseSkillMd(entry.name, content);
@@ -1519,7 +1816,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1519
1816
  } else if (entry.name.endsWith(".md")) {
1520
1817
  const cmdName = entry.name.replace(/\.md$/, "");
1521
1818
  try {
1522
- const content = await readFile(path3.join(dir, entry.name), "utf-8");
1819
+ const content = await readFile(path4.join(dir, entry.name), "utf-8");
1523
1820
  const skill = this.parseSkillMd(cmdName, content);
1524
1821
  skill.sourcePath = dir;
1525
1822
  skills.push(skill);
@@ -1562,14 +1859,20 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1562
1859
  if (!hasToolStart) {
1563
1860
  entries.push({ kind: "status", activity, detail });
1564
1861
  }
1565
- this.sendToServer({ type: "agent:activity", agentId, activity, detail, entries });
1862
+ this.sendToServer({ type: "agent:activity", agentId, activity, detail, entries, launchId: ap?.launchId || void 0 });
1566
1863
  if (ap) {
1567
1864
  ap.lastActivity = activity;
1568
1865
  ap.lastActivityDetail = detail;
1569
1866
  if (activity === "working" || activity === "thinking") {
1570
1867
  if (!ap.activityHeartbeat) {
1571
1868
  ap.activityHeartbeat = setInterval(() => {
1572
- this.sendToServer({ type: "agent:activity", agentId, activity: ap.lastActivity, detail: ap.lastActivityDetail });
1869
+ this.sendToServer({
1870
+ type: "agent:activity",
1871
+ agentId,
1872
+ activity: ap.lastActivity,
1873
+ detail: ap.lastActivityDetail,
1874
+ launchId: ap.launchId || void 0
1875
+ });
1573
1876
  }, ACTIVITY_HEARTBEAT_MS);
1574
1877
  }
1575
1878
  } else {
@@ -1580,33 +1883,64 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1580
1883
  }
1581
1884
  }
1582
1885
  }
1886
+ flushPendingTrajectory(agentId) {
1887
+ const ap = this.agents.get(agentId);
1888
+ const pending = ap?.pendingTrajectory;
1889
+ if (!ap || !pending) return;
1890
+ if (pending.timer) clearTimeout(pending.timer);
1891
+ ap.pendingTrajectory = null;
1892
+ const text = pending.text.length > MAX_TRAJECTORY_TEXT ? pending.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : pending.text;
1893
+ if (!text) return;
1894
+ if (pending.kind === "thinking") {
1895
+ this.broadcastActivity(agentId, "thinking", "", [{ kind: "thinking", text }]);
1896
+ } else {
1897
+ this.broadcastActivity(agentId, "thinking", "", [{ kind: "text", text }]);
1898
+ }
1899
+ }
1900
+ queueTrajectoryText(agentId, kind, text) {
1901
+ const ap = this.agents.get(agentId);
1902
+ if (!ap) return;
1903
+ if (!text) {
1904
+ this.broadcastActivity(agentId, "thinking", "");
1905
+ return;
1906
+ }
1907
+ const pending = ap.pendingTrajectory;
1908
+ if (pending && pending.kind === kind) {
1909
+ pending.text += text;
1910
+ if (pending.timer) clearTimeout(pending.timer);
1911
+ pending.timer = setTimeout(() => this.flushPendingTrajectory(agentId), TRAJECTORY_COALESCE_MS);
1912
+ return;
1913
+ }
1914
+ this.flushPendingTrajectory(agentId);
1915
+ if (ap.lastActivity !== "thinking" || ap.lastActivityDetail !== "") {
1916
+ this.broadcastActivity(agentId, "thinking", "");
1917
+ }
1918
+ ap.pendingTrajectory = {
1919
+ kind,
1920
+ text,
1921
+ timer: setTimeout(() => this.flushPendingTrajectory(agentId), TRAJECTORY_COALESCE_MS)
1922
+ };
1923
+ }
1583
1924
  /** Handle a single ParsedEvent from any runtime driver */
1584
1925
  handleParsedEvent(agentId, event, driver) {
1585
1926
  const ap = this.agents.get(agentId);
1586
1927
  switch (event.kind) {
1587
1928
  case "session_init":
1588
1929
  if (ap) ap.sessionId = event.sessionId;
1589
- this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId });
1590
- writeFile(
1591
- path3.join(this.dataDir, agentId, "session-meta.json"),
1592
- JSON.stringify({ sessionId: event.sessionId, updatedAt: (/* @__PURE__ */ new Date()).toISOString() })
1593
- ).catch(() => {
1594
- });
1930
+ this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId, launchId: ap?.launchId || void 0 });
1595
1931
  break;
1596
1932
  case "thinking": {
1597
- const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
1598
- const extra = text ? [{ kind: "thinking", text }] : [];
1599
- this.broadcastActivity(agentId, "thinking", "", extra);
1933
+ this.queueTrajectoryText(agentId, "thinking", event.text);
1600
1934
  if (ap) ap.isIdle = false;
1601
1935
  break;
1602
1936
  }
1603
1937
  case "text": {
1604
- const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
1605
- this.broadcastActivity(agentId, "thinking", "", [{ kind: "text", text }]);
1938
+ this.queueTrajectoryText(agentId, "text", event.text);
1606
1939
  if (ap) ap.isIdle = false;
1607
1940
  break;
1608
1941
  }
1609
1942
  case "tool_call": {
1943
+ this.flushPendingTrajectory(agentId);
1610
1944
  const toolName = event.name;
1611
1945
  const inputSummary = driver.summarizeToolInput(toolName, event.input);
1612
1946
  const detail = toolName === `${driver.mcpToolPrefix}check_messages` ? "Checking messages\u2026" : toolName === `${driver.mcpToolPrefix}send_message` ? "Sending message\u2026" : driver.toolDisplayName(toolName);
@@ -1615,35 +1949,42 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1615
1949
  break;
1616
1950
  }
1617
1951
  case "turn_end":
1952
+ this.flushPendingTrajectory(agentId);
1618
1953
  if (ap) {
1619
1954
  if (event.sessionId) ap.sessionId = event.sessionId;
1620
1955
  if (ap.inbox.length > 0 && ap.driver.supportsStdinNotification && ap.sessionId) {
1621
- const nextMessage = ap.inbox.shift();
1956
+ const nextMessages = ap.inbox.splice(0, ap.inbox.length);
1622
1957
  this.broadcastActivity(agentId, "working", "Message received");
1623
- this.deliverMessageViaStdin(agentId, ap, nextMessage);
1958
+ this.deliverMessagesViaStdin(agentId, ap, nextMessages, "idle");
1624
1959
  } else {
1625
1960
  ap.isIdle = true;
1626
1961
  this.broadcastActivity(agentId, "online", "Idle");
1627
1962
  }
1628
1963
  }
1629
1964
  if (event.sessionId) {
1630
- this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId });
1631
- writeFile(
1632
- path3.join(this.dataDir, agentId, "session-meta.json"),
1633
- JSON.stringify({ sessionId: event.sessionId, updatedAt: (/* @__PURE__ */ new Date()).toISOString() })
1634
- ).catch(() => {
1635
- });
1965
+ this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId, launchId: ap?.launchId || void 0 });
1636
1966
  }
1637
1967
  break;
1638
1968
  case "error": {
1969
+ this.flushPendingTrajectory(agentId);
1639
1970
  if (ap) ap.lastRuntimeError = event.message;
1640
1971
  const currentActivity = ap?.lastActivity || "working";
1641
1972
  const currentDetail = ap?.lastActivityDetail || "";
1642
- this.sendToServer({ type: "agent:activity", agentId, activity: currentActivity, detail: currentDetail, entries: [{ kind: "text", text: `Error: ${event.message}` }] });
1973
+ this.sendToServer({
1974
+ type: "agent:activity",
1975
+ agentId,
1976
+ activity: currentActivity,
1977
+ detail: currentDetail,
1978
+ entries: [{ kind: "text", text: `Error: ${event.message}` }],
1979
+ launchId: ap?.launchId || void 0
1980
+ });
1643
1981
  break;
1644
1982
  }
1645
1983
  }
1646
1984
  }
1985
+ sendAgentStatus(agentId, status, launchId) {
1986
+ this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
1987
+ }
1647
1988
  /** Send a batched notification to the agent via stdin about pending messages */
1648
1989
  sendStdinNotification(agentId) {
1649
1990
  const ap = this.agents.get(agentId);
@@ -1654,27 +1995,38 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1654
1995
  if (count === 0) return;
1655
1996
  if (ap.isIdle) return;
1656
1997
  if (!ap.sessionId) return;
1998
+ if (ap.driver.deliverMessageDirectlyWhileBusy && ap.inbox.length > 0) {
1999
+ const queuedMessages = ap.inbox.splice(0, ap.inbox.length);
2000
+ console.log(`[Agent ${agentId}] Delivering queued message via stdin while busy`);
2001
+ this.broadcastActivity(agentId, "working", "Message received");
2002
+ this.deliverMessagesViaStdin(agentId, ap, queuedMessages, "busy");
2003
+ return;
2004
+ }
1657
2005
  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.]`;
1658
- console.log(`[Agent ${agentId}] Sending stdin notification: ${count} message(s)`);
1659
- const encoded = ap.driver.encodeStdinMessage(notification, ap.sessionId);
2006
+ logger.info(`[Agent ${agentId}] Sending stdin notification: ${count} message(s)`);
2007
+ const encoded = ap.driver.encodeStdinMessage(notification, ap.sessionId, { mode: "busy" });
1660
2008
  if (encoded) {
1661
2009
  ap.process.stdin?.write(encoded + "\n");
1662
2010
  }
1663
2011
  }
1664
2012
  /** Deliver a message to an agent via stdin, formatting it the same way as the MCP bridge */
1665
- deliverMessageViaStdin(agentId, ap, message) {
1666
- const channelLabel = message.channel_type === "dm" ? `DM:@${message.channel_name}` : `#${message.channel_name}`;
1667
- const senderPrefix = message.sender_type === "agent" ? "(agent) " : "";
1668
- const time = message.timestamp ? ` (${toLocalTime(message.timestamp)})` : "";
1669
- const formatted = `[${channelLabel}]${time} ${senderPrefix}@${message.sender_name}: ${message.content}`;
1670
- const prompt = `New message received:
2013
+ deliverMessagesViaStdin(agentId, ap, messages, mode) {
2014
+ if (messages.length === 0) return;
2015
+ const prompt = messages.length === 1 ? `New message received:
2016
+
2017
+ ${formatIncomingMessage(messages[0])}
1671
2018
 
1672
- ${formatted}
2019
+ Respond as appropriate. Complete all your work before stopping.` : `New messages received:
2020
+
2021
+ ${messages.map((message) => formatIncomingMessage(message)).join("\n")}
1673
2022
 
1674
2023
  Respond as appropriate. Complete all your work before stopping.`;
1675
- const encoded = ap.driver.encodeStdinMessage(prompt, ap.sessionId);
2024
+ const encoded = ap.driver.encodeStdinMessage(prompt, ap.sessionId, { mode });
1676
2025
  if (encoded) {
1677
- console.log(`[Agent ${agentId}] Delivering message via stdin from @${message.sender_name}`);
2026
+ const senders = [...new Set(messages.map((message) => `@${message.sender_name}`))].join(", ");
2027
+ logger.info(
2028
+ `[Agent ${agentId}] Delivering ${mode} ${messages.length === 1 ? "message" : `${messages.length} messages`} via stdin from ${senders}`
2029
+ );
1678
2030
  ap.process.stdin?.write(encoded + "\n");
1679
2031
  }
1680
2032
  }
@@ -1694,8 +2046,8 @@ Respond as appropriate. Complete all your work before stopping.`;
1694
2046
  const nodes = [];
1695
2047
  for (const entry of entries) {
1696
2048
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
1697
- const fullPath = path3.join(dir, entry.name);
1698
- const relativePath = path3.relative(rootDir, fullPath);
2049
+ const fullPath = path4.join(dir, entry.name);
2050
+ const relativePath = path4.relative(rootDir, fullPath);
1699
2051
  let info;
1700
2052
  try {
1701
2053
  info = await stat(fullPath);
@@ -1761,53 +2113,12 @@ var RUNTIMES = [
1761
2113
  { id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
1762
2114
  { id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
1763
2115
  { id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: false },
1764
- { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: false }
2116
+ { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true }
1765
2117
  ];
1766
2118
 
1767
2119
  // src/index.ts
1768
2120
  var require2 = createRequire(import.meta.url);
1769
2121
  var DAEMON_VERSION = require2("../package.json").version;
1770
- var LOG_DIR = path4.join(os2.homedir(), ".slock", "logs");
1771
- var LOG_FILE = path4.join(LOG_DIR, "daemon.log");
1772
- var MAX_LOG_BYTES = 5 * 1024 * 1024;
1773
- async function initLogFile() {
1774
- try {
1775
- await mkdir2(LOG_DIR, { recursive: true });
1776
- try {
1777
- const s = await stat2(LOG_FILE);
1778
- if (s.size > MAX_LOG_BYTES) {
1779
- const content = await readFile2(LOG_FILE, "utf-8");
1780
- const trimmed = content.slice(Math.floor(content.length / 2));
1781
- await appendFile(LOG_FILE, "");
1782
- const { writeFile: writeFile2 } = await import("fs/promises");
1783
- await writeFile2(LOG_FILE, trimmed);
1784
- }
1785
- } catch {
1786
- }
1787
- } catch {
1788
- }
1789
- }
1790
- function logLine(level, ...args2) {
1791
- const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args2.map(String).join(" ")}
1792
- `;
1793
- appendFile(LOG_FILE, line).catch(() => {
1794
- });
1795
- }
1796
- var _origLog = console.log.bind(console);
1797
- var _origErr = console.error.bind(console);
1798
- var _origWarn = console.warn.bind(console);
1799
- console.log = (...args2) => {
1800
- _origLog(...args2);
1801
- logLine("INFO", ...args2);
1802
- };
1803
- console.error = (...args2) => {
1804
- _origErr(...args2);
1805
- logLine("ERROR", ...args2);
1806
- };
1807
- console.warn = (...args2) => {
1808
- _origWarn(...args2);
1809
- logLine("WARN", ...args2);
1810
- };
1811
2122
  function formatChannelTarget(msg) {
1812
2123
  return msg.message.channel_type === "dm" ? `dm:@${msg.message.channel_name}` : `#${msg.message.channel_name}`;
1813
2124
  }
@@ -1829,8 +2140,6 @@ function summarizeIncomingMessage(msg) {
1829
2140
  return `(agent=${msg.agentId}, runtime=${msg.runtime || "auto"})`;
1830
2141
  case "machine:workspace:delete":
1831
2142
  return `(directory=${msg.directoryName})`;
1832
- case "machine:feedback:collect":
1833
- return `(reportId=${msg.reportId}, agents=${msg.agents.length})`;
1834
2143
  default:
1835
2144
  return "";
1836
2145
  }
@@ -1858,134 +2167,14 @@ if (!serverUrl || !apiKey) {
1858
2167
  console.error("Usage: slock-daemon --server-url <url> --api-key <key>");
1859
2168
  process.exit(1);
1860
2169
  }
1861
- var __dirname = path4.dirname(fileURLToPath(import.meta.url));
1862
- var chatBridgePath = path4.resolve(__dirname, "chat-bridge.js");
2170
+ var __dirname = path5.dirname(fileURLToPath(import.meta.url));
2171
+ var chatBridgePath = path5.resolve(__dirname, "chat-bridge.js");
1863
2172
  try {
1864
2173
  accessSync(chatBridgePath);
1865
2174
  } catch {
1866
- chatBridgePath = path4.resolve(__dirname, "chat-bridge.ts");
2175
+ chatBridgePath = path5.resolve(__dirname, "chat-bridge.ts");
1867
2176
  }
1868
2177
  var connection;
1869
- async function collectAndUploadAgent(opts) {
1870
- const { agentId, reportAgentId, uploadUrl, authToken, timeRangeHours, includeSessionFiles, includeDaemonLogs, includeWorkspaceSnapshot, dataDir } = opts;
1871
- const sinceMs = Date.now() - timeRangeHours * 60 * 60 * 1e3;
1872
- let bytesUploaded = 0;
1873
- let anyUploadFailed = false;
1874
- try {
1875
- const agentDir = path4.join(dataDir, agentId);
1876
- let sessionId = opts.sessionId;
1877
- try {
1878
- const meta = JSON.parse(await readFile2(path4.join(agentDir, "session-meta.json"), "utf-8"));
1879
- if (meta.sessionId) sessionId = meta.sessionId;
1880
- } catch {
1881
- }
1882
- const filesToUpload = [];
1883
- if (includeSessionFiles && sessionId) {
1884
- const claudeProjectsDir = path4.join(os2.homedir(), ".claude", "projects");
1885
- try {
1886
- const projectDirs = await readdir2(claudeProjectsDir, { withFileTypes: true });
1887
- for (const pd of projectDirs) {
1888
- if (!pd.isDirectory()) continue;
1889
- const pdPath = path4.join(claudeProjectsDir, pd.name);
1890
- const files = await readdir2(pdPath, { withFileTypes: true });
1891
- for (const f of files) {
1892
- if (!f.isFile() || !f.name.endsWith(".jsonl")) continue;
1893
- if (!f.name.startsWith(sessionId)) continue;
1894
- const filePath = path4.join(pdPath, f.name);
1895
- const raw = await readFile2(filePath, "utf-8");
1896
- const filteredLines = raw.split("\n").filter((line) => {
1897
- if (!line.trim()) return false;
1898
- try {
1899
- const obj = JSON.parse(line);
1900
- if (!obj.timestamp) return true;
1901
- return new Date(obj.timestamp).getTime() >= sinceMs;
1902
- } catch {
1903
- return false;
1904
- }
1905
- }).join("\n");
1906
- if (!filteredLines.trim()) continue;
1907
- filesToUpload.push({ filename: f.name, data: Buffer.from(filteredLines), kind: "session_jsonl" });
1908
- }
1909
- }
1910
- } catch {
1911
- }
1912
- }
1913
- if (includeDaemonLogs) {
1914
- try {
1915
- const raw = await readFile2(LOG_FILE, "utf-8");
1916
- const filtered = raw.split("\n").filter((line) => {
1917
- if (!line.trim()) return false;
1918
- const ts = line.slice(0, 24);
1919
- return new Date(ts).getTime() >= sinceMs;
1920
- }).join("\n");
1921
- if (filtered.trim()) {
1922
- filesToUpload.push({ filename: "daemon.log", data: Buffer.from(filtered), kind: "daemon_log" });
1923
- }
1924
- } catch {
1925
- }
1926
- }
1927
- if (includeWorkspaceSnapshot) {
1928
- try {
1929
- const agentStat = await stat2(agentDir);
1930
- if (agentStat.isDirectory()) {
1931
- const files = await readdir2(agentDir, { withFileTypes: true });
1932
- for (const f of files) {
1933
- if (!f.isFile()) continue;
1934
- if (f.name === "session-meta.json") continue;
1935
- const filePath = path4.join(agentDir, f.name);
1936
- const s = await stat2(filePath);
1937
- if (s.size > 1024 * 1024) continue;
1938
- const data = await readFile2(filePath);
1939
- filesToUpload.push({ filename: f.name, data, kind: "runtime_log" });
1940
- }
1941
- }
1942
- } catch {
1943
- }
1944
- }
1945
- const manifest = {
1946
- reportAgentId,
1947
- agentId,
1948
- sessionId,
1949
- runtime: opts.runtime,
1950
- daemonVersion: DAEMON_VERSION,
1951
- hostname: os2.hostname(),
1952
- os: `${os2.platform()} ${os2.arch()}`,
1953
- collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
1954
- timeRangeHours,
1955
- includeSessionFiles,
1956
- includeDaemonLogs,
1957
- includeWorkspaceSnapshot,
1958
- artifactCount: filesToUpload.length
1959
- };
1960
- filesToUpload.unshift({ filename: "manifest.json", data: Buffer.from(JSON.stringify(manifest, null, 2)), kind: "manifest" });
1961
- for (const file of filesToUpload) {
1962
- const formData = new FormData();
1963
- formData.append("reportAgentId", reportAgentId);
1964
- formData.append("kind", file.kind);
1965
- formData.append("filename", file.filename);
1966
- formData.append("file", new Blob([new Uint8Array(file.data)]), file.filename);
1967
- const resp = await fetch(uploadUrl, {
1968
- method: "POST",
1969
- headers: { Authorization: `Bearer ${authToken}` },
1970
- body: formData
1971
- });
1972
- if (resp.ok) {
1973
- bytesUploaded += file.data.length;
1974
- } else {
1975
- console.warn(`[Feedback] Upload failed for ${file.filename}: ${resp.status}`);
1976
- anyUploadFailed = true;
1977
- }
1978
- }
1979
- if (anyUploadFailed) {
1980
- return { reportAgentId, status: "error", daemonVersion: DAEMON_VERSION, error: "One or more files failed to upload", bytesUploaded };
1981
- }
1982
- return { reportAgentId, status: "collected", daemonVersion: DAEMON_VERSION, bytesUploaded };
1983
- } catch (err) {
1984
- const error = err instanceof Error ? err.message : String(err);
1985
- console.error(`[Feedback] Collection failed for agent ${agentId}:`, error);
1986
- return { reportAgentId, status: "error", daemonVersion: DAEMON_VERSION, error };
1987
- }
1988
- }
1989
2178
  var agentManager = new AgentProcessManager(chatBridgePath, (msg) => {
1990
2179
  connection.send(msg);
1991
2180
  }, apiKey);
@@ -1994,27 +2183,27 @@ connection = new DaemonConnection({
1994
2183
  apiKey,
1995
2184
  onMessage: (msg) => {
1996
2185
  const summary = summarizeIncomingMessage(msg);
1997
- console.log(`[Daemon] Received ${msg.type}${summary ? ` ${summary}` : ""}`);
2186
+ logger.info(`[Daemon] Received ${msg.type}${summary ? ` ${summary}` : ""}`);
1998
2187
  switch (msg.type) {
1999
2188
  case "agent:start":
2000
- console.log(`[Agent ${msg.agentId}] Start requested (runtime=${msg.config.runtime}, model=${msg.config.model}, session=${msg.config.sessionId || "new"}${msg.wakeMessage ? ", wake=true" : ""})`);
2001
- agentManager.startAgent(msg.agentId, msg.config, msg.wakeMessage, msg.unreadSummary, msg.resumePrompt).catch((err) => {
2189
+ logger.info(`[Agent ${msg.agentId}] Start requested (runtime=${msg.config.runtime}, model=${msg.config.model}, session=${msg.config.sessionId || "new"}${msg.wakeMessage ? ", wake=true" : ""})`);
2190
+ agentManager.startAgent(msg.agentId, msg.config, msg.wakeMessage, msg.unreadSummary, msg.resumePrompt, msg.launchId).catch((err) => {
2002
2191
  const reason = err instanceof Error ? err.message : String(err);
2003
- console.error(`[Agent ${msg.agentId}] Start failed (${reason})`);
2004
- connection.send({ type: "agent:status", agentId: msg.agentId, status: "inactive" });
2005
- connection.send({ type: "agent:activity", agentId: msg.agentId, activity: "offline", detail: `Start failed: ${reason}` });
2192
+ logger.error(`[Agent ${msg.agentId}] Start failed (${reason})`);
2193
+ connection.send({ type: "agent:status", agentId: msg.agentId, status: "inactive", launchId: msg.launchId });
2194
+ connection.send({ type: "agent:activity", agentId: msg.agentId, activity: "offline", detail: `Start failed: ${reason}`, launchId: msg.launchId });
2006
2195
  });
2007
2196
  break;
2008
2197
  case "agent:stop":
2009
- console.log(`[Agent ${msg.agentId}] Stop requested`);
2198
+ logger.info(`[Agent ${msg.agentId}] Stop requested`);
2010
2199
  agentManager.stopAgent(msg.agentId);
2011
2200
  break;
2012
2201
  case "agent:reset-workspace":
2013
- console.log(`[Agent ${msg.agentId}] Workspace reset requested`);
2202
+ logger.info(`[Agent ${msg.agentId}] Workspace reset requested`);
2014
2203
  agentManager.resetWorkspace(msg.agentId);
2015
2204
  break;
2016
2205
  case "agent:deliver":
2017
- console.log(`[Agent ${msg.agentId}] Delivery received (seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`);
2206
+ logger.info(`[Agent ${msg.agentId}] Delivery received (seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`);
2018
2207
  agentManager.deliverMessage(msg.agentId, msg.message);
2019
2208
  connection.send({ type: "agent:deliver:ack", agentId: msg.agentId, seq: msg.seq });
2020
2209
  break;
@@ -2046,18 +2235,18 @@ connection = new DaemonConnection({
2046
2235
  agentManager.listSkills(msg.agentId, msg.runtime).then(({ global, workspace }) => {
2047
2236
  connection.send({ type: "agent:skills:list_result", agentId: msg.agentId, global, workspace });
2048
2237
  }).catch((err) => {
2049
- console.error(`[Daemon] Failed to list skills for ${msg.agentId}:`, err);
2238
+ logger.error(`[Daemon] Failed to list skills for ${msg.agentId}`, err);
2050
2239
  connection.send({ type: "agent:skills:list_result", agentId: msg.agentId, global: [], workspace: [] });
2051
2240
  });
2052
2241
  break;
2053
2242
  case "machine:workspace:scan":
2054
- console.log("[Daemon] Scanning all workspace directories");
2243
+ logger.info("[Daemon] Scanning all workspace directories");
2055
2244
  agentManager.scanAllWorkspaces().then((directories) => {
2056
2245
  connection.send({ type: "machine:workspace:scan_result", directories });
2057
2246
  });
2058
2247
  break;
2059
2248
  case "machine:workspace:delete":
2060
- console.log(`[Daemon] Deleting workspace directory: ${msg.directoryName}`);
2249
+ logger.info(`[Daemon] Deleting workspace directory: ${msg.directoryName}`);
2061
2250
  agentManager.deleteWorkspaceDirectory(msg.directoryName).then((success) => {
2062
2251
  connection.send({ type: "machine:workspace:delete_result", directoryName: msg.directoryName, success });
2063
2252
  });
@@ -2065,42 +2254,11 @@ connection = new DaemonConnection({
2065
2254
  case "ping":
2066
2255
  connection.send({ type: "pong" });
2067
2256
  break;
2068
- case "machine:feedback:collect": {
2069
- const { reportId, agents, timeRangeHours, includeSessionFiles, includeDaemonLogs, includeWorkspaceSnapshot, uploadUrl, authToken } = msg;
2070
- console.log(`[Daemon] Collecting feedback for report ${reportId} (${agents.length} agents)`);
2071
- const dataDir = agentManager.getDataDir();
2072
- Promise.all(agents.map(
2073
- (agent) => collectAndUploadAgent({
2074
- agentId: agent.agentId,
2075
- reportAgentId: agent.reportAgentId,
2076
- runtime: agent.runtime,
2077
- sessionId: agent.sessionId,
2078
- uploadUrl,
2079
- authToken,
2080
- timeRangeHours,
2081
- includeSessionFiles,
2082
- includeDaemonLogs,
2083
- includeWorkspaceSnapshot: includeWorkspaceSnapshot ?? false,
2084
- dataDir
2085
- })
2086
- )).then((agentResults) => {
2087
- connection.send({ type: "machine:feedback:result", reportId, agentResults });
2088
- }).catch((err) => {
2089
- console.error(`[Daemon] Feedback collection failed for report ${reportId}:`, err);
2090
- const agentResults = agents.map((a) => ({
2091
- reportAgentId: a.reportAgentId,
2092
- status: "error",
2093
- error: err instanceof Error ? err.message : String(err)
2094
- }));
2095
- connection.send({ type: "machine:feedback:result", reportId, agentResults });
2096
- });
2097
- break;
2098
- }
2099
2257
  }
2100
2258
  },
2101
2259
  onConnect: () => {
2102
2260
  const runtimes = detectRuntimes();
2103
- console.log(`[Daemon] Detected runtimes: ${runtimes.join(", ") || "none"}`);
2261
+ logger.info(`[Daemon] Detected runtimes: ${runtimes.join(", ") || "none"}`);
2104
2262
  connection.send({
2105
2263
  type: "ready",
2106
2264
  capabilities: ["agent:start", "agent:stop", "agent:deliver", "workspace:files"],
@@ -2112,24 +2270,23 @@ connection = new DaemonConnection({
2112
2270
  });
2113
2271
  for (const agentId of agentManager.getRunningAgentIds()) {
2114
2272
  const sessionId = agentManager.getAgentSessionId(agentId);
2273
+ const launchId = agentManager.getAgentLaunchId(agentId);
2115
2274
  if (sessionId) {
2116
- connection.send({ type: "agent:session", agentId, sessionId });
2275
+ connection.send({ type: "agent:session", agentId, sessionId, launchId: launchId || void 0 });
2117
2276
  }
2118
2277
  }
2119
- for (const { agentId, sessionId } of agentManager.getIdleAgentSessionIds()) {
2120
- connection.send({ type: "agent:session", agentId, sessionId });
2278
+ for (const { agentId, sessionId, launchId } of agentManager.getIdleAgentSessionIds()) {
2279
+ connection.send({ type: "agent:session", agentId, sessionId, launchId: launchId || void 0 });
2121
2280
  }
2122
2281
  },
2123
2282
  onDisconnect: () => {
2124
- console.log("[Daemon] Lost connection \u2014 agents continue running locally");
2283
+ logger.warn("[Daemon] Lost connection \u2014 agents continue running locally");
2125
2284
  }
2126
2285
  });
2127
- initLogFile().catch(() => {
2128
- });
2129
- console.log("[Slock Daemon] Starting...");
2286
+ logger.info("[Slock Daemon] Starting...");
2130
2287
  connection.connect();
2131
2288
  var shutdown = async () => {
2132
- console.log("[Slock Daemon] Shutting down...");
2289
+ logger.info("[Slock Daemon] Shutting down...");
2133
2290
  await agentManager.stopAll();
2134
2291
  connection.disconnect();
2135
2292
  process.exit(0);