agenthub-multiagent-mcp 1.1.2 → 1.1.4
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/README.md +170 -151
- package/dist/client.d.ts +3 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -1
- package/dist/client.js.map +1 -1
- package/dist/client.test.d.ts +2 -0
- package/dist/client.test.d.ts.map +1 -1
- package/dist/client.test.js +16 -1
- package/dist/client.test.js.map +1 -1
- package/dist/e2e.test.d.ts +1 -0
- package/dist/e2e.test.d.ts.map +1 -1
- package/dist/e2e.test.js +3 -1
- package/dist/e2e.test.js.map +1 -1
- package/dist/heartbeat.d.ts +11 -1
- package/dist/heartbeat.d.ts.map +1 -1
- package/dist/heartbeat.js +41 -3
- package/dist/heartbeat.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +13 -8
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/tools.test.js +6 -1
- package/dist/tools/tools.test.js.map +1 -1
- package/package.json +53 -53
- package/src/client.test.ts +223 -208
- package/src/client.ts +293 -286
- package/src/e2e.test.ts +300 -298
- package/src/heartbeat.ts +45 -3
- package/src/index.ts +123 -123
- package/src/tools/index.ts +672 -666
- package/src/tools/tools.test.ts +522 -517
- package/vitest.config.ts +8 -0
package/src/tools/index.ts
CHANGED
|
@@ -1,666 +1,672 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP tool definitions and handlers
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { ApiClient, Message } from "../client.js";
|
|
6
|
-
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
-
import * as state from "../state.js";
|
|
8
|
-
|
|
9
|
-
export interface ToolContext {
|
|
10
|
-
getCurrentAgentId: () => string;
|
|
11
|
-
setCurrentAgentId: (id: string) => void;
|
|
12
|
-
stopHeartbeat: () => void;
|
|
13
|
-
getWorkingDir: () => string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface UrgentAction {
|
|
17
|
-
required: true;
|
|
18
|
-
instruction: string;
|
|
19
|
-
message: Message;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface WrappedResponse {
|
|
23
|
-
result: unknown;
|
|
24
|
-
pending_messages: Message[];
|
|
25
|
-
pending_count: number;
|
|
26
|
-
has_more: boolean;
|
|
27
|
-
urgent_action: UrgentAction | null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Wraps a tool response with pending messages
|
|
32
|
-
* This enables automatic message delivery without extra API calls
|
|
33
|
-
*/
|
|
34
|
-
async function wrapWithMessages(
|
|
35
|
-
client: ApiClient,
|
|
36
|
-
agentId: string | null,
|
|
37
|
-
result: unknown
|
|
38
|
-
): Promise<WrappedResponse | unknown> {
|
|
39
|
-
// If not registered, return raw result
|
|
40
|
-
if (!agentId) return result;
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
// Fetch unread messages
|
|
44
|
-
const inbox = await client.getInbox(agentId, true, 10);
|
|
45
|
-
const messages = inbox.messages || [];
|
|
46
|
-
|
|
47
|
-
// Sort: urgent first, then by created_at
|
|
48
|
-
messages.sort((a, b) => {
|
|
49
|
-
const aUrgent = isUrgentMessage(a);
|
|
50
|
-
const bUrgent = isUrgentMessage(b);
|
|
51
|
-
if (aUrgent && !bUrgent) return -1;
|
|
52
|
-
if (!aUrgent && bUrgent) return 1;
|
|
53
|
-
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Find urgent message for action
|
|
57
|
-
const urgentMsg = messages.find(isUrgentMessage);
|
|
58
|
-
|
|
59
|
-
let urgentAction: UrgentAction | null = null;
|
|
60
|
-
if (urgentMsg) {
|
|
61
|
-
urgentAction = {
|
|
62
|
-
required: true,
|
|
63
|
-
instruction: buildUrgentInstruction(urgentMsg),
|
|
64
|
-
message: urgentMsg,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
result,
|
|
70
|
-
pending_messages: messages,
|
|
71
|
-
pending_count: inbox.total,
|
|
72
|
-
has_more: inbox.total > messages.length,
|
|
73
|
-
urgent_action: urgentAction,
|
|
74
|
-
};
|
|
75
|
-
} catch (error) {
|
|
76
|
-
// Don't break tool call if message fetch fails
|
|
77
|
-
console.warn("Failed to fetch messages for wrapping:", error);
|
|
78
|
-
return result;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Check if a message is urgent (requires immediate action)
|
|
84
|
-
*/
|
|
85
|
-
function isUrgentMessage(msg: Message): boolean {
|
|
86
|
-
return (
|
|
87
|
-
msg.priority === "urgent" ||
|
|
88
|
-
(msg.priority === "high" && msg.type === "question")
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Build instruction text for urgent messages
|
|
94
|
-
*/
|
|
95
|
-
function buildUrgentInstruction(msg: Message): string {
|
|
96
|
-
return `URGENT: Agent [${msg.from_agent}] needs immediate assistance.
|
|
97
|
-
|
|
98
|
-
Query: ${msg.subject}
|
|
99
|
-
|
|
100
|
-
Details: ${msg.body}
|
|
101
|
-
|
|
102
|
-
Spawn a subagent with Task tool to research and respond to this query. Use send_message to reply when done.`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function registerTools(): Tool[] {
|
|
106
|
-
return [
|
|
107
|
-
// Registration tools
|
|
108
|
-
{
|
|
109
|
-
name: "agent_register",
|
|
110
|
-
description: "Register this agent with AgentHub. Must be called before using other tools.",
|
|
111
|
-
inputSchema: {
|
|
112
|
-
type: "object",
|
|
113
|
-
properties: {
|
|
114
|
-
id: {
|
|
115
|
-
type: "string",
|
|
116
|
-
description: "Unique agent identifier (e.g., 'backend-1', 'tester')",
|
|
117
|
-
},
|
|
118
|
-
name: {
|
|
119
|
-
type: "string",
|
|
120
|
-
description: "Human-friendly name for this agent",
|
|
121
|
-
},
|
|
122
|
-
model: {
|
|
123
|
-
type: "string",
|
|
124
|
-
description: "Model name (e.g., 'claude-opus-4.5', 'gpt-4-turbo', 'o3', 'gemini-2.0-pro'). Provider is auto-detected.",
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
required: ["id", "model"],
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
name: "agent_start_work",
|
|
132
|
-
description: "Declare what you're currently working on. Sends notification to Slack.",
|
|
133
|
-
inputSchema: {
|
|
134
|
-
type: "object",
|
|
135
|
-
properties: {
|
|
136
|
-
task: {
|
|
137
|
-
type: "string",
|
|
138
|
-
description: "Description of the task you're starting",
|
|
139
|
-
},
|
|
140
|
-
project: {
|
|
141
|
-
type: "string",
|
|
142
|
-
description: "Project name (optional)",
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
required: ["task"],
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
name: "agent_set_status",
|
|
150
|
-
description: "Update your status (online or busy)",
|
|
151
|
-
inputSchema: {
|
|
152
|
-
type: "object",
|
|
153
|
-
properties: {
|
|
154
|
-
status: {
|
|
155
|
-
type: "string",
|
|
156
|
-
enum: ["online", "busy"],
|
|
157
|
-
description: "New status",
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
required: ["status"],
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
name: "agent_disconnect",
|
|
165
|
-
description: "Gracefully disconnect from AgentHub",
|
|
166
|
-
inputSchema: {
|
|
167
|
-
type: "object",
|
|
168
|
-
properties: {},
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
|
|
172
|
-
// Messaging tools
|
|
173
|
-
{
|
|
174
|
-
name: "send_message",
|
|
175
|
-
description: "Send a direct message to another agent",
|
|
176
|
-
inputSchema: {
|
|
177
|
-
type: "object",
|
|
178
|
-
properties: {
|
|
179
|
-
to: {
|
|
180
|
-
type: "string",
|
|
181
|
-
description: "Target agent ID",
|
|
182
|
-
},
|
|
183
|
-
type: {
|
|
184
|
-
type: "string",
|
|
185
|
-
enum: ["task", "status", "question", "response", "context"],
|
|
186
|
-
description: "Type of message",
|
|
187
|
-
},
|
|
188
|
-
subject: {
|
|
189
|
-
type: "string",
|
|
190
|
-
description: "Message subject/summary",
|
|
191
|
-
},
|
|
192
|
-
body: {
|
|
193
|
-
type: "string",
|
|
194
|
-
description: "Full message content",
|
|
195
|
-
},
|
|
196
|
-
priority: {
|
|
197
|
-
type: "string",
|
|
198
|
-
enum: ["normal", "high", "urgent"],
|
|
199
|
-
description: "Message priority (default: normal)",
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
required: ["to", "type", "subject", "body"],
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
name: "send_to_channel",
|
|
207
|
-
description: "Send a message to all members of a channel",
|
|
208
|
-
inputSchema: {
|
|
209
|
-
type: "object",
|
|
210
|
-
properties: {
|
|
211
|
-
channel: {
|
|
212
|
-
type: "string",
|
|
213
|
-
description: "Target channel name",
|
|
214
|
-
},
|
|
215
|
-
type: {
|
|
216
|
-
type: "string",
|
|
217
|
-
enum: ["task", "status", "question", "response", "context"],
|
|
218
|
-
description: "Type of message",
|
|
219
|
-
},
|
|
220
|
-
subject: {
|
|
221
|
-
type: "string",
|
|
222
|
-
description: "Message subject/summary",
|
|
223
|
-
},
|
|
224
|
-
body: {
|
|
225
|
-
type: "string",
|
|
226
|
-
description: "Full message content",
|
|
227
|
-
},
|
|
228
|
-
},
|
|
229
|
-
required: ["channel", "type", "subject", "body"],
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
name: "broadcast",
|
|
234
|
-
description: "Send a message to all registered agents",
|
|
235
|
-
inputSchema: {
|
|
236
|
-
type: "object",
|
|
237
|
-
properties: {
|
|
238
|
-
type: {
|
|
239
|
-
type: "string",
|
|
240
|
-
enum: ["task", "status", "question", "response", "context"],
|
|
241
|
-
description: "Type of message",
|
|
242
|
-
},
|
|
243
|
-
subject: {
|
|
244
|
-
type: "string",
|
|
245
|
-
description: "Message subject/summary",
|
|
246
|
-
},
|
|
247
|
-
body: {
|
|
248
|
-
type: "string",
|
|
249
|
-
description: "Full message content",
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
required: ["type", "subject", "body"],
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
name: "check_inbox",
|
|
257
|
-
description: "Check for incoming messages",
|
|
258
|
-
inputSchema: {
|
|
259
|
-
type: "object",
|
|
260
|
-
properties: {
|
|
261
|
-
unread_only: {
|
|
262
|
-
type: "boolean",
|
|
263
|
-
description: "Only return unread messages (default: false)",
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
},
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
name: "mark_read",
|
|
270
|
-
description: "Mark a message as read",
|
|
271
|
-
inputSchema: {
|
|
272
|
-
type: "object",
|
|
273
|
-
properties: {
|
|
274
|
-
message_id: {
|
|
275
|
-
type: "string",
|
|
276
|
-
description: "ID of the message to mark as read",
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
required: ["message_id"],
|
|
280
|
-
},
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
name: "reply",
|
|
284
|
-
description: "Reply to a message",
|
|
285
|
-
inputSchema: {
|
|
286
|
-
type: "object",
|
|
287
|
-
properties: {
|
|
288
|
-
message_id: {
|
|
289
|
-
type: "string",
|
|
290
|
-
description: "ID of the message to reply to",
|
|
291
|
-
},
|
|
292
|
-
body: {
|
|
293
|
-
type: "string",
|
|
294
|
-
description: "Reply content",
|
|
295
|
-
},
|
|
296
|
-
},
|
|
297
|
-
required: ["message_id", "body"],
|
|
298
|
-
},
|
|
299
|
-
},
|
|
300
|
-
|
|
301
|
-
// Discovery tools
|
|
302
|
-
{
|
|
303
|
-
name: "list_agents",
|
|
304
|
-
description: "List all registered agents",
|
|
305
|
-
inputSchema: {
|
|
306
|
-
type: "object",
|
|
307
|
-
properties: {
|
|
308
|
-
status: {
|
|
309
|
-
type: "string",
|
|
310
|
-
enum: ["online", "busy", "offline"],
|
|
311
|
-
description: "Filter by status (optional)",
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
},
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
name: "get_agent",
|
|
318
|
-
description: "Get details about a specific agent",
|
|
319
|
-
inputSchema: {
|
|
320
|
-
type: "object",
|
|
321
|
-
properties: {
|
|
322
|
-
id: {
|
|
323
|
-
type: "string",
|
|
324
|
-
description: "Agent ID to look up",
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
required: ["id"],
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
name: "list_channels",
|
|
332
|
-
description: "List all available channels",
|
|
333
|
-
inputSchema: {
|
|
334
|
-
type: "object",
|
|
335
|
-
properties: {},
|
|
336
|
-
},
|
|
337
|
-
},
|
|
338
|
-
{
|
|
339
|
-
name: "join_channel",
|
|
340
|
-
description: "Subscribe to a channel to receive its messages",
|
|
341
|
-
inputSchema: {
|
|
342
|
-
type: "object",
|
|
343
|
-
properties: {
|
|
344
|
-
channel: {
|
|
345
|
-
type: "string",
|
|
346
|
-
description: "Channel name to join",
|
|
347
|
-
},
|
|
348
|
-
},
|
|
349
|
-
required: ["channel"],
|
|
350
|
-
},
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
name: "leave_channel",
|
|
354
|
-
description: "Unsubscribe from a channel",
|
|
355
|
-
inputSchema: {
|
|
356
|
-
type: "object",
|
|
357
|
-
properties: {
|
|
358
|
-
channel: {
|
|
359
|
-
type: "string",
|
|
360
|
-
description: "Channel name to leave",
|
|
361
|
-
},
|
|
362
|
-
},
|
|
363
|
-
required: ["channel"],
|
|
364
|
-
},
|
|
365
|
-
},
|
|
366
|
-
|
|
367
|
-
// Task completion tools
|
|
368
|
-
{
|
|
369
|
-
name: "agent_complete_task",
|
|
370
|
-
description:
|
|
371
|
-
"Mark current task as complete with a summary. Posts to Slack and notifies team.",
|
|
372
|
-
inputSchema: {
|
|
373
|
-
type: "object",
|
|
374
|
-
properties: {
|
|
375
|
-
summary: {
|
|
376
|
-
type: "string",
|
|
377
|
-
description: "Summary of what was accomplished",
|
|
378
|
-
},
|
|
379
|
-
outcome: {
|
|
380
|
-
type: "string",
|
|
381
|
-
enum: ["completed", "blocked", "partial", "needs_review", "handed_off"],
|
|
382
|
-
description: "Task outcome status",
|
|
383
|
-
},
|
|
384
|
-
files_changed: {
|
|
385
|
-
type: "array",
|
|
386
|
-
items: { type: "string" },
|
|
387
|
-
description: "List of files modified (optional)",
|
|
388
|
-
},
|
|
389
|
-
time_spent: {
|
|
390
|
-
type: "string",
|
|
391
|
-
description: "Override auto-calculated time (optional, e.g., '45 minutes')",
|
|
392
|
-
},
|
|
393
|
-
next_steps: {
|
|
394
|
-
type: "string",
|
|
395
|
-
description:
|
|
396
|
-
"What comes next, can @mention agents for handoff (optional)",
|
|
397
|
-
},
|
|
398
|
-
},
|
|
399
|
-
required: ["summary", "outcome"],
|
|
400
|
-
},
|
|
401
|
-
},
|
|
402
|
-
{
|
|
403
|
-
name: "get_pending_tasks",
|
|
404
|
-
description: "Get tasks assigned to you by other agents",
|
|
405
|
-
inputSchema: {
|
|
406
|
-
type: "object",
|
|
407
|
-
properties: {},
|
|
408
|
-
},
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
name: "accept_task",
|
|
412
|
-
description: "Accept a pending task and start working on it",
|
|
413
|
-
inputSchema: {
|
|
414
|
-
type: "object",
|
|
415
|
-
properties: {
|
|
416
|
-
task_id: {
|
|
417
|
-
type: "string",
|
|
418
|
-
description: "ID of the task to accept",
|
|
419
|
-
},
|
|
420
|
-
},
|
|
421
|
-
required: ["task_id"],
|
|
422
|
-
},
|
|
423
|
-
},
|
|
424
|
-
{
|
|
425
|
-
name: "decline_task",
|
|
426
|
-
description: "Decline a pending task with reason",
|
|
427
|
-
inputSchema: {
|
|
428
|
-
type: "object",
|
|
429
|
-
properties: {
|
|
430
|
-
task_id: {
|
|
431
|
-
type: "string",
|
|
432
|
-
description: "ID of the task to decline",
|
|
433
|
-
},
|
|
434
|
-
reason: {
|
|
435
|
-
type: "string",
|
|
436
|
-
description: "Reason for declining",
|
|
437
|
-
},
|
|
438
|
-
},
|
|
439
|
-
required: ["task_id", "reason"],
|
|
440
|
-
},
|
|
441
|
-
},
|
|
442
|
-
];
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
export async function handleToolCall(
|
|
446
|
-
name: string,
|
|
447
|
-
args: Record<string, unknown>,
|
|
448
|
-
client: ApiClient,
|
|
449
|
-
context: ToolContext
|
|
450
|
-
): Promise<unknown> {
|
|
451
|
-
const agentId = context.getCurrentAgentId();
|
|
452
|
-
|
|
453
|
-
switch (name) {
|
|
454
|
-
// Registration
|
|
455
|
-
case "agent_register": {
|
|
456
|
-
const workingDir = context.getWorkingDir();
|
|
457
|
-
const owner = state.getCurrentOwner();
|
|
458
|
-
const requestedId = args.id as string;
|
|
459
|
-
|
|
460
|
-
// Check for existing state file (reconnection)
|
|
461
|
-
const existingState = state.loadState(workingDir);
|
|
462
|
-
|
|
463
|
-
if (existingState) {
|
|
464
|
-
// Verify owner matches
|
|
465
|
-
if (existingState.owner !== owner) {
|
|
466
|
-
// Different user - delete state and register fresh
|
|
467
|
-
state.deleteState(workingDir);
|
|
468
|
-
} else {
|
|
469
|
-
// Try to reconnect
|
|
470
|
-
try {
|
|
471
|
-
const reconnectResult = await client.reconnectAgent(
|
|
472
|
-
existingState.agent_id,
|
|
473
|
-
existingState.token,
|
|
474
|
-
owner
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
state
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
};
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
return wrapWithMessages(client, agentId, result);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
case "
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
return client
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
case "
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const result = await client.
|
|
601
|
-
return wrapWithMessages(client, agentId, result);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const result = await client.
|
|
607
|
-
return wrapWithMessages(client, agentId, result);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const result = await client.
|
|
623
|
-
return wrapWithMessages(client, agentId, result);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
case "
|
|
627
|
-
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
628
|
-
const result = await client.
|
|
629
|
-
return wrapWithMessages(client, agentId, result);
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
return wrapWithMessages(client, agentId, result);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
case "
|
|
652
|
-
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
653
|
-
const result = await client.
|
|
654
|
-
return wrapWithMessages(client, agentId, result);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
case "
|
|
658
|
-
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
659
|
-
const result = await client.
|
|
660
|
-
return wrapWithMessages(client, agentId, result);
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
throw new Error(
|
|
665
|
-
|
|
666
|
-
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool definitions and handlers
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ApiClient, Message } from "../client.js";
|
|
6
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import * as state from "../state.js";
|
|
8
|
+
|
|
9
|
+
export interface ToolContext {
|
|
10
|
+
getCurrentAgentId: () => string;
|
|
11
|
+
setCurrentAgentId: (id: string) => void;
|
|
12
|
+
stopHeartbeat: () => void;
|
|
13
|
+
getWorkingDir: () => string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface UrgentAction {
|
|
17
|
+
required: true;
|
|
18
|
+
instruction: string;
|
|
19
|
+
message: Message;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface WrappedResponse {
|
|
23
|
+
result: unknown;
|
|
24
|
+
pending_messages: Message[];
|
|
25
|
+
pending_count: number;
|
|
26
|
+
has_more: boolean;
|
|
27
|
+
urgent_action: UrgentAction | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Wraps a tool response with pending messages
|
|
32
|
+
* This enables automatic message delivery without extra API calls
|
|
33
|
+
*/
|
|
34
|
+
async function wrapWithMessages(
|
|
35
|
+
client: ApiClient,
|
|
36
|
+
agentId: string | null,
|
|
37
|
+
result: unknown
|
|
38
|
+
): Promise<WrappedResponse | unknown> {
|
|
39
|
+
// If not registered, return raw result
|
|
40
|
+
if (!agentId) return result;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// Fetch unread messages
|
|
44
|
+
const inbox = await client.getInbox(agentId, true, 10);
|
|
45
|
+
const messages = inbox.messages || [];
|
|
46
|
+
|
|
47
|
+
// Sort: urgent first, then by created_at
|
|
48
|
+
messages.sort((a, b) => {
|
|
49
|
+
const aUrgent = isUrgentMessage(a);
|
|
50
|
+
const bUrgent = isUrgentMessage(b);
|
|
51
|
+
if (aUrgent && !bUrgent) return -1;
|
|
52
|
+
if (!aUrgent && bUrgent) return 1;
|
|
53
|
+
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Find urgent message for action
|
|
57
|
+
const urgentMsg = messages.find(isUrgentMessage);
|
|
58
|
+
|
|
59
|
+
let urgentAction: UrgentAction | null = null;
|
|
60
|
+
if (urgentMsg) {
|
|
61
|
+
urgentAction = {
|
|
62
|
+
required: true,
|
|
63
|
+
instruction: buildUrgentInstruction(urgentMsg),
|
|
64
|
+
message: urgentMsg,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
result,
|
|
70
|
+
pending_messages: messages,
|
|
71
|
+
pending_count: inbox.total,
|
|
72
|
+
has_more: inbox.total > messages.length,
|
|
73
|
+
urgent_action: urgentAction,
|
|
74
|
+
};
|
|
75
|
+
} catch (error) {
|
|
76
|
+
// Don't break tool call if message fetch fails
|
|
77
|
+
console.warn("Failed to fetch messages for wrapping:", error);
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if a message is urgent (requires immediate action)
|
|
84
|
+
*/
|
|
85
|
+
function isUrgentMessage(msg: Message): boolean {
|
|
86
|
+
return (
|
|
87
|
+
msg.priority === "urgent" ||
|
|
88
|
+
(msg.priority === "high" && msg.type === "question")
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Build instruction text for urgent messages
|
|
94
|
+
*/
|
|
95
|
+
function buildUrgentInstruction(msg: Message): string {
|
|
96
|
+
return `URGENT: Agent [${msg.from_agent}] needs immediate assistance.
|
|
97
|
+
|
|
98
|
+
Query: ${msg.subject}
|
|
99
|
+
|
|
100
|
+
Details: ${msg.body}
|
|
101
|
+
|
|
102
|
+
Spawn a subagent with Task tool to research and respond to this query. Use send_message to reply when done.`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function registerTools(): Tool[] {
|
|
106
|
+
return [
|
|
107
|
+
// Registration tools
|
|
108
|
+
{
|
|
109
|
+
name: "agent_register",
|
|
110
|
+
description: "Register this agent with AgentHub. Must be called before using other tools.",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {
|
|
114
|
+
id: {
|
|
115
|
+
type: "string",
|
|
116
|
+
description: "Unique agent identifier (e.g., 'backend-1', 'tester')",
|
|
117
|
+
},
|
|
118
|
+
name: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "Human-friendly name for this agent",
|
|
121
|
+
},
|
|
122
|
+
model: {
|
|
123
|
+
type: "string",
|
|
124
|
+
description: "Model name (e.g., 'claude-opus-4.5', 'gpt-4-turbo', 'o3', 'gemini-2.0-pro'). Provider is auto-detected.",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
required: ["id", "model"],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "agent_start_work",
|
|
132
|
+
description: "Declare what you're currently working on. Sends notification to Slack.",
|
|
133
|
+
inputSchema: {
|
|
134
|
+
type: "object",
|
|
135
|
+
properties: {
|
|
136
|
+
task: {
|
|
137
|
+
type: "string",
|
|
138
|
+
description: "Description of the task you're starting",
|
|
139
|
+
},
|
|
140
|
+
project: {
|
|
141
|
+
type: "string",
|
|
142
|
+
description: "Project name (optional)",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
required: ["task"],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "agent_set_status",
|
|
150
|
+
description: "Update your status (online or busy)",
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
status: {
|
|
155
|
+
type: "string",
|
|
156
|
+
enum: ["online", "busy"],
|
|
157
|
+
description: "New status",
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
required: ["status"],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "agent_disconnect",
|
|
165
|
+
description: "Gracefully disconnect from AgentHub",
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: "object",
|
|
168
|
+
properties: {},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
// Messaging tools
|
|
173
|
+
{
|
|
174
|
+
name: "send_message",
|
|
175
|
+
description: "Send a direct message to another agent",
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: {
|
|
179
|
+
to: {
|
|
180
|
+
type: "string",
|
|
181
|
+
description: "Target agent ID",
|
|
182
|
+
},
|
|
183
|
+
type: {
|
|
184
|
+
type: "string",
|
|
185
|
+
enum: ["task", "status", "question", "response", "context"],
|
|
186
|
+
description: "Type of message",
|
|
187
|
+
},
|
|
188
|
+
subject: {
|
|
189
|
+
type: "string",
|
|
190
|
+
description: "Message subject/summary",
|
|
191
|
+
},
|
|
192
|
+
body: {
|
|
193
|
+
type: "string",
|
|
194
|
+
description: "Full message content",
|
|
195
|
+
},
|
|
196
|
+
priority: {
|
|
197
|
+
type: "string",
|
|
198
|
+
enum: ["normal", "high", "urgent"],
|
|
199
|
+
description: "Message priority (default: normal)",
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
required: ["to", "type", "subject", "body"],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "send_to_channel",
|
|
207
|
+
description: "Send a message to all members of a channel",
|
|
208
|
+
inputSchema: {
|
|
209
|
+
type: "object",
|
|
210
|
+
properties: {
|
|
211
|
+
channel: {
|
|
212
|
+
type: "string",
|
|
213
|
+
description: "Target channel name",
|
|
214
|
+
},
|
|
215
|
+
type: {
|
|
216
|
+
type: "string",
|
|
217
|
+
enum: ["task", "status", "question", "response", "context"],
|
|
218
|
+
description: "Type of message",
|
|
219
|
+
},
|
|
220
|
+
subject: {
|
|
221
|
+
type: "string",
|
|
222
|
+
description: "Message subject/summary",
|
|
223
|
+
},
|
|
224
|
+
body: {
|
|
225
|
+
type: "string",
|
|
226
|
+
description: "Full message content",
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
required: ["channel", "type", "subject", "body"],
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: "broadcast",
|
|
234
|
+
description: "Send a message to all registered agents",
|
|
235
|
+
inputSchema: {
|
|
236
|
+
type: "object",
|
|
237
|
+
properties: {
|
|
238
|
+
type: {
|
|
239
|
+
type: "string",
|
|
240
|
+
enum: ["task", "status", "question", "response", "context"],
|
|
241
|
+
description: "Type of message",
|
|
242
|
+
},
|
|
243
|
+
subject: {
|
|
244
|
+
type: "string",
|
|
245
|
+
description: "Message subject/summary",
|
|
246
|
+
},
|
|
247
|
+
body: {
|
|
248
|
+
type: "string",
|
|
249
|
+
description: "Full message content",
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
required: ["type", "subject", "body"],
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "check_inbox",
|
|
257
|
+
description: "Check for incoming messages",
|
|
258
|
+
inputSchema: {
|
|
259
|
+
type: "object",
|
|
260
|
+
properties: {
|
|
261
|
+
unread_only: {
|
|
262
|
+
type: "boolean",
|
|
263
|
+
description: "Only return unread messages (default: false)",
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: "mark_read",
|
|
270
|
+
description: "Mark a message as read",
|
|
271
|
+
inputSchema: {
|
|
272
|
+
type: "object",
|
|
273
|
+
properties: {
|
|
274
|
+
message_id: {
|
|
275
|
+
type: "string",
|
|
276
|
+
description: "ID of the message to mark as read",
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
required: ["message_id"],
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: "reply",
|
|
284
|
+
description: "Reply to a message",
|
|
285
|
+
inputSchema: {
|
|
286
|
+
type: "object",
|
|
287
|
+
properties: {
|
|
288
|
+
message_id: {
|
|
289
|
+
type: "string",
|
|
290
|
+
description: "ID of the message to reply to",
|
|
291
|
+
},
|
|
292
|
+
body: {
|
|
293
|
+
type: "string",
|
|
294
|
+
description: "Reply content",
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
required: ["message_id", "body"],
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
// Discovery tools
|
|
302
|
+
{
|
|
303
|
+
name: "list_agents",
|
|
304
|
+
description: "List all registered agents",
|
|
305
|
+
inputSchema: {
|
|
306
|
+
type: "object",
|
|
307
|
+
properties: {
|
|
308
|
+
status: {
|
|
309
|
+
type: "string",
|
|
310
|
+
enum: ["online", "busy", "offline"],
|
|
311
|
+
description: "Filter by status (optional)",
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: "get_agent",
|
|
318
|
+
description: "Get details about a specific agent",
|
|
319
|
+
inputSchema: {
|
|
320
|
+
type: "object",
|
|
321
|
+
properties: {
|
|
322
|
+
id: {
|
|
323
|
+
type: "string",
|
|
324
|
+
description: "Agent ID to look up",
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
required: ["id"],
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: "list_channels",
|
|
332
|
+
description: "List all available channels",
|
|
333
|
+
inputSchema: {
|
|
334
|
+
type: "object",
|
|
335
|
+
properties: {},
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
name: "join_channel",
|
|
340
|
+
description: "Subscribe to a channel to receive its messages",
|
|
341
|
+
inputSchema: {
|
|
342
|
+
type: "object",
|
|
343
|
+
properties: {
|
|
344
|
+
channel: {
|
|
345
|
+
type: "string",
|
|
346
|
+
description: "Channel name to join",
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
required: ["channel"],
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: "leave_channel",
|
|
354
|
+
description: "Unsubscribe from a channel",
|
|
355
|
+
inputSchema: {
|
|
356
|
+
type: "object",
|
|
357
|
+
properties: {
|
|
358
|
+
channel: {
|
|
359
|
+
type: "string",
|
|
360
|
+
description: "Channel name to leave",
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
required: ["channel"],
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
// Task completion tools
|
|
368
|
+
{
|
|
369
|
+
name: "agent_complete_task",
|
|
370
|
+
description:
|
|
371
|
+
"Mark current task as complete with a summary. Posts to Slack and notifies team.",
|
|
372
|
+
inputSchema: {
|
|
373
|
+
type: "object",
|
|
374
|
+
properties: {
|
|
375
|
+
summary: {
|
|
376
|
+
type: "string",
|
|
377
|
+
description: "Summary of what was accomplished",
|
|
378
|
+
},
|
|
379
|
+
outcome: {
|
|
380
|
+
type: "string",
|
|
381
|
+
enum: ["completed", "blocked", "partial", "needs_review", "handed_off"],
|
|
382
|
+
description: "Task outcome status",
|
|
383
|
+
},
|
|
384
|
+
files_changed: {
|
|
385
|
+
type: "array",
|
|
386
|
+
items: { type: "string" },
|
|
387
|
+
description: "List of files modified (optional)",
|
|
388
|
+
},
|
|
389
|
+
time_spent: {
|
|
390
|
+
type: "string",
|
|
391
|
+
description: "Override auto-calculated time (optional, e.g., '45 minutes')",
|
|
392
|
+
},
|
|
393
|
+
next_steps: {
|
|
394
|
+
type: "string",
|
|
395
|
+
description:
|
|
396
|
+
"What comes next, can @mention agents for handoff (optional)",
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
required: ["summary", "outcome"],
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
name: "get_pending_tasks",
|
|
404
|
+
description: "Get tasks assigned to you by other agents",
|
|
405
|
+
inputSchema: {
|
|
406
|
+
type: "object",
|
|
407
|
+
properties: {},
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: "accept_task",
|
|
412
|
+
description: "Accept a pending task and start working on it",
|
|
413
|
+
inputSchema: {
|
|
414
|
+
type: "object",
|
|
415
|
+
properties: {
|
|
416
|
+
task_id: {
|
|
417
|
+
type: "string",
|
|
418
|
+
description: "ID of the task to accept",
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
required: ["task_id"],
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: "decline_task",
|
|
426
|
+
description: "Decline a pending task with reason",
|
|
427
|
+
inputSchema: {
|
|
428
|
+
type: "object",
|
|
429
|
+
properties: {
|
|
430
|
+
task_id: {
|
|
431
|
+
type: "string",
|
|
432
|
+
description: "ID of the task to decline",
|
|
433
|
+
},
|
|
434
|
+
reason: {
|
|
435
|
+
type: "string",
|
|
436
|
+
description: "Reason for declining",
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
required: ["task_id", "reason"],
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
];
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export async function handleToolCall(
|
|
446
|
+
name: string,
|
|
447
|
+
args: Record<string, unknown>,
|
|
448
|
+
client: ApiClient,
|
|
449
|
+
context: ToolContext
|
|
450
|
+
): Promise<unknown> {
|
|
451
|
+
const agentId = context.getCurrentAgentId();
|
|
452
|
+
|
|
453
|
+
switch (name) {
|
|
454
|
+
// Registration
|
|
455
|
+
case "agent_register": {
|
|
456
|
+
const workingDir = context.getWorkingDir();
|
|
457
|
+
const owner = state.getCurrentOwner();
|
|
458
|
+
const requestedId = args.id as string;
|
|
459
|
+
|
|
460
|
+
// Check for existing state file (reconnection)
|
|
461
|
+
const existingState = state.loadState(workingDir);
|
|
462
|
+
|
|
463
|
+
if (existingState) {
|
|
464
|
+
// Verify owner matches
|
|
465
|
+
if (existingState.owner !== owner) {
|
|
466
|
+
// Different user - delete state and register fresh
|
|
467
|
+
state.deleteState(workingDir);
|
|
468
|
+
} else {
|
|
469
|
+
// Try to reconnect
|
|
470
|
+
try {
|
|
471
|
+
const reconnectResult = await client.reconnectAgent(
|
|
472
|
+
existingState.agent_id,
|
|
473
|
+
existingState.token,
|
|
474
|
+
owner,
|
|
475
|
+
args.model as string // Pass the requested model to update it
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
context.setCurrentAgentId(existingState.agent_id);
|
|
479
|
+
|
|
480
|
+
// Update state with any new info
|
|
481
|
+
state.saveState(workingDir, {
|
|
482
|
+
...existingState,
|
|
483
|
+
last_task: undefined, // Cleared on reconnect
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
return {
|
|
487
|
+
...reconnectResult,
|
|
488
|
+
mode: "reconnected",
|
|
489
|
+
message: reconnectResult.was_offline
|
|
490
|
+
? `Reconnected after timeout. You have ${reconnectResult.pending_tasks_count} pending tasks and ${reconnectResult.unread_messages_count} unread messages.`
|
|
491
|
+
: `Reconnected. You have ${reconnectResult.pending_tasks_count} pending tasks and ${reconnectResult.unread_messages_count} unread messages.`,
|
|
492
|
+
};
|
|
493
|
+
} catch (error) {
|
|
494
|
+
// Reconnection failed (invalid token, agent not found, etc.)
|
|
495
|
+
console.warn("Reconnection failed, registering fresh:", error);
|
|
496
|
+
state.deleteState(workingDir);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Fresh registration
|
|
502
|
+
// Auto-detect model from environment or use provided value
|
|
503
|
+
const model = args.model as string ||
|
|
504
|
+
process.env.CLAUDE_MODEL ||
|
|
505
|
+
"claude-opus-4-5-20251101";
|
|
506
|
+
|
|
507
|
+
const result = await client.registerAgent(
|
|
508
|
+
requestedId,
|
|
509
|
+
args.name as string | undefined,
|
|
510
|
+
owner,
|
|
511
|
+
workingDir,
|
|
512
|
+
model
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
context.setCurrentAgentId(requestedId);
|
|
516
|
+
|
|
517
|
+
// Save state for future reconnection
|
|
518
|
+
state.saveState(workingDir, {
|
|
519
|
+
agent_id: requestedId,
|
|
520
|
+
owner,
|
|
521
|
+
token: result.token,
|
|
522
|
+
registered_at: result.registered_at,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
return {
|
|
526
|
+
...result,
|
|
527
|
+
mode: "registered",
|
|
528
|
+
message: `Registered as ${requestedId}. You have ${result.pending_tasks_count} pending tasks and ${result.unread_messages_count} unread messages.`,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
case "agent_start_work": {
|
|
533
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
534
|
+
const result = await client.startWork(
|
|
535
|
+
agentId,
|
|
536
|
+
args.task as string,
|
|
537
|
+
args.project as string | undefined
|
|
538
|
+
);
|
|
539
|
+
return wrapWithMessages(client, agentId, result);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
case "agent_set_status": {
|
|
543
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
544
|
+
const result = await client.heartbeat(agentId, args.status as "online" | "busy");
|
|
545
|
+
return wrapWithMessages(client, agentId, result);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
case "agent_disconnect": {
|
|
549
|
+
// No wrapping - agent is going offline
|
|
550
|
+
if (!agentId) throw new Error("Not registered.");
|
|
551
|
+
context.stopHeartbeat();
|
|
552
|
+
return client.disconnect(agentId);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Messaging
|
|
556
|
+
case "send_message": {
|
|
557
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
558
|
+
const result = await client.sendMessage({
|
|
559
|
+
from_agent: agentId,
|
|
560
|
+
to_agent: args.to as string,
|
|
561
|
+
type: args.type as string,
|
|
562
|
+
subject: args.subject as string,
|
|
563
|
+
body: args.body as string,
|
|
564
|
+
priority: args.priority as string | undefined,
|
|
565
|
+
});
|
|
566
|
+
return wrapWithMessages(client, agentId, result);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
case "send_to_channel": {
|
|
570
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
571
|
+
const result = await client.sendMessage({
|
|
572
|
+
from_agent: agentId,
|
|
573
|
+
to_channel: args.channel as string,
|
|
574
|
+
type: args.type as string,
|
|
575
|
+
subject: args.subject as string,
|
|
576
|
+
body: args.body as string,
|
|
577
|
+
});
|
|
578
|
+
return wrapWithMessages(client, agentId, result);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
case "broadcast": {
|
|
582
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
583
|
+
const result = await client.sendMessage({
|
|
584
|
+
from_agent: agentId,
|
|
585
|
+
broadcast: true,
|
|
586
|
+
type: args.type as string,
|
|
587
|
+
subject: args.subject as string,
|
|
588
|
+
body: args.body as string,
|
|
589
|
+
});
|
|
590
|
+
return wrapWithMessages(client, agentId, result);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
case "check_inbox": {
|
|
594
|
+
// No wrapping - already fetching messages
|
|
595
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
596
|
+
return client.getInbox(agentId, args.unread_only as boolean | undefined);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
case "mark_read": {
|
|
600
|
+
const result = await client.markRead(args.message_id as string);
|
|
601
|
+
return wrapWithMessages(client, agentId, result);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
case "reply": {
|
|
605
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
606
|
+
const result = await client.reply(args.message_id as string, agentId, args.body as string);
|
|
607
|
+
return wrapWithMessages(client, agentId, result);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Discovery
|
|
611
|
+
case "list_agents": {
|
|
612
|
+
const result = await client.listAgents(args.status as string | undefined);
|
|
613
|
+
return wrapWithMessages(client, agentId, result);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
case "get_agent": {
|
|
617
|
+
const result = await client.getAgent(args.id as string);
|
|
618
|
+
return wrapWithMessages(client, agentId, result);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
case "list_channels": {
|
|
622
|
+
const result = await client.listChannels();
|
|
623
|
+
return wrapWithMessages(client, agentId, result);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
case "join_channel": {
|
|
627
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
628
|
+
const result = await client.joinChannel(args.channel as string, agentId);
|
|
629
|
+
return wrapWithMessages(client, agentId, result);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
case "leave_channel": {
|
|
633
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
634
|
+
const result = await client.leaveChannel(args.channel as string, agentId);
|
|
635
|
+
return wrapWithMessages(client, agentId, result);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Task completion
|
|
639
|
+
case "agent_complete_task": {
|
|
640
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
641
|
+
const result = await client.completeTask(agentId, {
|
|
642
|
+
summary: args.summary as string,
|
|
643
|
+
outcome: args.outcome as "completed" | "blocked" | "partial" | "needs_review" | "handed_off",
|
|
644
|
+
files_changed: args.files_changed as string[] | undefined,
|
|
645
|
+
time_spent: args.time_spent as string | undefined,
|
|
646
|
+
next_steps: args.next_steps as string | undefined,
|
|
647
|
+
});
|
|
648
|
+
return wrapWithMessages(client, agentId, result);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
case "get_pending_tasks": {
|
|
652
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
653
|
+
const result = await client.getPendingTasks(agentId);
|
|
654
|
+
return wrapWithMessages(client, agentId, result);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
case "accept_task": {
|
|
658
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
659
|
+
const result = await client.acceptTask(agentId, args.task_id as string);
|
|
660
|
+
return wrapWithMessages(client, agentId, result);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
case "decline_task": {
|
|
664
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
665
|
+
const result = await client.declineTask(agentId, args.task_id as string, args.reason as string);
|
|
666
|
+
return wrapWithMessages(client, agentId, result);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
default:
|
|
670
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
671
|
+
}
|
|
672
|
+
}
|