agenthub-multiagent-mcp 1.9.1 → 1.10.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/daemon.d.ts +25 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +267 -0
- package/dist/daemon.js.map +1 -0
- package/package.json +3 -2
- package/src/daemon.ts +316 -0
package/dist/daemon.d.ts
ADDED
|
@@ -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,267 @@
|
|
|
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
|
+
"--print",
|
|
147
|
+
"--dangerously-skip-permissions",
|
|
148
|
+
"--message", prompt,
|
|
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
|
+
});
|
|
158
|
+
let stdout = "";
|
|
159
|
+
let stderr = "";
|
|
160
|
+
child.stdout?.on("data", (data) => {
|
|
161
|
+
stdout += data.toString();
|
|
162
|
+
});
|
|
163
|
+
child.stderr?.on("data", (data) => {
|
|
164
|
+
stderr += data.toString();
|
|
165
|
+
});
|
|
166
|
+
child.on("close", (code) => {
|
|
167
|
+
activeProcesses--;
|
|
168
|
+
if (code === 0) {
|
|
169
|
+
log(`Claude completed successfully (output: ${stdout.length} chars)`);
|
|
170
|
+
// If this was a message, send a reply with Claude's response
|
|
171
|
+
if (messageId && stdout.trim()) {
|
|
172
|
+
sendReply(messageId, stdout.trim());
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
logError(`Claude exited with code ${code}`);
|
|
177
|
+
if (stderr)
|
|
178
|
+
logError(`stderr: ${stderr.slice(0, 500)}`);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
child.on("error", (err) => {
|
|
182
|
+
activeProcesses--;
|
|
183
|
+
logError(`Failed to spawn Claude:`, err.message);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Send a reply back to AgentHub
|
|
188
|
+
*/
|
|
189
|
+
async function sendReply(messageId, body) {
|
|
190
|
+
try {
|
|
191
|
+
const response = await fetch(`${API_URL}/api/messages`, {
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: {
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
"X-API-Key": API_KEY,
|
|
196
|
+
},
|
|
197
|
+
body: JSON.stringify({
|
|
198
|
+
from_agent: AGENT_ID,
|
|
199
|
+
to_agent: "", // Will be resolved from reply_to
|
|
200
|
+
type: "response",
|
|
201
|
+
subject: "Re: (auto-response)",
|
|
202
|
+
body: body.slice(0, 5000), // Limit response length
|
|
203
|
+
reply_to: messageId,
|
|
204
|
+
}),
|
|
205
|
+
});
|
|
206
|
+
if (response.ok) {
|
|
207
|
+
log(`Reply sent for message ${messageId}`);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
logError(`Reply failed: HTTP ${response.status}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
logError(`Reply failed:`, err);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Mark a message as read
|
|
219
|
+
*/
|
|
220
|
+
async function markRead(messageId) {
|
|
221
|
+
try {
|
|
222
|
+
await fetch(`${API_URL}/api/messages/${messageId}/read`, {
|
|
223
|
+
method: "POST",
|
|
224
|
+
headers: {
|
|
225
|
+
"Content-Type": "application/json",
|
|
226
|
+
"X-API-Key": API_KEY,
|
|
227
|
+
},
|
|
228
|
+
body: JSON.stringify({ agent_id: AGENT_ID }),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// Non-fatal
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Graceful shutdown
|
|
237
|
+
*/
|
|
238
|
+
function shutdown() {
|
|
239
|
+
log("Shutting down...");
|
|
240
|
+
isShuttingDown = true;
|
|
241
|
+
ws?.close();
|
|
242
|
+
// Wait for active processes to finish
|
|
243
|
+
if (activeProcesses > 0) {
|
|
244
|
+
log(`Waiting for ${activeProcesses} active process(es)...`);
|
|
245
|
+
const check = setInterval(() => {
|
|
246
|
+
if (activeProcesses === 0) {
|
|
247
|
+
clearInterval(check);
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
}, 1000);
|
|
251
|
+
// Force exit after 30 seconds
|
|
252
|
+
setTimeout(() => process.exit(1), 30000);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
process.exit(0);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
process.on("SIGINT", shutdown);
|
|
259
|
+
process.on("SIGTERM", shutdown);
|
|
260
|
+
// Start
|
|
261
|
+
log(`AgentHub Daemon starting`);
|
|
262
|
+
log(` Agent: ${AGENT_ID}`);
|
|
263
|
+
log(` Project: ${PROJECT_DIR}`);
|
|
264
|
+
log(` Server: ${API_URL}`);
|
|
265
|
+
log(` Max concurrent: ${MAX_CONCURRENT}`);
|
|
266
|
+
connect();
|
|
267
|
+
//# 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.
|
|
3
|
+
"version": "1.10.0",
|
|
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,316 @@
|
|
|
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
|
+
"--print",
|
|
190
|
+
"--dangerously-skip-permissions",
|
|
191
|
+
"--message", prompt,
|
|
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
|
+
});
|
|
201
|
+
|
|
202
|
+
let stdout = "";
|
|
203
|
+
let stderr = "";
|
|
204
|
+
|
|
205
|
+
child.stdout?.on("data", (data: Buffer) => {
|
|
206
|
+
stdout += data.toString();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
child.stderr?.on("data", (data: Buffer) => {
|
|
210
|
+
stderr += data.toString();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
child.on("close", (code: number | null) => {
|
|
214
|
+
activeProcesses--;
|
|
215
|
+
|
|
216
|
+
if (code === 0) {
|
|
217
|
+
log(`Claude completed successfully (output: ${stdout.length} chars)`);
|
|
218
|
+
|
|
219
|
+
// If this was a message, send a reply with Claude's response
|
|
220
|
+
if (messageId && stdout.trim()) {
|
|
221
|
+
sendReply(messageId, stdout.trim());
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
logError(`Claude exited with code ${code}`);
|
|
225
|
+
if (stderr) logError(`stderr: ${stderr.slice(0, 500)}`);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
child.on("error", (err: Error) => {
|
|
230
|
+
activeProcesses--;
|
|
231
|
+
logError(`Failed to spawn Claude:`, err.message);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Send a reply back to AgentHub
|
|
237
|
+
*/
|
|
238
|
+
async function sendReply(messageId: string, body: string): Promise<void> {
|
|
239
|
+
try {
|
|
240
|
+
const response = await fetch(`${API_URL}/api/messages`, {
|
|
241
|
+
method: "POST",
|
|
242
|
+
headers: {
|
|
243
|
+
"Content-Type": "application/json",
|
|
244
|
+
"X-API-Key": API_KEY,
|
|
245
|
+
},
|
|
246
|
+
body: JSON.stringify({
|
|
247
|
+
from_agent: AGENT_ID,
|
|
248
|
+
to_agent: "", // Will be resolved from reply_to
|
|
249
|
+
type: "response",
|
|
250
|
+
subject: "Re: (auto-response)",
|
|
251
|
+
body: body.slice(0, 5000), // Limit response length
|
|
252
|
+
reply_to: messageId,
|
|
253
|
+
}),
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
if (response.ok) {
|
|
257
|
+
log(`Reply sent for message ${messageId}`);
|
|
258
|
+
} else {
|
|
259
|
+
logError(`Reply failed: HTTP ${response.status}`);
|
|
260
|
+
}
|
|
261
|
+
} catch (err) {
|
|
262
|
+
logError(`Reply failed:`, err);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Mark a message as read
|
|
268
|
+
*/
|
|
269
|
+
async function markRead(messageId: string): Promise<void> {
|
|
270
|
+
try {
|
|
271
|
+
await fetch(`${API_URL}/api/messages/${messageId}/read`, {
|
|
272
|
+
method: "POST",
|
|
273
|
+
headers: {
|
|
274
|
+
"Content-Type": "application/json",
|
|
275
|
+
"X-API-Key": API_KEY,
|
|
276
|
+
},
|
|
277
|
+
body: JSON.stringify({ agent_id: AGENT_ID }),
|
|
278
|
+
});
|
|
279
|
+
} catch {
|
|
280
|
+
// Non-fatal
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Graceful shutdown
|
|
286
|
+
*/
|
|
287
|
+
function shutdown(): void {
|
|
288
|
+
log("Shutting down...");
|
|
289
|
+
isShuttingDown = true;
|
|
290
|
+
ws?.close();
|
|
291
|
+
// Wait for active processes to finish
|
|
292
|
+
if (activeProcesses > 0) {
|
|
293
|
+
log(`Waiting for ${activeProcesses} active process(es)...`);
|
|
294
|
+
const check = setInterval(() => {
|
|
295
|
+
if (activeProcesses === 0) {
|
|
296
|
+
clearInterval(check);
|
|
297
|
+
process.exit(0);
|
|
298
|
+
}
|
|
299
|
+
}, 1000);
|
|
300
|
+
// Force exit after 30 seconds
|
|
301
|
+
setTimeout(() => process.exit(1), 30000);
|
|
302
|
+
} else {
|
|
303
|
+
process.exit(0);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
process.on("SIGINT", shutdown);
|
|
308
|
+
process.on("SIGTERM", shutdown);
|
|
309
|
+
|
|
310
|
+
// Start
|
|
311
|
+
log(`AgentHub Daemon starting`);
|
|
312
|
+
log(` Agent: ${AGENT_ID}`);
|
|
313
|
+
log(` Project: ${PROJECT_DIR}`);
|
|
314
|
+
log(` Server: ${API_URL}`);
|
|
315
|
+
log(` Max concurrent: ${MAX_CONCURRENT}`);
|
|
316
|
+
connect();
|