@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/chat-bridge.js +4 -4
- package/dist/index.js +495 -338
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,16 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1155
|
+
const createDriver = driverFactories[runtimeId];
|
|
1156
|
+
const driver = createDriver?.();
|
|
888
1157
|
if (!driver) {
|
|
889
|
-
throw new Error(`Unknown runtime: ${runtimeId}. Available: ${Object.keys(
|
|
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 =
|
|
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
|
-
|
|
1286
|
+
logger.info(`[Agent ${agentId}] Start ignored (already running)`);
|
|
995
1287
|
return;
|
|
996
1288
|
}
|
|
997
1289
|
if (this.agentsStarting.has(agentId)) {
|
|
998
|
-
|
|
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 =
|
|
1296
|
+
const agentDataDir = path4.join(this.dataDir, agentId);
|
|
1005
1297
|
await mkdir(agentDataDir, { recursive: true });
|
|
1006
|
-
const memoryMdPath =
|
|
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(
|
|
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 (
|
|
1035
|
-
const channelLabel = wakeMessage
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1218
|
-
this.
|
|
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
|
-
|
|
1224
|
-
this.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
1551
|
+
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
1255
1552
|
this.broadcastActivity(agentId, "offline", "Stopped");
|
|
1256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
1614
|
+
const agentDataDir = path4.join(this.dataDir, agentId);
|
|
1318
1615
|
try {
|
|
1319
1616
|
await rm(agentDataDir, { recursive: true, force: true });
|
|
1320
|
-
|
|
1617
|
+
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
1321
1618
|
} catch (err) {
|
|
1322
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1691
|
+
const targetDir = path4.join(this.dataDir, directoryName);
|
|
1395
1692
|
try {
|
|
1396
1693
|
await rm(targetDir, { recursive: true, force: true });
|
|
1397
|
-
|
|
1694
|
+
logger.info(`[Workspace] Deleted directory: ${targetDir}`);
|
|
1398
1695
|
return true;
|
|
1399
1696
|
} catch (err) {
|
|
1400
|
-
|
|
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 =
|
|
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 =
|
|
1415
|
-
if (!resolved.startsWith(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 =
|
|
1424
|
-
const resolved =
|
|
1425
|
-
if (!resolved.startsWith(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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1956
|
+
const nextMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
1622
1957
|
this.broadcastActivity(agentId, "working", "Message received");
|
|
1623
|
-
this.
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
1666
|
-
|
|
1667
|
-
const
|
|
1668
|
-
|
|
1669
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1698
|
-
const relativePath =
|
|
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:
|
|
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 =
|
|
1862
|
-
var chatBridgePath =
|
|
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 =
|
|
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
|
-
|
|
2186
|
+
logger.info(`[Daemon] Received ${msg.type}${summary ? ` ${summary}` : ""}`);
|
|
1998
2187
|
switch (msg.type) {
|
|
1999
2188
|
case "agent:start":
|
|
2000
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2198
|
+
logger.info(`[Agent ${msg.agentId}] Stop requested`);
|
|
2010
2199
|
agentManager.stopAgent(msg.agentId);
|
|
2011
2200
|
break;
|
|
2012
2201
|
case "agent:reset-workspace":
|
|
2013
|
-
|
|
2202
|
+
logger.info(`[Agent ${msg.agentId}] Workspace reset requested`);
|
|
2014
2203
|
agentManager.resetWorkspace(msg.agentId);
|
|
2015
2204
|
break;
|
|
2016
2205
|
case "agent:deliver":
|
|
2017
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2283
|
+
logger.warn("[Daemon] Lost connection \u2014 agents continue running locally");
|
|
2125
2284
|
}
|
|
2126
2285
|
});
|
|
2127
|
-
|
|
2128
|
-
});
|
|
2129
|
-
console.log("[Slock Daemon] Starting...");
|
|
2286
|
+
logger.info("[Slock Daemon] Starting...");
|
|
2130
2287
|
connection.connect();
|
|
2131
2288
|
var shutdown = async () => {
|
|
2132
|
-
|
|
2289
|
+
logger.info("[Slock Daemon] Shutting down...");
|
|
2133
2290
|
await agentManager.stopAll();
|
|
2134
2291
|
connection.disconnect();
|
|
2135
2292
|
process.exit(0);
|