@slock-ai/daemon 0.2.1 → 0.2.3
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/{src/chat-bridge.ts → dist/chat-bridge.js} +81 -126
- package/{src/agentProcessManager.ts → dist/index.js} +403 -225
- package/package.json +8 -8
- package/src/connection.ts +0 -100
- package/src/index.ts +0 -154
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slock-ai/daemon",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
|
-
"slock-daemon": "
|
|
6
|
+
"slock-daemon": "dist/index.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
|
-
"
|
|
9
|
+
"dist"
|
|
10
10
|
],
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -18,23 +18,23 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
21
|
-
"tsx": "^4.19.2",
|
|
22
21
|
"ws": "^8.18.0",
|
|
23
22
|
"zod": "^4.3.6"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
25
|
"@types/node": "^22.12.0",
|
|
27
26
|
"@types/ws": "^8.5.13",
|
|
27
|
+
"tsup": "^8.5.1",
|
|
28
28
|
"typescript": "^5.7.3",
|
|
29
29
|
"@slock-ai/shared": "0.1.0"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"dev": "tsx watch src/index.ts",
|
|
33
33
|
"start": "tsx src/index.ts",
|
|
34
|
-
"build": "
|
|
34
|
+
"build": "tsup",
|
|
35
35
|
"typecheck": "tsc --noEmit",
|
|
36
|
-
"release:patch": "npm version patch --no-git-tag-version && cd ../.. && git add packages/daemon/package.json && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p \"require('./packages/daemon/package.json').version\")\" && git tag daemon-v$(node -p \"require('./packages/daemon/package.json').version\") && git push && git push --tags",
|
|
37
|
-
"release:minor": "npm version minor --no-git-tag-version && cd ../.. && git add packages/daemon/package.json && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p \"require('./packages/daemon/package.json').version\")\" && git tag daemon-v$(node -p \"require('./packages/daemon/package.json').version\") && git push && git push --tags",
|
|
38
|
-
"release:major": "npm version major --no-git-tag-version && cd ../.. && git add packages/daemon/package.json && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p \"require('./packages/daemon/package.json').version\")\" && git tag daemon-v$(node -p \"require('./packages/daemon/package.json').version\") && git push && git push --tags"
|
|
36
|
+
"release:patch": "npm version patch --no-git-tag-version && cd ../.. && pnpm install --lockfile-only && git add packages/daemon/package.json pnpm-lock.yaml && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p \"require('./packages/daemon/package.json').version\")\" && git tag daemon-v$(node -p \"require('./packages/daemon/package.json').version\") && git push && git push --tags",
|
|
37
|
+
"release:minor": "npm version minor --no-git-tag-version && cd ../.. && pnpm install --lockfile-only && git add packages/daemon/package.json pnpm-lock.yaml && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p \"require('./packages/daemon/package.json').version\")\" && git tag daemon-v$(node -p \"require('./packages/daemon/package.json').version\") && git push && git push --tags",
|
|
38
|
+
"release:major": "npm version major --no-git-tag-version && cd ../.. && pnpm install --lockfile-only && git add packages/daemon/package.json pnpm-lock.yaml && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p \"require('./packages/daemon/package.json').version\")\" && git tag daemon-v$(node -p \"require('./packages/daemon/package.json').version\") && git push && git push --tags"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/src/connection.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import WebSocket from "ws";
|
|
2
|
-
import type { ServerToMachineMessage, MachineToServerMessage } from "@slock-ai/shared";
|
|
3
|
-
|
|
4
|
-
export interface ConnectionOptions {
|
|
5
|
-
serverUrl: string;
|
|
6
|
-
apiKey: string;
|
|
7
|
-
onMessage: (msg: ServerToMachineMessage) => void;
|
|
8
|
-
onConnect: () => void;
|
|
9
|
-
onDisconnect: () => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class DaemonConnection {
|
|
13
|
-
private ws: WebSocket | null = null;
|
|
14
|
-
private options: ConnectionOptions;
|
|
15
|
-
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
16
|
-
private reconnectDelay = 1000;
|
|
17
|
-
private maxReconnectDelay = 30000;
|
|
18
|
-
private shouldConnect = true;
|
|
19
|
-
|
|
20
|
-
constructor(options: ConnectionOptions) {
|
|
21
|
-
this.options = options;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
connect() {
|
|
25
|
-
this.shouldConnect = true;
|
|
26
|
-
this.doConnect();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
disconnect() {
|
|
30
|
-
this.shouldConnect = false;
|
|
31
|
-
if (this.reconnectTimer) {
|
|
32
|
-
clearTimeout(this.reconnectTimer);
|
|
33
|
-
this.reconnectTimer = null;
|
|
34
|
-
}
|
|
35
|
-
if (this.ws) {
|
|
36
|
-
this.ws.close();
|
|
37
|
-
this.ws = null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
send(msg: MachineToServerMessage) {
|
|
42
|
-
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
43
|
-
this.ws.send(JSON.stringify(msg));
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
get connected(): boolean {
|
|
48
|
-
return this.ws?.readyState === WebSocket.OPEN;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
private doConnect() {
|
|
52
|
-
if (!this.shouldConnect) return;
|
|
53
|
-
|
|
54
|
-
const wsUrl = this.options.serverUrl.replace(/^http/, "ws") + `/daemon/connect?key=${this.options.apiKey}`;
|
|
55
|
-
|
|
56
|
-
console.log(`[Daemon] Connecting to ${this.options.serverUrl}...`);
|
|
57
|
-
|
|
58
|
-
this.ws = new WebSocket(wsUrl);
|
|
59
|
-
|
|
60
|
-
this.ws.on("open", () => {
|
|
61
|
-
console.log("[Daemon] Connected to server");
|
|
62
|
-
this.reconnectDelay = 1000; // Reset backoff
|
|
63
|
-
this.options.onConnect();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
this.ws.on("message", (data: Buffer) => {
|
|
67
|
-
try {
|
|
68
|
-
const msg: ServerToMachineMessage = JSON.parse(data.toString());
|
|
69
|
-
this.options.onMessage(msg);
|
|
70
|
-
} catch (err) {
|
|
71
|
-
console.error("[Daemon] Invalid message from server:", err);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
this.ws.on("close", () => {
|
|
76
|
-
console.log("[Daemon] Disconnected from server");
|
|
77
|
-
this.options.onDisconnect();
|
|
78
|
-
this.scheduleReconnect();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
this.ws.on("error", (err) => {
|
|
82
|
-
console.error("[Daemon] WebSocket error:", err.message);
|
|
83
|
-
// 'close' will fire after this
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private scheduleReconnect() {
|
|
88
|
-
if (!this.shouldConnect) return;
|
|
89
|
-
if (this.reconnectTimer) return;
|
|
90
|
-
|
|
91
|
-
console.log(`[Daemon] Reconnecting in ${this.reconnectDelay}ms...`);
|
|
92
|
-
this.reconnectTimer = setTimeout(() => {
|
|
93
|
-
this.reconnectTimer = null;
|
|
94
|
-
this.doConnect();
|
|
95
|
-
}, this.reconnectDelay);
|
|
96
|
-
|
|
97
|
-
// Exponential backoff
|
|
98
|
-
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
|
|
99
|
-
}
|
|
100
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx tsx
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import { execSync } from "node:child_process";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { DaemonConnection } from "./connection.js";
|
|
7
|
-
import { AgentProcessManager } from "./agentProcessManager.js";
|
|
8
|
-
import { RUNTIMES, type ServerToMachineMessage } from "@slock-ai/shared";
|
|
9
|
-
|
|
10
|
-
/** Detect which CLI runtimes are installed on this machine */
|
|
11
|
-
function detectRuntimes(): string[] {
|
|
12
|
-
const detected: string[] = [];
|
|
13
|
-
for (const rt of RUNTIMES) {
|
|
14
|
-
try {
|
|
15
|
-
execSync(`which ${rt.binary}`, { stdio: "pipe" });
|
|
16
|
-
detected.push(rt.id);
|
|
17
|
-
} catch { /* not installed */ }
|
|
18
|
-
}
|
|
19
|
-
return detected;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Parse CLI args
|
|
23
|
-
const args = process.argv.slice(2);
|
|
24
|
-
let serverUrl = "";
|
|
25
|
-
let apiKey = "";
|
|
26
|
-
|
|
27
|
-
for (let i = 0; i < args.length; i++) {
|
|
28
|
-
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
29
|
-
if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!serverUrl || !apiKey) {
|
|
33
|
-
console.error("Usage: slock-daemon --server-url <url> --api-key <key>");
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Resolve chat-bridge path (bundled within daemon package)
|
|
38
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
39
|
-
const chatBridgePath = path.resolve(__dirname, "chat-bridge.ts");
|
|
40
|
-
|
|
41
|
-
// Create connection and agent manager
|
|
42
|
-
let connection: DaemonConnection;
|
|
43
|
-
|
|
44
|
-
const agentManager = new AgentProcessManager(chatBridgePath, (msg) => {
|
|
45
|
-
connection.send(msg);
|
|
46
|
-
}, apiKey);
|
|
47
|
-
|
|
48
|
-
connection = new DaemonConnection({
|
|
49
|
-
serverUrl,
|
|
50
|
-
apiKey,
|
|
51
|
-
onMessage: (msg: ServerToMachineMessage) => {
|
|
52
|
-
console.log(`[Daemon] Received: ${msg.type}`, msg.type === "ping" ? "" : JSON.stringify(msg).slice(0, 200));
|
|
53
|
-
switch (msg.type) {
|
|
54
|
-
case "agent:start":
|
|
55
|
-
console.log(`[Daemon] Starting agent ${msg.agentId} (model: ${msg.config.model}, session: ${msg.config.sessionId || "new"}${msg.wakeMessage ? ", with wake message" : ""})`);
|
|
56
|
-
agentManager.startAgent(msg.agentId, msg.config, msg.wakeMessage, msg.unreadSummary);
|
|
57
|
-
break;
|
|
58
|
-
|
|
59
|
-
case "agent:stop":
|
|
60
|
-
console.log(`[Daemon] Stopping agent ${msg.agentId}`);
|
|
61
|
-
agentManager.stopAgent(msg.agentId);
|
|
62
|
-
break;
|
|
63
|
-
|
|
64
|
-
case "agent:sleep":
|
|
65
|
-
console.log(`[Daemon] Sleeping agent ${msg.agentId}`);
|
|
66
|
-
agentManager.sleepAgent(msg.agentId);
|
|
67
|
-
break;
|
|
68
|
-
|
|
69
|
-
case "agent:reset-workspace":
|
|
70
|
-
console.log(`[Daemon] Resetting workspace for agent ${msg.agentId}`);
|
|
71
|
-
agentManager.resetWorkspace(msg.agentId);
|
|
72
|
-
break;
|
|
73
|
-
|
|
74
|
-
case "agent:deliver":
|
|
75
|
-
console.log(`[Daemon] Delivering message to ${msg.agentId}: ${msg.message.content.slice(0, 80)}`);
|
|
76
|
-
agentManager.deliverMessage(msg.agentId, msg.message);
|
|
77
|
-
connection.send({ type: "agent:deliver:ack", agentId: msg.agentId, seq: msg.seq });
|
|
78
|
-
break;
|
|
79
|
-
|
|
80
|
-
case "agent:workspace:list":
|
|
81
|
-
agentManager.getFileTree(msg.agentId).then((files) => {
|
|
82
|
-
connection.send({ type: "agent:workspace:file_tree", agentId: msg.agentId, files });
|
|
83
|
-
});
|
|
84
|
-
break;
|
|
85
|
-
|
|
86
|
-
case "agent:workspace:read":
|
|
87
|
-
agentManager.readFile(msg.agentId, msg.path).then(({ content, binary }) => {
|
|
88
|
-
connection.send({
|
|
89
|
-
type: "agent:workspace:file_content",
|
|
90
|
-
agentId: msg.agentId,
|
|
91
|
-
requestId: msg.requestId,
|
|
92
|
-
content,
|
|
93
|
-
binary,
|
|
94
|
-
});
|
|
95
|
-
}).catch(() => {
|
|
96
|
-
connection.send({
|
|
97
|
-
type: "agent:workspace:file_content",
|
|
98
|
-
agentId: msg.agentId,
|
|
99
|
-
requestId: msg.requestId,
|
|
100
|
-
content: null,
|
|
101
|
-
binary: false,
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
break;
|
|
105
|
-
|
|
106
|
-
case "machine:workspace:scan":
|
|
107
|
-
console.log("[Daemon] Scanning all workspace directories");
|
|
108
|
-
agentManager.scanAllWorkspaces().then((directories) => {
|
|
109
|
-
connection.send({ type: "machine:workspace:scan_result", directories });
|
|
110
|
-
});
|
|
111
|
-
break;
|
|
112
|
-
|
|
113
|
-
case "machine:workspace:delete":
|
|
114
|
-
console.log(`[Daemon] Deleting workspace directory: ${msg.directoryName}`);
|
|
115
|
-
agentManager.deleteWorkspaceDirectory(msg.directoryName).then((success) => {
|
|
116
|
-
connection.send({ type: "machine:workspace:delete_result", directoryName: msg.directoryName, success });
|
|
117
|
-
});
|
|
118
|
-
break;
|
|
119
|
-
|
|
120
|
-
case "ping":
|
|
121
|
-
connection.send({ type: "pong" });
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
onConnect: () => {
|
|
126
|
-
const runtimes = detectRuntimes();
|
|
127
|
-
console.log(`[Daemon] Detected runtimes: ${runtimes.join(", ") || "none"}`);
|
|
128
|
-
connection.send({
|
|
129
|
-
type: "ready",
|
|
130
|
-
capabilities: ["agent:start", "agent:stop", "agent:deliver", "workspace:files"],
|
|
131
|
-
runtimes,
|
|
132
|
-
runningAgents: agentManager.getRunningAgentIds(),
|
|
133
|
-
hostname: os.hostname(),
|
|
134
|
-
os: `${os.platform()} ${os.arch()}`,
|
|
135
|
-
});
|
|
136
|
-
},
|
|
137
|
-
onDisconnect: () => {
|
|
138
|
-
console.log("[Daemon] Lost connection — agents continue running locally");
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
console.log("[Slock Daemon] Starting...");
|
|
143
|
-
connection.connect();
|
|
144
|
-
|
|
145
|
-
// Graceful shutdown
|
|
146
|
-
const shutdown = async () => {
|
|
147
|
-
console.log("[Slock Daemon] Shutting down...");
|
|
148
|
-
await agentManager.stopAll();
|
|
149
|
-
connection.disconnect();
|
|
150
|
-
process.exit(0);
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
process.on("SIGTERM", shutdown);
|
|
154
|
-
process.on("SIGINT", shutdown);
|