@slock-ai/daemon 0.29.0 → 0.30.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.
Files changed (2) hide show
  1. package/dist/index.js +70 -10
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -10,26 +10,39 @@ import { fileURLToPath } from "url";
10
10
 
11
11
  // src/connection.ts
12
12
  import WebSocket from "ws";
13
+ var systemClock = {
14
+ now: () => Date.now(),
15
+ setTimeout: (fn, ms) => setTimeout(fn, ms),
16
+ clearTimeout: (timer) => clearTimeout(timer)
17
+ };
18
+ var INBOUND_WATCHDOG_MS = 7e4;
13
19
  var DaemonConnection = class {
14
20
  ws = null;
15
21
  options;
22
+ clock;
16
23
  reconnectTimer = null;
17
- reconnectDelay = 1e3;
24
+ watchdogTimer = null;
25
+ reconnectDelay;
18
26
  maxReconnectDelay = 3e4;
19
27
  shouldConnect = true;
20
28
  reconnectAttempt = 0;
21
29
  lastDroppedSendLogAt = 0;
22
30
  constructor(options) {
23
31
  this.options = options;
32
+ this.clock = options.clock ?? systemClock;
33
+ this.reconnectDelay = options.minReconnectDelayMs ?? 1e3;
24
34
  }
25
35
  connect() {
26
36
  this.shouldConnect = true;
37
+ if (this.reconnectTimer) return;
38
+ if (this.ws && this.ws.readyState !== WebSocket.CLOSED) return;
27
39
  this.doConnect();
28
40
  }
29
41
  disconnect() {
30
42
  this.shouldConnect = false;
43
+ this.clearWatchdog();
31
44
  if (this.reconnectTimer) {
32
- clearTimeout(this.reconnectTimer);
45
+ this.clock.clearTimeout(this.reconnectTimer);
33
46
  this.reconnectTimer = null;
34
47
  }
35
48
  if (this.ws) {
@@ -43,7 +56,7 @@ var DaemonConnection = class {
43
56
  this.ws.send(JSON.stringify(msg));
44
57
  return;
45
58
  }
46
- const now = Date.now();
59
+ const now = this.clock.now();
47
60
  if (now - this.lastDroppedSendLogAt > 5e3) {
48
61
  this.lastDroppedSendLogAt = now;
49
62
  console.warn(`[Daemon] Dropping outbound message while disconnected: ${msg.type}`);
@@ -54,16 +67,23 @@ var DaemonConnection = class {
54
67
  }
55
68
  doConnect() {
56
69
  if (!this.shouldConnect) return;
70
+ if (this.ws && this.ws.readyState !== WebSocket.CLOSED) return;
57
71
  const wsUrl = this.options.serverUrl.replace(/^http/, "ws") + `/daemon/connect?key=${this.options.apiKey}`;
58
72
  console.log(`[Daemon] Connecting to ${this.options.serverUrl}...`);
59
- this.ws = new WebSocket(wsUrl);
60
- this.ws.on("open", () => {
73
+ const ws = this.options.wsFactory ? this.options.wsFactory(wsUrl) : new WebSocket(wsUrl);
74
+ this.ws = ws;
75
+ ws.on("open", () => {
76
+ if (this.ws !== ws) return;
77
+ if (!this.shouldConnect) return;
61
78
  console.log("[Daemon] Connected to server");
62
79
  this.reconnectAttempt = 0;
63
- this.reconnectDelay = 1e3;
80
+ this.reconnectDelay = this.options.minReconnectDelayMs ?? 1e3;
81
+ this.resetWatchdog();
64
82
  this.options.onConnect();
65
83
  });
66
- this.ws.on("message", (data) => {
84
+ ws.on("message", (data) => {
85
+ if (this.ws !== ws) return;
86
+ this.resetWatchdog();
67
87
  try {
68
88
  const msg = JSON.parse(data.toString());
69
89
  this.options.onMessage(msg);
@@ -71,7 +91,10 @@ var DaemonConnection = class {
71
91
  console.error("[Daemon] Invalid message from server:", err);
72
92
  }
73
93
  });
74
- this.ws.on("close", (code, reasonBuffer) => {
94
+ ws.on("close", (code, reasonBuffer) => {
95
+ if (this.ws !== ws) return;
96
+ this.ws = null;
97
+ this.clearWatchdog();
75
98
  const reason = reasonBuffer.toString("utf8");
76
99
  console.log(
77
100
  `[Daemon] Disconnected from server (code=${code}, reason=${JSON.stringify(reason)}, reconnecting=${this.shouldConnect})`
@@ -79,7 +102,8 @@ var DaemonConnection = class {
79
102
  this.options.onDisconnect();
80
103
  this.scheduleReconnect();
81
104
  });
82
- this.ws.on("error", (err) => {
105
+ ws.on("error", (err) => {
106
+ if (this.ws !== ws) return;
83
107
  console.error("[Daemon] WebSocket error:", err.message);
84
108
  });
85
109
  }
@@ -88,12 +112,29 @@ var DaemonConnection = class {
88
112
  if (this.reconnectTimer) return;
89
113
  this.reconnectAttempt += 1;
90
114
  console.log(`[Daemon] Reconnecting to server in ${this.reconnectDelay}ms (attempt ${this.reconnectAttempt})`);
91
- this.reconnectTimer = setTimeout(() => {
115
+ this.reconnectTimer = this.clock.setTimeout(() => {
92
116
  this.reconnectTimer = null;
93
117
  this.doConnect();
94
118
  }, this.reconnectDelay);
95
119
  this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
96
120
  }
121
+ resetWatchdog() {
122
+ this.clearWatchdog();
123
+ const ms = this.options.inboundWatchdogMs ?? INBOUND_WATCHDOG_MS;
124
+ this.watchdogTimer = this.clock.setTimeout(() => {
125
+ console.warn(`[Daemon] No inbound traffic for ${ms / 1e3}s \u2014 forcing reconnect`);
126
+ try {
127
+ this.ws?.terminate();
128
+ } catch {
129
+ }
130
+ }, ms);
131
+ }
132
+ clearWatchdog() {
133
+ if (this.watchdogTimer) {
134
+ this.clock.clearTimeout(this.watchdogTimer);
135
+ this.watchdogTimer = null;
136
+ }
137
+ }
97
138
  };
98
139
 
99
140
  // src/agentProcessManager.ts
@@ -1288,6 +1329,16 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1288
1329
  getRunningAgentIds() {
1289
1330
  return [...this.agents.keys()];
1290
1331
  }
1332
+ getAgentSessionId(agentId) {
1333
+ return this.agents.get(agentId)?.sessionId ?? null;
1334
+ }
1335
+ getIdleAgentSessionIds() {
1336
+ const result = [];
1337
+ for (const [agentId, { sessionId }] of this.idleAgentConfigs) {
1338
+ if (sessionId) result.push({ agentId, sessionId });
1339
+ }
1340
+ return result;
1341
+ }
1291
1342
  // Machine-level workspace scanning
1292
1343
  async scanAllWorkspaces() {
1293
1344
  const results = [];
@@ -1851,6 +1902,15 @@ connection = new DaemonConnection({
1851
1902
  os: `${os2.platform()} ${os2.arch()}`,
1852
1903
  daemonVersion: DAEMON_VERSION
1853
1904
  });
1905
+ for (const agentId of agentManager.getRunningAgentIds()) {
1906
+ const sessionId = agentManager.getAgentSessionId(agentId);
1907
+ if (sessionId) {
1908
+ connection.send({ type: "agent:session", agentId, sessionId });
1909
+ }
1910
+ }
1911
+ for (const { agentId, sessionId } of agentManager.getIdleAgentSessionIds()) {
1912
+ connection.send({ type: "agent:session", agentId, sessionId });
1913
+ }
1854
1914
  },
1855
1915
  onDisconnect: () => {
1856
1916
  console.log("[Daemon] Lost connection \u2014 agents continue running locally");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"
@@ -32,7 +32,7 @@
32
32
  "dev": "tsx watch src/index.ts",
33
33
  "start": "tsx src/index.ts",
34
34
  "build": "tsup",
35
- "test": "node --import tsx --test src/**/*.test.ts",
35
+ "test": "node --import tsx --test --test-force-exit 'src/**/*.test.ts'",
36
36
  "typecheck": "tsc --noEmit",
37
37
  "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",
38
38
  "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",