agenthub-multiagent-mcp 1.9.1 → 1.10.1

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.
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentHub Daemon — Persistent bridge for autonomous Claude Code agents
4
+ *
5
+ * Maintains a WebSocket connection to AgentHub and spawns Claude Code
6
+ * sessions to handle incoming messages. This solves the "idle agent"
7
+ * problem where no hooks fire because there's no user interaction.
8
+ *
9
+ * Usage:
10
+ * agenthub-daemon --agent-id <id> --project-dir <path>
11
+ * agenthub-daemon --agent-id krishi-dev --project-dir /home/user/project
12
+ *
13
+ * Environment:
14
+ * AGENTHUB_URL - AgentHub server URL (default: https://agenthub.contetial.com)
15
+ * AGENTHUB_API_KEY - API key for authentication (required)
16
+ *
17
+ * The daemon:
18
+ * 1. Connects to AgentHub via WebSocket
19
+ * 2. Listens for incoming messages and tasks
20
+ * 3. Spawns `claude` CLI to process each message
21
+ * 4. Sends the response back to AgentHub
22
+ * 5. Reconnects automatically on disconnection
23
+ */
24
+ export {};
25
+ //# sourceMappingURL=daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;GAqBG"}
package/dist/daemon.js ADDED
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentHub Daemon — Persistent bridge for autonomous Claude Code agents
4
+ *
5
+ * Maintains a WebSocket connection to AgentHub and spawns Claude Code
6
+ * sessions to handle incoming messages. This solves the "idle agent"
7
+ * problem where no hooks fire because there's no user interaction.
8
+ *
9
+ * Usage:
10
+ * agenthub-daemon --agent-id <id> --project-dir <path>
11
+ * agenthub-daemon --agent-id krishi-dev --project-dir /home/user/project
12
+ *
13
+ * Environment:
14
+ * AGENTHUB_URL - AgentHub server URL (default: https://agenthub.contetial.com)
15
+ * AGENTHUB_API_KEY - API key for authentication (required)
16
+ *
17
+ * The daemon:
18
+ * 1. Connects to AgentHub via WebSocket
19
+ * 2. Listens for incoming messages and tasks
20
+ * 3. Spawns `claude` CLI to process each message
21
+ * 4. Sends the response back to AgentHub
22
+ * 5. Reconnects automatically on disconnection
23
+ */
24
+ import WebSocket from "ws";
25
+ import { spawn } from "child_process";
26
+ // Parse CLI arguments
27
+ const args = process.argv.slice(2);
28
+ function getArg(name) {
29
+ const idx = args.indexOf(`--${name}`);
30
+ return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
31
+ }
32
+ const AGENT_ID = getArg("agent-id") || process.env.AGENTHUB_AGENT_ID || "";
33
+ const PROJECT_DIR = getArg("project-dir") || process.cwd();
34
+ const API_KEY = process.env.AGENTHUB_API_KEY || "";
35
+ const API_URL = process.env.AGENTHUB_URL || "https://agenthub.contetial.com";
36
+ const WS_URL = API_URL.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
37
+ const MAX_CONCURRENT = parseInt(getArg("max-concurrent") || "1", 10);
38
+ const CLAUDE_CMD = getArg("claude-cmd") || "claude";
39
+ if (!AGENT_ID) {
40
+ console.error("Error: --agent-id or AGENTHUB_AGENT_ID required");
41
+ process.exit(1);
42
+ }
43
+ if (!API_KEY) {
44
+ console.error("Error: AGENTHUB_API_KEY environment variable required");
45
+ process.exit(1);
46
+ }
47
+ // State
48
+ let ws = null;
49
+ let reconnectAttempts = 0;
50
+ let activeProcesses = 0;
51
+ let isShuttingDown = false;
52
+ function log(msg, ...extra) {
53
+ const ts = new Date().toISOString();
54
+ console.log(`[${ts}] [agenthub-daemon] ${msg}`, ...extra);
55
+ }
56
+ function logError(msg, ...extra) {
57
+ const ts = new Date().toISOString();
58
+ console.error(`[${ts}] [agenthub-daemon] ${msg}`, ...extra);
59
+ }
60
+ /**
61
+ * Connect to AgentHub WebSocket
62
+ */
63
+ function connect() {
64
+ if (isShuttingDown)
65
+ return;
66
+ const url = `${WS_URL}/ws/agents/${AGENT_ID}?api_key=${API_KEY}`;
67
+ log(`Connecting to AgentHub as "${AGENT_ID}"...`);
68
+ ws = new WebSocket(url);
69
+ ws.on("open", () => {
70
+ log("WebSocket connected");
71
+ reconnectAttempts = 0;
72
+ });
73
+ ws.on("message", (data) => {
74
+ try {
75
+ const msg = JSON.parse(data.toString());
76
+ handleWSMessage(msg);
77
+ }
78
+ catch (err) {
79
+ logError("Failed to parse WebSocket message:", err);
80
+ }
81
+ });
82
+ ws.on("close", (code, reason) => {
83
+ log(`WebSocket closed (code: ${code}, reason: ${reason.toString()})`);
84
+ ws = null;
85
+ scheduleReconnect();
86
+ });
87
+ ws.on("error", (err) => {
88
+ logError("WebSocket error:", err.message);
89
+ });
90
+ }
91
+ /**
92
+ * Schedule reconnection with exponential backoff
93
+ */
94
+ function scheduleReconnect() {
95
+ if (isShuttingDown)
96
+ return;
97
+ reconnectAttempts++;
98
+ const delay = Math.min(1000 * Math.pow(2, reconnectAttempts - 1), 30000);
99
+ log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts})...`);
100
+ setTimeout(() => {
101
+ if (!isShuttingDown)
102
+ connect();
103
+ }, delay);
104
+ }
105
+ /**
106
+ * Handle incoming WebSocket messages
107
+ */
108
+ function handleWSMessage(msg) {
109
+ // Respond to pings
110
+ if (msg.type === "ping") {
111
+ ws?.send(JSON.stringify({ type: "pong", timestamp: new Date().toISOString() }));
112
+ return;
113
+ }
114
+ // Handle incoming message
115
+ if (msg.type === "message" && msg.message) {
116
+ const m = msg.message;
117
+ log(`Message from ${m.from_agent}: ${m.subject}`);
118
+ // Acknowledge receipt
119
+ ws?.send(JSON.stringify({ type: "ack", message_id: m.id, timestamp: new Date().toISOString() }));
120
+ // Process with Claude
121
+ processWithClaude(`You received a message on AgentHub.\n\nFrom: ${m.from_agent}\nSubject: ${m.subject}\nType: ${m.type}\nPriority: ${m.priority}\n\nBody:\n${m.body}\n\nPlease review and respond using the reply tool with message_id="${m.id}".`, m.id);
122
+ return;
123
+ }
124
+ // Handle incoming task
125
+ if (msg.type === "task" && msg.task) {
126
+ const t = msg.task;
127
+ log(`Task from ${t.assigned_by}: ${t.task}`);
128
+ processWithClaude(`You have been assigned a new task on AgentHub.\n\nTask: ${t.task}\nAssigned by: ${t.assigned_by}\nPriority: ${t.priority}\nTask ID: ${t.id}\n\nPlease accept this task using accept_task with task_id="${t.id}", then start working on it.`, undefined);
129
+ return;
130
+ }
131
+ }
132
+ /**
133
+ * Spawn Claude Code CLI to process a message
134
+ */
135
+ function processWithClaude(prompt, messageId) {
136
+ if (activeProcesses >= MAX_CONCURRENT) {
137
+ log(`Skipping — already processing ${activeProcesses} message(s) (max: ${MAX_CONCURRENT})`);
138
+ // Still mark as read if possible
139
+ if (messageId)
140
+ markRead(messageId);
141
+ return;
142
+ }
143
+ activeProcesses++;
144
+ log(`Spawning Claude Code (active: ${activeProcesses}/${MAX_CONCURRENT})...`);
145
+ const child = spawn(CLAUDE_CMD, [
146
+ "-p",
147
+ prompt,
148
+ "--dangerously-skip-permissions",
149
+ ], {
150
+ cwd: PROJECT_DIR,
151
+ env: {
152
+ ...process.env,
153
+ AGENTHUB_AGENT_ID: AGENT_ID,
154
+ },
155
+ stdio: ["pipe", "pipe", "pipe"],
156
+ timeout: 300_000, // 5 minute timeout
157
+ shell: true, // Required on Windows where claude is a shell script wrapper
158
+ });
159
+ let stdout = "";
160
+ let stderr = "";
161
+ child.stdout?.on("data", (data) => {
162
+ stdout += data.toString();
163
+ });
164
+ child.stderr?.on("data", (data) => {
165
+ stderr += data.toString();
166
+ });
167
+ child.on("close", (code) => {
168
+ activeProcesses--;
169
+ if (code === 0) {
170
+ log(`Claude completed successfully (output: ${stdout.length} chars)`);
171
+ // If this was a message, send a reply with Claude's response
172
+ if (messageId && stdout.trim()) {
173
+ sendReply(messageId, stdout.trim());
174
+ }
175
+ }
176
+ else {
177
+ logError(`Claude exited with code ${code}`);
178
+ if (stderr)
179
+ logError(`stderr: ${stderr.slice(0, 500)}`);
180
+ }
181
+ });
182
+ child.on("error", (err) => {
183
+ activeProcesses--;
184
+ logError(`Failed to spawn Claude:`, err.message);
185
+ });
186
+ }
187
+ /**
188
+ * Send a reply back to AgentHub
189
+ */
190
+ async function sendReply(messageId, body) {
191
+ try {
192
+ const response = await fetch(`${API_URL}/api/messages`, {
193
+ method: "POST",
194
+ headers: {
195
+ "Content-Type": "application/json",
196
+ "X-API-Key": API_KEY,
197
+ },
198
+ body: JSON.stringify({
199
+ from_agent: AGENT_ID,
200
+ to_agent: "", // Will be resolved from reply_to
201
+ type: "response",
202
+ subject: "Re: (auto-response)",
203
+ body: body.slice(0, 5000), // Limit response length
204
+ reply_to: messageId,
205
+ }),
206
+ });
207
+ if (response.ok) {
208
+ log(`Reply sent for message ${messageId}`);
209
+ }
210
+ else {
211
+ logError(`Reply failed: HTTP ${response.status}`);
212
+ }
213
+ }
214
+ catch (err) {
215
+ logError(`Reply failed:`, err);
216
+ }
217
+ }
218
+ /**
219
+ * Mark a message as read
220
+ */
221
+ async function markRead(messageId) {
222
+ try {
223
+ await fetch(`${API_URL}/api/messages/${messageId}/read`, {
224
+ method: "POST",
225
+ headers: {
226
+ "Content-Type": "application/json",
227
+ "X-API-Key": API_KEY,
228
+ },
229
+ body: JSON.stringify({ agent_id: AGENT_ID }),
230
+ });
231
+ }
232
+ catch {
233
+ // Non-fatal
234
+ }
235
+ }
236
+ /**
237
+ * Graceful shutdown
238
+ */
239
+ function shutdown() {
240
+ log("Shutting down...");
241
+ isShuttingDown = true;
242
+ ws?.close();
243
+ // Wait for active processes to finish
244
+ if (activeProcesses > 0) {
245
+ log(`Waiting for ${activeProcesses} active process(es)...`);
246
+ const check = setInterval(() => {
247
+ if (activeProcesses === 0) {
248
+ clearInterval(check);
249
+ process.exit(0);
250
+ }
251
+ }, 1000);
252
+ // Force exit after 30 seconds
253
+ setTimeout(() => process.exit(1), 30000);
254
+ }
255
+ else {
256
+ process.exit(0);
257
+ }
258
+ }
259
+ process.on("SIGINT", shutdown);
260
+ process.on("SIGTERM", shutdown);
261
+ // Start
262
+ log(`AgentHub Daemon starting`);
263
+ log(` Agent: ${AGENT_ID}`);
264
+ log(` Project: ${PROJECT_DIR}`);
265
+ log(` Server: ${API_URL}`);
266
+ log(` Max concurrent: ${MAX_CONCURRENT}`);
267
+ connect();
268
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "agenthub-multiagent-mcp",
3
- "version": "1.9.1",
3
+ "version": "1.10.1",
4
4
  "description": "MCP server for AgentHub multi-agent communication",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "agenthub-multiagent-mcp": "./dist/index.js",
9
- "agenthub-hook": "./dist/planHookCli.js"
9
+ "agenthub-hook": "./dist/planHookCli.js",
10
+ "agenthub-daemon": "./dist/daemon.js"
10
11
  },
11
12
  "scripts": {
12
13
  "build": "tsc",
package/src/daemon.ts ADDED
@@ -0,0 +1,317 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentHub Daemon — Persistent bridge for autonomous Claude Code agents
4
+ *
5
+ * Maintains a WebSocket connection to AgentHub and spawns Claude Code
6
+ * sessions to handle incoming messages. This solves the "idle agent"
7
+ * problem where no hooks fire because there's no user interaction.
8
+ *
9
+ * Usage:
10
+ * agenthub-daemon --agent-id <id> --project-dir <path>
11
+ * agenthub-daemon --agent-id krishi-dev --project-dir /home/user/project
12
+ *
13
+ * Environment:
14
+ * AGENTHUB_URL - AgentHub server URL (default: https://agenthub.contetial.com)
15
+ * AGENTHUB_API_KEY - API key for authentication (required)
16
+ *
17
+ * The daemon:
18
+ * 1. Connects to AgentHub via WebSocket
19
+ * 2. Listens for incoming messages and tasks
20
+ * 3. Spawns `claude` CLI to process each message
21
+ * 4. Sends the response back to AgentHub
22
+ * 5. Reconnects automatically on disconnection
23
+ */
24
+
25
+ import WebSocket from "ws";
26
+ import { spawn } from "child_process";
27
+
28
+ // Parse CLI arguments
29
+ const args = process.argv.slice(2);
30
+ function getArg(name: string): string | undefined {
31
+ const idx = args.indexOf(`--${name}`);
32
+ return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
33
+ }
34
+
35
+ const AGENT_ID = getArg("agent-id") || process.env.AGENTHUB_AGENT_ID || "";
36
+ const PROJECT_DIR = getArg("project-dir") || process.cwd();
37
+ const API_KEY = process.env.AGENTHUB_API_KEY || "";
38
+ const API_URL = process.env.AGENTHUB_URL || "https://agenthub.contetial.com";
39
+ const WS_URL = API_URL.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
40
+ const MAX_CONCURRENT = parseInt(getArg("max-concurrent") || "1", 10);
41
+ const CLAUDE_CMD = getArg("claude-cmd") || "claude";
42
+
43
+ if (!AGENT_ID) {
44
+ console.error("Error: --agent-id or AGENTHUB_AGENT_ID required");
45
+ process.exit(1);
46
+ }
47
+ if (!API_KEY) {
48
+ console.error("Error: AGENTHUB_API_KEY environment variable required");
49
+ process.exit(1);
50
+ }
51
+
52
+ // State
53
+ let ws: WebSocket | null = null;
54
+ let reconnectAttempts = 0;
55
+ let activeProcesses = 0;
56
+ let isShuttingDown = false;
57
+
58
+ function log(msg: string, ...extra: unknown[]): void {
59
+ const ts = new Date().toISOString();
60
+ console.log(`[${ts}] [agenthub-daemon] ${msg}`, ...extra);
61
+ }
62
+
63
+ function logError(msg: string, ...extra: unknown[]): void {
64
+ const ts = new Date().toISOString();
65
+ console.error(`[${ts}] [agenthub-daemon] ${msg}`, ...extra);
66
+ }
67
+
68
+ /**
69
+ * Connect to AgentHub WebSocket
70
+ */
71
+ function connect(): void {
72
+ if (isShuttingDown) return;
73
+
74
+ const url = `${WS_URL}/ws/agents/${AGENT_ID}?api_key=${API_KEY}`;
75
+ log(`Connecting to AgentHub as "${AGENT_ID}"...`);
76
+
77
+ ws = new WebSocket(url);
78
+
79
+ ws.on("open", () => {
80
+ log("WebSocket connected");
81
+ reconnectAttempts = 0;
82
+ });
83
+
84
+ ws.on("message", (data: WebSocket.Data) => {
85
+ try {
86
+ const msg = JSON.parse(data.toString());
87
+ handleWSMessage(msg);
88
+ } catch (err) {
89
+ logError("Failed to parse WebSocket message:", err);
90
+ }
91
+ });
92
+
93
+ ws.on("close", (code: number, reason: Buffer) => {
94
+ log(`WebSocket closed (code: ${code}, reason: ${reason.toString()})`);
95
+ ws = null;
96
+ scheduleReconnect();
97
+ });
98
+
99
+ ws.on("error", (err: Error) => {
100
+ logError("WebSocket error:", err.message);
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Schedule reconnection with exponential backoff
106
+ */
107
+ function scheduleReconnect(): void {
108
+ if (isShuttingDown) return;
109
+
110
+ reconnectAttempts++;
111
+ const delay = Math.min(1000 * Math.pow(2, reconnectAttempts - 1), 30000);
112
+ log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts})...`);
113
+
114
+ setTimeout(() => {
115
+ if (!isShuttingDown) connect();
116
+ }, delay);
117
+ }
118
+
119
+ /**
120
+ * Handle incoming WebSocket messages
121
+ */
122
+ function handleWSMessage(msg: {
123
+ type: string;
124
+ message?: {
125
+ id: string;
126
+ from_agent: string;
127
+ subject: string;
128
+ body: string;
129
+ type: string;
130
+ priority: string;
131
+ };
132
+ task?: {
133
+ id: string;
134
+ task: string;
135
+ assigned_by: string;
136
+ priority: string;
137
+ };
138
+ }): void {
139
+ // Respond to pings
140
+ if (msg.type === "ping") {
141
+ ws?.send(JSON.stringify({ type: "pong", timestamp: new Date().toISOString() }));
142
+ return;
143
+ }
144
+
145
+ // Handle incoming message
146
+ if (msg.type === "message" && msg.message) {
147
+ const m = msg.message;
148
+ log(`Message from ${m.from_agent}: ${m.subject}`);
149
+
150
+ // Acknowledge receipt
151
+ ws?.send(JSON.stringify({ type: "ack", message_id: m.id, timestamp: new Date().toISOString() }));
152
+
153
+ // Process with Claude
154
+ processWithClaude(
155
+ `You received a message on AgentHub.\n\nFrom: ${m.from_agent}\nSubject: ${m.subject}\nType: ${m.type}\nPriority: ${m.priority}\n\nBody:\n${m.body}\n\nPlease review and respond using the reply tool with message_id="${m.id}".`,
156
+ m.id
157
+ );
158
+ return;
159
+ }
160
+
161
+ // Handle incoming task
162
+ if (msg.type === "task" && msg.task) {
163
+ const t = msg.task;
164
+ log(`Task from ${t.assigned_by}: ${t.task}`);
165
+
166
+ processWithClaude(
167
+ `You have been assigned a new task on AgentHub.\n\nTask: ${t.task}\nAssigned by: ${t.assigned_by}\nPriority: ${t.priority}\nTask ID: ${t.id}\n\nPlease accept this task using accept_task with task_id="${t.id}", then start working on it.`,
168
+ undefined
169
+ );
170
+ return;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Spawn Claude Code CLI to process a message
176
+ */
177
+ function processWithClaude(prompt: string, messageId: string | undefined): void {
178
+ if (activeProcesses >= MAX_CONCURRENT) {
179
+ log(`Skipping — already processing ${activeProcesses} message(s) (max: ${MAX_CONCURRENT})`);
180
+ // Still mark as read if possible
181
+ if (messageId) markRead(messageId);
182
+ return;
183
+ }
184
+
185
+ activeProcesses++;
186
+ log(`Spawning Claude Code (active: ${activeProcesses}/${MAX_CONCURRENT})...`);
187
+
188
+ const child = spawn(CLAUDE_CMD, [
189
+ "-p",
190
+ prompt,
191
+ "--dangerously-skip-permissions",
192
+ ], {
193
+ cwd: PROJECT_DIR,
194
+ env: {
195
+ ...process.env,
196
+ AGENTHUB_AGENT_ID: AGENT_ID,
197
+ },
198
+ stdio: ["pipe", "pipe", "pipe"],
199
+ timeout: 300_000, // 5 minute timeout
200
+ shell: true, // Required on Windows where claude is a shell script wrapper
201
+ });
202
+
203
+ let stdout = "";
204
+ let stderr = "";
205
+
206
+ child.stdout?.on("data", (data: Buffer) => {
207
+ stdout += data.toString();
208
+ });
209
+
210
+ child.stderr?.on("data", (data: Buffer) => {
211
+ stderr += data.toString();
212
+ });
213
+
214
+ child.on("close", (code: number | null) => {
215
+ activeProcesses--;
216
+
217
+ if (code === 0) {
218
+ log(`Claude completed successfully (output: ${stdout.length} chars)`);
219
+
220
+ // If this was a message, send a reply with Claude's response
221
+ if (messageId && stdout.trim()) {
222
+ sendReply(messageId, stdout.trim());
223
+ }
224
+ } else {
225
+ logError(`Claude exited with code ${code}`);
226
+ if (stderr) logError(`stderr: ${stderr.slice(0, 500)}`);
227
+ }
228
+ });
229
+
230
+ child.on("error", (err: Error) => {
231
+ activeProcesses--;
232
+ logError(`Failed to spawn Claude:`, err.message);
233
+ });
234
+ }
235
+
236
+ /**
237
+ * Send a reply back to AgentHub
238
+ */
239
+ async function sendReply(messageId: string, body: string): Promise<void> {
240
+ try {
241
+ const response = await fetch(`${API_URL}/api/messages`, {
242
+ method: "POST",
243
+ headers: {
244
+ "Content-Type": "application/json",
245
+ "X-API-Key": API_KEY,
246
+ },
247
+ body: JSON.stringify({
248
+ from_agent: AGENT_ID,
249
+ to_agent: "", // Will be resolved from reply_to
250
+ type: "response",
251
+ subject: "Re: (auto-response)",
252
+ body: body.slice(0, 5000), // Limit response length
253
+ reply_to: messageId,
254
+ }),
255
+ });
256
+
257
+ if (response.ok) {
258
+ log(`Reply sent for message ${messageId}`);
259
+ } else {
260
+ logError(`Reply failed: HTTP ${response.status}`);
261
+ }
262
+ } catch (err) {
263
+ logError(`Reply failed:`, err);
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Mark a message as read
269
+ */
270
+ async function markRead(messageId: string): Promise<void> {
271
+ try {
272
+ await fetch(`${API_URL}/api/messages/${messageId}/read`, {
273
+ method: "POST",
274
+ headers: {
275
+ "Content-Type": "application/json",
276
+ "X-API-Key": API_KEY,
277
+ },
278
+ body: JSON.stringify({ agent_id: AGENT_ID }),
279
+ });
280
+ } catch {
281
+ // Non-fatal
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Graceful shutdown
287
+ */
288
+ function shutdown(): void {
289
+ log("Shutting down...");
290
+ isShuttingDown = true;
291
+ ws?.close();
292
+ // Wait for active processes to finish
293
+ if (activeProcesses > 0) {
294
+ log(`Waiting for ${activeProcesses} active process(es)...`);
295
+ const check = setInterval(() => {
296
+ if (activeProcesses === 0) {
297
+ clearInterval(check);
298
+ process.exit(0);
299
+ }
300
+ }, 1000);
301
+ // Force exit after 30 seconds
302
+ setTimeout(() => process.exit(1), 30000);
303
+ } else {
304
+ process.exit(0);
305
+ }
306
+ }
307
+
308
+ process.on("SIGINT", shutdown);
309
+ process.on("SIGTERM", shutdown);
310
+
311
+ // Start
312
+ log(`AgentHub Daemon starting`);
313
+ log(` Agent: ${AGENT_ID}`);
314
+ log(` Project: ${PROJECT_DIR}`);
315
+ log(` Server: ${API_URL}`);
316
+ log(` Max concurrent: ${MAX_CONCURRENT}`);
317
+ connect();