@slock-ai/daemon 0.30.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 -128
- package/package.json +4 -4
package/dist/chat-bridge.js
CHANGED
|
@@ -470,7 +470,7 @@ ${formatted}`
|
|
|
470
470
|
);
|
|
471
471
|
server.tool(
|
|
472
472
|
"create_tasks",
|
|
473
|
-
"Create one or more new
|
|
473
|
+
"Create one or more new task-messages in a top-level channel or DM. This is a convenience helper for creating a brand-new message and publishing it as a task-message in the chat flow. Thread messages cannot become tasks. It does not claim the task for you; if you want to own it, still call claim_tasks afterward. It is not a separate task board outside the chat flow. Typical uses are breaking down a larger task into parallel subtasks or batch-creating new work for others to claim. Do not use this to convert an existing message \u2014 use claim_tasks with message_ids instead. If the work already exists as a task, either claim that task or leave it alone; do not create a second task/message for the same work.",
|
|
474
474
|
{
|
|
475
475
|
channel: z.string().describe("The channel to create tasks in \u2014 e.g. '#engineering'"),
|
|
476
476
|
tasks: z.array(
|
|
@@ -519,13 +519,13 @@ server.tool(
|
|
|
519
519
|
"claim_tasks",
|
|
520
520
|
`Claim tasks so you are assigned to work on them. Two modes:
|
|
521
521
|
1. By task number: claim existing tasks shown in list_tasks. Use task_numbers=[1, 3].
|
|
522
|
-
2. By message ID: convert a regular message into a task and claim it. Use message_ids=["a1b2c3d4"]. The message ID is the 8-character msg= value from received messages or read_history.
|
|
522
|
+
2. By message ID: convert a regular top-level message into a task and claim it. Use message_ids=["a1b2c3d4"]. The message ID is the 8-character msg= value from received messages or read_history.
|
|
523
523
|
|
|
524
|
-
If a task is in "todo" status, claiming auto-advances it to "in_progress". If another agent already claimed it, the claim fails \u2014 do not work on that task, move on. Always claim before starting any work to prevent duplicate effort.`,
|
|
524
|
+
Thread messages cannot be claimed or converted into tasks. If a task is in "todo" status, claiming auto-advances it to "in_progress". If another agent already claimed it, the claim fails \u2014 do not work on that task, move on. Always claim before starting any work to prevent duplicate effort.`,
|
|
525
525
|
{
|
|
526
526
|
channel: z.string().describe("The channel \u2014 e.g. '#engineering'"),
|
|
527
527
|
task_numbers: z.array(z.number()).optional().describe("Task numbers to claim (from list_tasks output, e.g. [1, 3])"),
|
|
528
|
-
message_ids: z.array(z.string()).optional().describe("Message IDs or short ID prefixes (the 8-char msg= value, e.g. ['a1b2c3d4']). Converts
|
|
528
|
+
message_ids: z.array(z.string()).optional().describe("Message IDs or short ID prefixes (the 8-char msg= value, e.g. ['a1b2c3d4']). Converts a regular top-level message to a task and claims it. Thread messages are not allowed.")
|
|
529
529
|
},
|
|
530
530
|
async ({ channel, task_numbers, message_ids }) => {
|
|
531
531
|
try {
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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";
|
|
@@ -10,6 +10,33 @@ import { fileURLToPath } from "url";
|
|
|
10
10
|
|
|
11
11
|
// src/connection.ts
|
|
12
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
|
|
13
40
|
var systemClock = {
|
|
14
41
|
now: () => Date.now(),
|
|
15
42
|
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
@@ -46,7 +73,7 @@ var DaemonConnection = class {
|
|
|
46
73
|
this.reconnectTimer = null;
|
|
47
74
|
}
|
|
48
75
|
if (this.ws) {
|
|
49
|
-
|
|
76
|
+
logger.info("[Daemon] Disconnect requested");
|
|
50
77
|
this.ws.close();
|
|
51
78
|
this.ws = null;
|
|
52
79
|
}
|
|
@@ -59,7 +86,7 @@ var DaemonConnection = class {
|
|
|
59
86
|
const now = this.clock.now();
|
|
60
87
|
if (now - this.lastDroppedSendLogAt > 5e3) {
|
|
61
88
|
this.lastDroppedSendLogAt = now;
|
|
62
|
-
|
|
89
|
+
logger.warn(`[Daemon] Dropping outbound message while disconnected: ${msg.type}`);
|
|
63
90
|
}
|
|
64
91
|
}
|
|
65
92
|
get connected() {
|
|
@@ -69,13 +96,13 @@ var DaemonConnection = class {
|
|
|
69
96
|
if (!this.shouldConnect) return;
|
|
70
97
|
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) return;
|
|
71
98
|
const wsUrl = this.options.serverUrl.replace(/^http/, "ws") + `/daemon/connect?key=${this.options.apiKey}`;
|
|
72
|
-
|
|
99
|
+
logger.info(`[Daemon] Connecting to ${this.options.serverUrl}...`);
|
|
73
100
|
const ws = this.options.wsFactory ? this.options.wsFactory(wsUrl) : new WebSocket(wsUrl);
|
|
74
101
|
this.ws = ws;
|
|
75
102
|
ws.on("open", () => {
|
|
76
103
|
if (this.ws !== ws) return;
|
|
77
104
|
if (!this.shouldConnect) return;
|
|
78
|
-
|
|
105
|
+
logger.info("[Daemon] Connected to server");
|
|
79
106
|
this.reconnectAttempt = 0;
|
|
80
107
|
this.reconnectDelay = this.options.minReconnectDelayMs ?? 1e3;
|
|
81
108
|
this.resetWatchdog();
|
|
@@ -88,7 +115,7 @@ var DaemonConnection = class {
|
|
|
88
115
|
const msg = JSON.parse(data.toString());
|
|
89
116
|
this.options.onMessage(msg);
|
|
90
117
|
} catch (err) {
|
|
91
|
-
|
|
118
|
+
logger.error("[Daemon] Invalid message from server", err);
|
|
92
119
|
}
|
|
93
120
|
});
|
|
94
121
|
ws.on("close", (code, reasonBuffer) => {
|
|
@@ -96,7 +123,7 @@ var DaemonConnection = class {
|
|
|
96
123
|
this.ws = null;
|
|
97
124
|
this.clearWatchdog();
|
|
98
125
|
const reason = reasonBuffer.toString("utf8");
|
|
99
|
-
|
|
126
|
+
logger.warn(
|
|
100
127
|
`[Daemon] Disconnected from server (code=${code}, reason=${JSON.stringify(reason)}, reconnecting=${this.shouldConnect})`
|
|
101
128
|
);
|
|
102
129
|
this.options.onDisconnect();
|
|
@@ -104,14 +131,14 @@ var DaemonConnection = class {
|
|
|
104
131
|
});
|
|
105
132
|
ws.on("error", (err) => {
|
|
106
133
|
if (this.ws !== ws) return;
|
|
107
|
-
|
|
134
|
+
logger.error(`[Daemon] WebSocket error: ${err.message}`);
|
|
108
135
|
});
|
|
109
136
|
}
|
|
110
137
|
scheduleReconnect() {
|
|
111
138
|
if (!this.shouldConnect) return;
|
|
112
139
|
if (this.reconnectTimer) return;
|
|
113
140
|
this.reconnectAttempt += 1;
|
|
114
|
-
|
|
141
|
+
logger.info(`[Daemon] Reconnecting to server in ${this.reconnectDelay}ms (attempt ${this.reconnectAttempt})`);
|
|
115
142
|
this.reconnectTimer = this.clock.setTimeout(() => {
|
|
116
143
|
this.reconnectTimer = null;
|
|
117
144
|
this.doConnect();
|
|
@@ -122,7 +149,7 @@ var DaemonConnection = class {
|
|
|
122
149
|
this.clearWatchdog();
|
|
123
150
|
const ms = this.options.inboundWatchdogMs ?? INBOUND_WATCHDOG_MS;
|
|
124
151
|
this.watchdogTimer = this.clock.setTimeout(() => {
|
|
125
|
-
|
|
152
|
+
logger.warn(`[Daemon] No inbound traffic for ${ms / 1e3}s \u2014 forcing reconnect`);
|
|
126
153
|
try {
|
|
127
154
|
this.ws?.terminate();
|
|
128
155
|
} catch {
|
|
@@ -139,7 +166,7 @@ var DaemonConnection = class {
|
|
|
139
166
|
|
|
140
167
|
// src/agentProcessManager.ts
|
|
141
168
|
import { mkdir, writeFile, access, readdir, stat, readFile, rm } from "fs/promises";
|
|
142
|
-
import
|
|
169
|
+
import path4 from "path";
|
|
143
170
|
import os from "os";
|
|
144
171
|
|
|
145
172
|
// src/drivers/claude.ts
|
|
@@ -182,7 +209,7 @@ You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
|
182
209
|
3. **${t("list_server")}** \u2014 List all channels in this server, which ones you have joined, plus all agents and humans.
|
|
183
210
|
4. **${t("read_history")}** \u2014 Read past messages from a channel or DM.
|
|
184
211
|
5. **${t("list_tasks")}** \u2014 View a channel's task board.
|
|
185
|
-
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).
|
|
186
213
|
7. **${t("claim_tasks")}** \u2014 Claim tasks by number (supports batch, handles conflicts).
|
|
187
214
|
8. **${t("unclaim_task")}** \u2014 Release your claim on a task.
|
|
188
215
|
9. **${t("update_task_status")}** \u2014 Change a task's status (e.g. to in_review or done).
|
|
@@ -266,6 +293,8 @@ When someone sends a message that asks you to do something \u2014 fix a bug, wri
|
|
|
266
293
|
- A regular message (no task suffix): \`@Alice: Can someone look into the login bug?\`
|
|
267
294
|
- A system notification about task changes: \`\u{1F4CB} Alice converted a message to task #3 "Fix the login bug"\`
|
|
268
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
|
+
|
|
269
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.
|
|
270
299
|
|
|
271
300
|
**Status flow:** \`todo\` \u2192 \`in_progress\` \u2192 \`in_review\` \u2192 \`done\`
|
|
@@ -279,6 +308,21 @@ When someone sends a message that asks you to do something \u2014 fix a bug, wri
|
|
|
279
308
|
4. When done, set status to \`in_review\` so a human can validate
|
|
280
309
|
5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
|
|
281
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
|
+
|
|
282
326
|
### Splitting tasks for parallel execution
|
|
283
327
|
|
|
284
328
|
When you need to break down a large task into subtasks, structure them so agents can work **in parallel**:
|
|
@@ -552,7 +596,7 @@ var ClaudeDriver = class {
|
|
|
552
596
|
}
|
|
553
597
|
return events;
|
|
554
598
|
}
|
|
555
|
-
encodeStdinMessage(text, sessionId) {
|
|
599
|
+
encodeStdinMessage(text, sessionId, _opts) {
|
|
556
600
|
return JSON.stringify({
|
|
557
601
|
type: "user",
|
|
558
602
|
message: {
|
|
@@ -807,7 +851,7 @@ var CodexDriver = class {
|
|
|
807
851
|
}
|
|
808
852
|
return events;
|
|
809
853
|
}
|
|
810
|
-
encodeStdinMessage(_text, _sessionId) {
|
|
854
|
+
encodeStdinMessage(_text, _sessionId, _opts) {
|
|
811
855
|
return null;
|
|
812
856
|
}
|
|
813
857
|
buildSystemPrompt(config, _agentId) {
|
|
@@ -877,21 +921,247 @@ var CodexDriver = class {
|
|
|
877
921
|
}
|
|
878
922
|
};
|
|
879
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
|
+
|
|
880
1148
|
// src/drivers/index.ts
|
|
881
|
-
var
|
|
882
|
-
claude: new ClaudeDriver(),
|
|
883
|
-
codex: new CodexDriver()
|
|
1149
|
+
var driverFactories = {
|
|
1150
|
+
claude: () => new ClaudeDriver(),
|
|
1151
|
+
codex: () => new CodexDriver(),
|
|
1152
|
+
kimi: () => new KimiDriver()
|
|
884
1153
|
};
|
|
885
1154
|
function getDriver(runtimeId) {
|
|
886
|
-
const
|
|
1155
|
+
const createDriver = driverFactories[runtimeId];
|
|
1156
|
+
const driver = createDriver?.();
|
|
887
1157
|
if (!driver) {
|
|
888
|
-
throw new Error(`Unknown runtime: ${runtimeId}. Available: ${Object.keys(
|
|
1158
|
+
throw new Error(`Unknown runtime: ${runtimeId}. Available: ${Object.keys(driverFactories).join(", ")}`);
|
|
889
1159
|
}
|
|
890
1160
|
return driver;
|
|
891
1161
|
}
|
|
892
1162
|
|
|
893
1163
|
// src/agentProcessManager.ts
|
|
894
|
-
var DATA_DIR =
|
|
1164
|
+
var DATA_DIR = path4.join(os.homedir(), ".slock", "agents");
|
|
895
1165
|
function toLocalTime(iso) {
|
|
896
1166
|
const d = new Date(iso);
|
|
897
1167
|
if (isNaN(d.getTime())) return iso;
|
|
@@ -901,6 +1171,28 @@ function toLocalTime(iso) {
|
|
|
901
1171
|
function formatChannelLabel(message) {
|
|
902
1172
|
return message.channel_type === "dm" ? `DM:@${message.channel_name}` : `#${message.channel_name}`;
|
|
903
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
|
+
}
|
|
904
1196
|
function buildUnreadSummary(messages, excludeChannel) {
|
|
905
1197
|
const summary = /* @__PURE__ */ new Map();
|
|
906
1198
|
for (const message of messages) {
|
|
@@ -911,6 +1203,7 @@ function buildUnreadSummary(messages, excludeChannel) {
|
|
|
911
1203
|
return summary.size > 0 ? Object.fromEntries(summary) : void 0;
|
|
912
1204
|
}
|
|
913
1205
|
var MAX_TRAJECTORY_TEXT = 2e3;
|
|
1206
|
+
var TRAJECTORY_COALESCE_MS = 350;
|
|
914
1207
|
var ACTIVITY_HEARTBEAT_MS = 6e4;
|
|
915
1208
|
var MAX_STDOUT_LINES = 8;
|
|
916
1209
|
var MAX_STDOUT_LINE_LENGTH = 240;
|
|
@@ -988,21 +1281,21 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
988
1281
|
this.dataDir = opts.dataDir || DATA_DIR;
|
|
989
1282
|
this.driverResolver = opts.driverResolver || getDriver;
|
|
990
1283
|
}
|
|
991
|
-
async startAgent(agentId, config, wakeMessage, unreadSummary, resumePrompt) {
|
|
1284
|
+
async startAgent(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
|
|
992
1285
|
if (this.agents.has(agentId)) {
|
|
993
|
-
|
|
1286
|
+
logger.info(`[Agent ${agentId}] Start ignored (already running)`);
|
|
994
1287
|
return;
|
|
995
1288
|
}
|
|
996
1289
|
if (this.agentsStarting.has(agentId)) {
|
|
997
|
-
|
|
1290
|
+
logger.info(`[Agent ${agentId}] Start ignored (startup in progress)`);
|
|
998
1291
|
return;
|
|
999
1292
|
}
|
|
1000
1293
|
this.agentsStarting.add(agentId);
|
|
1001
1294
|
try {
|
|
1002
1295
|
const driver = this.driverResolver(config.runtime || "claude");
|
|
1003
|
-
const agentDataDir =
|
|
1296
|
+
const agentDataDir = path4.join(this.dataDir, agentId);
|
|
1004
1297
|
await mkdir(agentDataDir, { recursive: true });
|
|
1005
|
-
const memoryMdPath =
|
|
1298
|
+
const memoryMdPath = path4.join(agentDataDir, "MEMORY.md");
|
|
1006
1299
|
try {
|
|
1007
1300
|
await access(memoryMdPath);
|
|
1008
1301
|
} catch {
|
|
@@ -1020,7 +1313,7 @@ ${config.description || "No role defined yet."}
|
|
|
1020
1313
|
`;
|
|
1021
1314
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
1022
1315
|
}
|
|
1023
|
-
await mkdir(
|
|
1316
|
+
await mkdir(path4.join(agentDataDir, "notes"), { recursive: true });
|
|
1024
1317
|
const isResume = !!config.sessionId;
|
|
1025
1318
|
let prompt;
|
|
1026
1319
|
if (isResume && resumePrompt) {
|
|
@@ -1030,14 +1323,11 @@ ${config.description || "No role defined yet."}
|
|
|
1030
1323
|
|
|
1031
1324
|
Note: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call check_messages to check for messages.`;
|
|
1032
1325
|
}
|
|
1033
|
-
} else if (
|
|
1034
|
-
const channelLabel = wakeMessage
|
|
1035
|
-
const senderPrefix = wakeMessage.sender_type === "agent" ? "(agent) " : "";
|
|
1036
|
-
const time = wakeMessage.timestamp ? ` (${toLocalTime(wakeMessage.timestamp)})` : "";
|
|
1037
|
-
const formatted = `[${channelLabel}]${time} ${senderPrefix}@${wakeMessage.sender_name}: ${wakeMessage.content}`;
|
|
1326
|
+
} else if (wakeMessage) {
|
|
1327
|
+
const channelLabel = formatChannelLabel(wakeMessage);
|
|
1038
1328
|
prompt = `New message received:
|
|
1039
1329
|
|
|
1040
|
-
${
|
|
1330
|
+
${formatIncomingMessage(wakeMessage)}`;
|
|
1041
1331
|
if (unreadSummary && Object.keys(unreadSummary).length > 0) {
|
|
1042
1332
|
const otherUnread = Object.entries(unreadSummary).filter(([key]) => key !== channelLabel);
|
|
1043
1333
|
if (otherUnread.length > 0) {
|
|
@@ -1096,6 +1386,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1096
1386
|
inbox: this.startingInboxes.get(agentId) || [],
|
|
1097
1387
|
config,
|
|
1098
1388
|
sessionId: config.sessionId || null,
|
|
1389
|
+
launchId: launchId || null,
|
|
1099
1390
|
isIdle: false,
|
|
1100
1391
|
notificationTimer: null,
|
|
1101
1392
|
pendingNotificationCount: 0,
|
|
@@ -1107,7 +1398,8 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1107
1398
|
lastRuntimeError: null,
|
|
1108
1399
|
spawnError: null,
|
|
1109
1400
|
exitCode: null,
|
|
1110
|
-
exitSignal: null
|
|
1401
|
+
exitSignal: null,
|
|
1402
|
+
pendingTrajectory: null
|
|
1111
1403
|
};
|
|
1112
1404
|
this.startingInboxes.delete(agentId);
|
|
1113
1405
|
this.agents.set(agentId, agentProcess);
|
|
@@ -1138,12 +1430,12 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1138
1430
|
if (current) {
|
|
1139
1431
|
current.recentStderr = pushRecentStderr(current.recentStderr, text);
|
|
1140
1432
|
}
|
|
1141
|
-
|
|
1433
|
+
logger.error(`[Agent ${agentId} stderr]: ${text}`);
|
|
1142
1434
|
});
|
|
1143
1435
|
proc.on("error", (err) => {
|
|
1144
1436
|
const current = this.agents.get(agentId);
|
|
1145
1437
|
if (current) current.spawnError = err.message;
|
|
1146
|
-
|
|
1438
|
+
logger.error(`[Agent ${agentId}] Process error: ${err.message}`);
|
|
1147
1439
|
});
|
|
1148
1440
|
proc.on("exit", (code, signal) => {
|
|
1149
1441
|
const current = this.agents.get(agentId);
|
|
@@ -1151,7 +1443,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1151
1443
|
current.exitCode = code;
|
|
1152
1444
|
current.exitSignal = signal;
|
|
1153
1445
|
}
|
|
1154
|
-
|
|
1446
|
+
logger.info(`[Agent ${agentId}] Process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
|
|
1155
1447
|
});
|
|
1156
1448
|
proc.on("close", (code, signal) => {
|
|
1157
1449
|
if (this.agents.has(agentId)) {
|
|
@@ -1160,6 +1452,9 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1160
1452
|
if (ap.notificationTimer) {
|
|
1161
1453
|
clearTimeout(ap.notificationTimer);
|
|
1162
1454
|
}
|
|
1455
|
+
if (ap.pendingTrajectory?.timer) {
|
|
1456
|
+
clearTimeout(ap.pendingTrajectory.timer);
|
|
1457
|
+
}
|
|
1163
1458
|
if (ap.activityHeartbeat) {
|
|
1164
1459
|
clearInterval(ap.activityHeartbeat);
|
|
1165
1460
|
}
|
|
@@ -1170,19 +1465,21 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1170
1465
|
const queuedWakeMessage = !ap.driver.supportsStdinNotification ? ap.inbox.shift() : void 0;
|
|
1171
1466
|
const unreadSummary2 = queuedWakeMessage ? buildUnreadSummary(ap.inbox, formatChannelLabel(queuedWakeMessage)) : void 0;
|
|
1172
1467
|
if (queuedWakeMessage) {
|
|
1173
|
-
|
|
1468
|
+
logger.info(`[Agent ${agentId}] Turn completed; restarting immediately for queued message`);
|
|
1174
1469
|
const nextConfig = { ...ap.config, sessionId: ap.sessionId };
|
|
1175
1470
|
this.idleAgentConfigs.set(agentId, {
|
|
1176
1471
|
config: nextConfig,
|
|
1177
|
-
sessionId: ap.sessionId
|
|
1472
|
+
sessionId: ap.sessionId,
|
|
1473
|
+
launchId: ap.launchId
|
|
1178
1474
|
});
|
|
1179
1475
|
this.broadcastActivity(agentId, "working", "Message received");
|
|
1180
1476
|
this.idleAgentConfigs.delete(agentId);
|
|
1181
|
-
this.startAgent(agentId, nextConfig, queuedWakeMessage, unreadSummary2).catch((err) => {
|
|
1182
|
-
|
|
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);
|
|
1183
1479
|
this.idleAgentConfigs.set(agentId, {
|
|
1184
1480
|
config: nextConfig,
|
|
1185
|
-
sessionId: ap.sessionId
|
|
1481
|
+
sessionId: ap.sessionId,
|
|
1482
|
+
launchId: ap.launchId
|
|
1186
1483
|
});
|
|
1187
1484
|
this.broadcastActivity(agentId, "online", "Process idle");
|
|
1188
1485
|
});
|
|
@@ -1190,10 +1487,11 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1190
1487
|
}
|
|
1191
1488
|
this.idleAgentConfigs.set(agentId, {
|
|
1192
1489
|
config: { ...ap.config, sessionId: ap.sessionId },
|
|
1193
|
-
sessionId: ap.sessionId
|
|
1490
|
+
sessionId: ap.sessionId,
|
|
1491
|
+
launchId: ap.launchId
|
|
1194
1492
|
});
|
|
1195
1493
|
if (!ap.driver.supportsStdinNotification) {
|
|
1196
|
-
|
|
1494
|
+
logger.info(`[Agent ${agentId}] Turn completed; cached idle state for future restart`);
|
|
1197
1495
|
}
|
|
1198
1496
|
this.broadcastActivity(agentId, "online", "Process idle");
|
|
1199
1497
|
} else {
|
|
@@ -1203,7 +1501,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1203
1501
|
if (isMissingResumeSession(ap)) {
|
|
1204
1502
|
const staleSessionId = ap.sessionId;
|
|
1205
1503
|
const restartConfig = { ...ap.config, sessionId: null };
|
|
1206
|
-
|
|
1504
|
+
logger.warn(
|
|
1207
1505
|
`[Agent ${agentId}] Stored Claude session ${staleSessionId} is unavailable locally; falling back to cold start`
|
|
1208
1506
|
);
|
|
1209
1507
|
this.broadcastActivity(
|
|
@@ -1212,20 +1510,20 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1212
1510
|
"Stored Claude session missing; cold-starting a new session\u2026",
|
|
1213
1511
|
[{ kind: "text", text: `Stored Claude session ${staleSessionId} was not found locally. Falling back to a cold start.` }]
|
|
1214
1512
|
);
|
|
1215
|
-
this.startAgent(agentId, restartConfig).catch((err) => {
|
|
1216
|
-
|
|
1217
|
-
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);
|
|
1218
1516
|
this.broadcastActivity(agentId, "offline", `Crashed (${summary})`);
|
|
1219
1517
|
});
|
|
1220
1518
|
return;
|
|
1221
1519
|
}
|
|
1222
|
-
|
|
1223
|
-
this.
|
|
1520
|
+
logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
|
|
1521
|
+
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
1224
1522
|
this.broadcastActivity(agentId, "offline", `Crashed (${summary})`);
|
|
1225
1523
|
}
|
|
1226
1524
|
}
|
|
1227
1525
|
});
|
|
1228
|
-
this.
|
|
1526
|
+
this.sendAgentStatus(agentId, "active", launchId || null);
|
|
1229
1527
|
this.broadcastActivity(agentId, "working", "Starting\u2026");
|
|
1230
1528
|
} catch (err) {
|
|
1231
1529
|
this.agentsStarting.delete(agentId);
|
|
@@ -1237,7 +1535,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1237
1535
|
const ap = this.agents.get(agentId);
|
|
1238
1536
|
if (!ap) {
|
|
1239
1537
|
if (!silent) {
|
|
1240
|
-
|
|
1538
|
+
logger.info(`[Agent ${agentId}] Stop requested but no running process was found`);
|
|
1241
1539
|
}
|
|
1242
1540
|
return;
|
|
1243
1541
|
}
|
|
@@ -1250,15 +1548,15 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1250
1548
|
this.agents.delete(agentId);
|
|
1251
1549
|
ap.process.kill("SIGTERM");
|
|
1252
1550
|
if (!silent) {
|
|
1253
|
-
this.
|
|
1551
|
+
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
1254
1552
|
this.broadcastActivity(agentId, "offline", "Stopped");
|
|
1255
|
-
|
|
1553
|
+
logger.info(`[Agent ${agentId}] Stopped by request`);
|
|
1256
1554
|
}
|
|
1257
1555
|
if (wait) {
|
|
1258
1556
|
await new Promise((resolve) => {
|
|
1259
1557
|
const forceKillTimer = setTimeout(() => {
|
|
1260
1558
|
if (!silent) {
|
|
1261
|
-
|
|
1559
|
+
logger.warn(`[Agent ${agentId}] Stop timed out; force killing`);
|
|
1262
1560
|
}
|
|
1263
1561
|
try {
|
|
1264
1562
|
ap.process.kill("SIGKILL");
|
|
@@ -1288,10 +1586,10 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1288
1586
|
}
|
|
1289
1587
|
const cached = this.idleAgentConfigs.get(agentId);
|
|
1290
1588
|
if (cached) {
|
|
1291
|
-
|
|
1589
|
+
logger.info(`[Agent ${agentId}] Starting from idle state for new message`);
|
|
1292
1590
|
this.idleAgentConfigs.delete(agentId);
|
|
1293
|
-
this.startAgent(agentId, cached.config, message).catch((err) => {
|
|
1294
|
-
|
|
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);
|
|
1295
1593
|
});
|
|
1296
1594
|
}
|
|
1297
1595
|
return;
|
|
@@ -1299,7 +1597,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1299
1597
|
if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
1300
1598
|
ap.isIdle = false;
|
|
1301
1599
|
this.broadcastActivity(agentId, "working", "Message received");
|
|
1302
|
-
this.
|
|
1600
|
+
this.deliverMessagesViaStdin(agentId, ap, [message], "idle");
|
|
1303
1601
|
return;
|
|
1304
1602
|
}
|
|
1305
1603
|
ap.inbox.push(message);
|
|
@@ -1313,12 +1611,12 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1313
1611
|
}
|
|
1314
1612
|
}
|
|
1315
1613
|
async resetWorkspace(agentId) {
|
|
1316
|
-
const agentDataDir =
|
|
1614
|
+
const agentDataDir = path4.join(this.dataDir, agentId);
|
|
1317
1615
|
try {
|
|
1318
1616
|
await rm(agentDataDir, { recursive: true, force: true });
|
|
1319
|
-
|
|
1617
|
+
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
1320
1618
|
} catch (err) {
|
|
1321
|
-
|
|
1619
|
+
logger.error(`[Agent ${agentId}] Workspace reset failed`, err);
|
|
1322
1620
|
}
|
|
1323
1621
|
}
|
|
1324
1622
|
async stopAll() {
|
|
@@ -1332,10 +1630,13 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1332
1630
|
getAgentSessionId(agentId) {
|
|
1333
1631
|
return this.agents.get(agentId)?.sessionId ?? null;
|
|
1334
1632
|
}
|
|
1633
|
+
getAgentLaunchId(agentId) {
|
|
1634
|
+
return this.agents.get(agentId)?.launchId ?? null;
|
|
1635
|
+
}
|
|
1335
1636
|
getIdleAgentSessionIds() {
|
|
1336
1637
|
const result = [];
|
|
1337
|
-
for (const [agentId, { sessionId }] of this.idleAgentConfigs) {
|
|
1338
|
-
if (sessionId) result.push({ agentId, sessionId });
|
|
1638
|
+
for (const [agentId, { sessionId, launchId }] of this.idleAgentConfigs) {
|
|
1639
|
+
if (sessionId) result.push({ agentId, sessionId, launchId });
|
|
1339
1640
|
}
|
|
1340
1641
|
return result;
|
|
1341
1642
|
}
|
|
@@ -1350,14 +1651,14 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1350
1651
|
}
|
|
1351
1652
|
for (const entry of entries) {
|
|
1352
1653
|
if (!entry.isDirectory()) continue;
|
|
1353
|
-
const dirPath =
|
|
1654
|
+
const dirPath = path4.join(this.dataDir, entry.name);
|
|
1354
1655
|
try {
|
|
1355
1656
|
const dirContents = await readdir(dirPath, { withFileTypes: true });
|
|
1356
1657
|
let totalSize = 0;
|
|
1357
1658
|
let latestMtime = /* @__PURE__ */ new Date(0);
|
|
1358
1659
|
let fileCount = 0;
|
|
1359
1660
|
for (const item of dirContents) {
|
|
1360
|
-
const itemPath =
|
|
1661
|
+
const itemPath = path4.join(dirPath, item.name);
|
|
1361
1662
|
try {
|
|
1362
1663
|
const info = await stat(itemPath);
|
|
1363
1664
|
if (item.isFile()) {
|
|
@@ -1387,19 +1688,19 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1387
1688
|
if (directoryName.includes("/") || directoryName.includes("..") || directoryName.includes("\\")) {
|
|
1388
1689
|
return false;
|
|
1389
1690
|
}
|
|
1390
|
-
const targetDir =
|
|
1691
|
+
const targetDir = path4.join(this.dataDir, directoryName);
|
|
1391
1692
|
try {
|
|
1392
1693
|
await rm(targetDir, { recursive: true, force: true });
|
|
1393
|
-
|
|
1694
|
+
logger.info(`[Workspace] Deleted directory: ${targetDir}`);
|
|
1394
1695
|
return true;
|
|
1395
1696
|
} catch (err) {
|
|
1396
|
-
|
|
1697
|
+
logger.error(`[Workspace] Failed to delete directory ${targetDir}`, err);
|
|
1397
1698
|
return false;
|
|
1398
1699
|
}
|
|
1399
1700
|
}
|
|
1400
1701
|
// Workspace file browsing
|
|
1401
1702
|
async getFileTree(agentId, dirPath) {
|
|
1402
|
-
const agentDir =
|
|
1703
|
+
const agentDir = path4.join(this.dataDir, agentId);
|
|
1403
1704
|
try {
|
|
1404
1705
|
await stat(agentDir);
|
|
1405
1706
|
} catch {
|
|
@@ -1407,8 +1708,8 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1407
1708
|
}
|
|
1408
1709
|
let targetDir = agentDir;
|
|
1409
1710
|
if (dirPath) {
|
|
1410
|
-
const resolved =
|
|
1411
|
-
if (!resolved.startsWith(agentDir +
|
|
1711
|
+
const resolved = path4.resolve(agentDir, dirPath);
|
|
1712
|
+
if (!resolved.startsWith(agentDir + path4.sep) && resolved !== agentDir) {
|
|
1412
1713
|
return [];
|
|
1413
1714
|
}
|
|
1414
1715
|
targetDir = resolved;
|
|
@@ -1416,9 +1717,9 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1416
1717
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
1417
1718
|
}
|
|
1418
1719
|
async readFile(agentId, filePath) {
|
|
1419
|
-
const agentDir =
|
|
1420
|
-
const resolved =
|
|
1421
|
-
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) {
|
|
1422
1723
|
throw new Error("Access denied");
|
|
1423
1724
|
}
|
|
1424
1725
|
const info = await stat(resolved);
|
|
@@ -1442,7 +1743,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1442
1743
|
".sh",
|
|
1443
1744
|
".py"
|
|
1444
1745
|
]);
|
|
1445
|
-
const ext =
|
|
1746
|
+
const ext = path4.extname(resolved).toLowerCase();
|
|
1446
1747
|
if (!TEXT_EXTENSIONS.has(ext) && ext !== "") {
|
|
1447
1748
|
return { content: null, binary: true };
|
|
1448
1749
|
}
|
|
@@ -1469,13 +1770,13 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1469
1770
|
const agent = this.agents.get(agentId);
|
|
1470
1771
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
1471
1772
|
const home = os.homedir();
|
|
1472
|
-
const workspaceDir =
|
|
1773
|
+
const workspaceDir = path4.join(this.dataDir, agentId);
|
|
1473
1774
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
1474
1775
|
const globalResults = await Promise.all(
|
|
1475
|
-
paths.global.map((p) => this.scanSkillsDir(
|
|
1776
|
+
paths.global.map((p) => this.scanSkillsDir(path4.join(home, p)))
|
|
1476
1777
|
);
|
|
1477
1778
|
const workspaceResults = await Promise.all(
|
|
1478
|
-
paths.workspace.map((p) => this.scanSkillsDir(
|
|
1779
|
+
paths.workspace.map((p) => this.scanSkillsDir(path4.join(workspaceDir, p)))
|
|
1479
1780
|
);
|
|
1480
1781
|
const dedup = (skills) => {
|
|
1481
1782
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1504,7 +1805,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1504
1805
|
const skills = [];
|
|
1505
1806
|
for (const entry of entries) {
|
|
1506
1807
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
1507
|
-
const skillMd =
|
|
1808
|
+
const skillMd = path4.join(dir, entry.name, "SKILL.md");
|
|
1508
1809
|
try {
|
|
1509
1810
|
const content = await readFile(skillMd, "utf-8");
|
|
1510
1811
|
const skill = this.parseSkillMd(entry.name, content);
|
|
@@ -1515,7 +1816,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1515
1816
|
} else if (entry.name.endsWith(".md")) {
|
|
1516
1817
|
const cmdName = entry.name.replace(/\.md$/, "");
|
|
1517
1818
|
try {
|
|
1518
|
-
const content = await readFile(
|
|
1819
|
+
const content = await readFile(path4.join(dir, entry.name), "utf-8");
|
|
1519
1820
|
const skill = this.parseSkillMd(cmdName, content);
|
|
1520
1821
|
skill.sourcePath = dir;
|
|
1521
1822
|
skills.push(skill);
|
|
@@ -1558,14 +1859,20 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1558
1859
|
if (!hasToolStart) {
|
|
1559
1860
|
entries.push({ kind: "status", activity, detail });
|
|
1560
1861
|
}
|
|
1561
|
-
this.sendToServer({ type: "agent:activity", agentId, activity, detail, entries });
|
|
1862
|
+
this.sendToServer({ type: "agent:activity", agentId, activity, detail, entries, launchId: ap?.launchId || void 0 });
|
|
1562
1863
|
if (ap) {
|
|
1563
1864
|
ap.lastActivity = activity;
|
|
1564
1865
|
ap.lastActivityDetail = detail;
|
|
1565
1866
|
if (activity === "working" || activity === "thinking") {
|
|
1566
1867
|
if (!ap.activityHeartbeat) {
|
|
1567
1868
|
ap.activityHeartbeat = setInterval(() => {
|
|
1568
|
-
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
|
+
});
|
|
1569
1876
|
}, ACTIVITY_HEARTBEAT_MS);
|
|
1570
1877
|
}
|
|
1571
1878
|
} else {
|
|
@@ -1576,28 +1883,64 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1576
1883
|
}
|
|
1577
1884
|
}
|
|
1578
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
|
+
}
|
|
1579
1924
|
/** Handle a single ParsedEvent from any runtime driver */
|
|
1580
1925
|
handleParsedEvent(agentId, event, driver) {
|
|
1581
1926
|
const ap = this.agents.get(agentId);
|
|
1582
1927
|
switch (event.kind) {
|
|
1583
1928
|
case "session_init":
|
|
1584
1929
|
if (ap) ap.sessionId = event.sessionId;
|
|
1585
|
-
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId });
|
|
1930
|
+
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId, launchId: ap?.launchId || void 0 });
|
|
1586
1931
|
break;
|
|
1587
1932
|
case "thinking": {
|
|
1588
|
-
|
|
1589
|
-
const extra = text ? [{ kind: "thinking", text }] : [];
|
|
1590
|
-
this.broadcastActivity(agentId, "thinking", "", extra);
|
|
1933
|
+
this.queueTrajectoryText(agentId, "thinking", event.text);
|
|
1591
1934
|
if (ap) ap.isIdle = false;
|
|
1592
1935
|
break;
|
|
1593
1936
|
}
|
|
1594
1937
|
case "text": {
|
|
1595
|
-
|
|
1596
|
-
this.broadcastActivity(agentId, "thinking", "", [{ kind: "text", text }]);
|
|
1938
|
+
this.queueTrajectoryText(agentId, "text", event.text);
|
|
1597
1939
|
if (ap) ap.isIdle = false;
|
|
1598
1940
|
break;
|
|
1599
1941
|
}
|
|
1600
1942
|
case "tool_call": {
|
|
1943
|
+
this.flushPendingTrajectory(agentId);
|
|
1601
1944
|
const toolName = event.name;
|
|
1602
1945
|
const inputSummary = driver.summarizeToolInput(toolName, event.input);
|
|
1603
1946
|
const detail = toolName === `${driver.mcpToolPrefix}check_messages` ? "Checking messages\u2026" : toolName === `${driver.mcpToolPrefix}send_message` ? "Sending message\u2026" : driver.toolDisplayName(toolName);
|
|
@@ -1606,30 +1949,42 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1606
1949
|
break;
|
|
1607
1950
|
}
|
|
1608
1951
|
case "turn_end":
|
|
1952
|
+
this.flushPendingTrajectory(agentId);
|
|
1609
1953
|
if (ap) {
|
|
1610
1954
|
if (event.sessionId) ap.sessionId = event.sessionId;
|
|
1611
1955
|
if (ap.inbox.length > 0 && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
1612
|
-
const
|
|
1956
|
+
const nextMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
1613
1957
|
this.broadcastActivity(agentId, "working", "Message received");
|
|
1614
|
-
this.
|
|
1958
|
+
this.deliverMessagesViaStdin(agentId, ap, nextMessages, "idle");
|
|
1615
1959
|
} else {
|
|
1616
1960
|
ap.isIdle = true;
|
|
1617
1961
|
this.broadcastActivity(agentId, "online", "Idle");
|
|
1618
1962
|
}
|
|
1619
1963
|
}
|
|
1620
1964
|
if (event.sessionId) {
|
|
1621
|
-
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId });
|
|
1965
|
+
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId, launchId: ap?.launchId || void 0 });
|
|
1622
1966
|
}
|
|
1623
1967
|
break;
|
|
1624
1968
|
case "error": {
|
|
1969
|
+
this.flushPendingTrajectory(agentId);
|
|
1625
1970
|
if (ap) ap.lastRuntimeError = event.message;
|
|
1626
1971
|
const currentActivity = ap?.lastActivity || "working";
|
|
1627
1972
|
const currentDetail = ap?.lastActivityDetail || "";
|
|
1628
|
-
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
|
+
});
|
|
1629
1981
|
break;
|
|
1630
1982
|
}
|
|
1631
1983
|
}
|
|
1632
1984
|
}
|
|
1985
|
+
sendAgentStatus(agentId, status, launchId) {
|
|
1986
|
+
this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
|
|
1987
|
+
}
|
|
1633
1988
|
/** Send a batched notification to the agent via stdin about pending messages */
|
|
1634
1989
|
sendStdinNotification(agentId) {
|
|
1635
1990
|
const ap = this.agents.get(agentId);
|
|
@@ -1640,27 +1995,38 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1640
1995
|
if (count === 0) return;
|
|
1641
1996
|
if (ap.isIdle) return;
|
|
1642
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
|
+
}
|
|
1643
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.]`;
|
|
1644
|
-
|
|
1645
|
-
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" });
|
|
1646
2008
|
if (encoded) {
|
|
1647
2009
|
ap.process.stdin?.write(encoded + "\n");
|
|
1648
2010
|
}
|
|
1649
2011
|
}
|
|
1650
2012
|
/** Deliver a message to an agent via stdin, formatting it the same way as the MCP bridge */
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
const
|
|
1654
|
-
const time = message.timestamp ? ` (${toLocalTime(message.timestamp)})` : "";
|
|
1655
|
-
const formatted = `[${channelLabel}]${time} ${senderPrefix}@${message.sender_name}: ${message.content}`;
|
|
1656
|
-
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:
|
|
1657
2016
|
|
|
1658
|
-
${
|
|
2017
|
+
${formatIncomingMessage(messages[0])}
|
|
2018
|
+
|
|
2019
|
+
Respond as appropriate. Complete all your work before stopping.` : `New messages received:
|
|
2020
|
+
|
|
2021
|
+
${messages.map((message) => formatIncomingMessage(message)).join("\n")}
|
|
1659
2022
|
|
|
1660
2023
|
Respond as appropriate. Complete all your work before stopping.`;
|
|
1661
|
-
const encoded = ap.driver.encodeStdinMessage(prompt, ap.sessionId);
|
|
2024
|
+
const encoded = ap.driver.encodeStdinMessage(prompt, ap.sessionId, { mode });
|
|
1662
2025
|
if (encoded) {
|
|
1663
|
-
|
|
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
|
+
);
|
|
1664
2030
|
ap.process.stdin?.write(encoded + "\n");
|
|
1665
2031
|
}
|
|
1666
2032
|
}
|
|
@@ -1680,8 +2046,8 @@ Respond as appropriate. Complete all your work before stopping.`;
|
|
|
1680
2046
|
const nodes = [];
|
|
1681
2047
|
for (const entry of entries) {
|
|
1682
2048
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
1683
|
-
const fullPath =
|
|
1684
|
-
const relativePath =
|
|
2049
|
+
const fullPath = path4.join(dir, entry.name);
|
|
2050
|
+
const relativePath = path4.relative(rootDir, fullPath);
|
|
1685
2051
|
let info;
|
|
1686
2052
|
try {
|
|
1687
2053
|
info = await stat(fullPath);
|
|
@@ -1747,7 +2113,7 @@ var RUNTIMES = [
|
|
|
1747
2113
|
{ id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
|
|
1748
2114
|
{ id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
|
|
1749
2115
|
{ id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: false },
|
|
1750
|
-
{ id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported:
|
|
2116
|
+
{ id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true }
|
|
1751
2117
|
];
|
|
1752
2118
|
|
|
1753
2119
|
// src/index.ts
|
|
@@ -1801,12 +2167,12 @@ if (!serverUrl || !apiKey) {
|
|
|
1801
2167
|
console.error("Usage: slock-daemon --server-url <url> --api-key <key>");
|
|
1802
2168
|
process.exit(1);
|
|
1803
2169
|
}
|
|
1804
|
-
var __dirname =
|
|
1805
|
-
var chatBridgePath =
|
|
2170
|
+
var __dirname = path5.dirname(fileURLToPath(import.meta.url));
|
|
2171
|
+
var chatBridgePath = path5.resolve(__dirname, "chat-bridge.js");
|
|
1806
2172
|
try {
|
|
1807
2173
|
accessSync(chatBridgePath);
|
|
1808
2174
|
} catch {
|
|
1809
|
-
chatBridgePath =
|
|
2175
|
+
chatBridgePath = path5.resolve(__dirname, "chat-bridge.ts");
|
|
1810
2176
|
}
|
|
1811
2177
|
var connection;
|
|
1812
2178
|
var agentManager = new AgentProcessManager(chatBridgePath, (msg) => {
|
|
@@ -1817,27 +2183,27 @@ connection = new DaemonConnection({
|
|
|
1817
2183
|
apiKey,
|
|
1818
2184
|
onMessage: (msg) => {
|
|
1819
2185
|
const summary = summarizeIncomingMessage(msg);
|
|
1820
|
-
|
|
2186
|
+
logger.info(`[Daemon] Received ${msg.type}${summary ? ` ${summary}` : ""}`);
|
|
1821
2187
|
switch (msg.type) {
|
|
1822
2188
|
case "agent:start":
|
|
1823
|
-
|
|
1824
|
-
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) => {
|
|
1825
2191
|
const reason = err instanceof Error ? err.message : String(err);
|
|
1826
|
-
|
|
1827
|
-
connection.send({ type: "agent:status", agentId: msg.agentId, status: "inactive" });
|
|
1828
|
-
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 });
|
|
1829
2195
|
});
|
|
1830
2196
|
break;
|
|
1831
2197
|
case "agent:stop":
|
|
1832
|
-
|
|
2198
|
+
logger.info(`[Agent ${msg.agentId}] Stop requested`);
|
|
1833
2199
|
agentManager.stopAgent(msg.agentId);
|
|
1834
2200
|
break;
|
|
1835
2201
|
case "agent:reset-workspace":
|
|
1836
|
-
|
|
2202
|
+
logger.info(`[Agent ${msg.agentId}] Workspace reset requested`);
|
|
1837
2203
|
agentManager.resetWorkspace(msg.agentId);
|
|
1838
2204
|
break;
|
|
1839
2205
|
case "agent:deliver":
|
|
1840
|
-
|
|
2206
|
+
logger.info(`[Agent ${msg.agentId}] Delivery received (seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`);
|
|
1841
2207
|
agentManager.deliverMessage(msg.agentId, msg.message);
|
|
1842
2208
|
connection.send({ type: "agent:deliver:ack", agentId: msg.agentId, seq: msg.seq });
|
|
1843
2209
|
break;
|
|
@@ -1869,18 +2235,18 @@ connection = new DaemonConnection({
|
|
|
1869
2235
|
agentManager.listSkills(msg.agentId, msg.runtime).then(({ global, workspace }) => {
|
|
1870
2236
|
connection.send({ type: "agent:skills:list_result", agentId: msg.agentId, global, workspace });
|
|
1871
2237
|
}).catch((err) => {
|
|
1872
|
-
|
|
2238
|
+
logger.error(`[Daemon] Failed to list skills for ${msg.agentId}`, err);
|
|
1873
2239
|
connection.send({ type: "agent:skills:list_result", agentId: msg.agentId, global: [], workspace: [] });
|
|
1874
2240
|
});
|
|
1875
2241
|
break;
|
|
1876
2242
|
case "machine:workspace:scan":
|
|
1877
|
-
|
|
2243
|
+
logger.info("[Daemon] Scanning all workspace directories");
|
|
1878
2244
|
agentManager.scanAllWorkspaces().then((directories) => {
|
|
1879
2245
|
connection.send({ type: "machine:workspace:scan_result", directories });
|
|
1880
2246
|
});
|
|
1881
2247
|
break;
|
|
1882
2248
|
case "machine:workspace:delete":
|
|
1883
|
-
|
|
2249
|
+
logger.info(`[Daemon] Deleting workspace directory: ${msg.directoryName}`);
|
|
1884
2250
|
agentManager.deleteWorkspaceDirectory(msg.directoryName).then((success) => {
|
|
1885
2251
|
connection.send({ type: "machine:workspace:delete_result", directoryName: msg.directoryName, success });
|
|
1886
2252
|
});
|
|
@@ -1892,7 +2258,7 @@ connection = new DaemonConnection({
|
|
|
1892
2258
|
},
|
|
1893
2259
|
onConnect: () => {
|
|
1894
2260
|
const runtimes = detectRuntimes();
|
|
1895
|
-
|
|
2261
|
+
logger.info(`[Daemon] Detected runtimes: ${runtimes.join(", ") || "none"}`);
|
|
1896
2262
|
connection.send({
|
|
1897
2263
|
type: "ready",
|
|
1898
2264
|
capabilities: ["agent:start", "agent:stop", "agent:deliver", "workspace:files"],
|
|
@@ -1904,22 +2270,23 @@ connection = new DaemonConnection({
|
|
|
1904
2270
|
});
|
|
1905
2271
|
for (const agentId of agentManager.getRunningAgentIds()) {
|
|
1906
2272
|
const sessionId = agentManager.getAgentSessionId(agentId);
|
|
2273
|
+
const launchId = agentManager.getAgentLaunchId(agentId);
|
|
1907
2274
|
if (sessionId) {
|
|
1908
|
-
connection.send({ type: "agent:session", agentId, sessionId });
|
|
2275
|
+
connection.send({ type: "agent:session", agentId, sessionId, launchId: launchId || void 0 });
|
|
1909
2276
|
}
|
|
1910
2277
|
}
|
|
1911
|
-
for (const { agentId, sessionId } of agentManager.getIdleAgentSessionIds()) {
|
|
1912
|
-
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 });
|
|
1913
2280
|
}
|
|
1914
2281
|
},
|
|
1915
2282
|
onDisconnect: () => {
|
|
1916
|
-
|
|
2283
|
+
logger.warn("[Daemon] Lost connection \u2014 agents continue running locally");
|
|
1917
2284
|
}
|
|
1918
2285
|
});
|
|
1919
|
-
|
|
2286
|
+
logger.info("[Slock Daemon] Starting...");
|
|
1920
2287
|
connection.connect();
|
|
1921
2288
|
var shutdown = async () => {
|
|
1922
|
-
|
|
2289
|
+
logger.info("[Slock Daemon] Shutting down...");
|
|
1923
2290
|
await agentManager.stopAll();
|
|
1924
2291
|
connection.disconnect();
|
|
1925
2292
|
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slock-ai/daemon",
|
|
3
|
-
"version": "0.30.0",
|
|
3
|
+
"version": "0.30.1-alpha.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"slock-daemon": "dist/index.js"
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
21
|
-
"ws": "^8.
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
21
|
+
"ws": "^8.20.0",
|
|
22
22
|
"zod": "^4.3.6"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@types/node": "^25.
|
|
25
|
+
"@types/node": "^25.5.0",
|
|
26
26
|
"@types/ws": "^8.18.1",
|
|
27
27
|
"tsup": "^8.5.1",
|
|
28
28
|
"typescript": "^5.9.3",
|