agenthub-multiagent-mcp 1.1.4 → 1.3.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/client.d.ts +102 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +71 -2
- package/dist/client.js.map +1 -1
- package/dist/heartbeat.d.ts +16 -0
- package/dist/heartbeat.d.ts.map +1 -1
- package/dist/heartbeat.js +97 -26
- package/dist/heartbeat.js.map +1 -1
- package/dist/index.js +69 -5
- package/dist/index.js.map +1 -1
- package/dist/state.d.ts +1 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js.map +1 -1
- package/dist/tools/index.d.ts +15 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +234 -51
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/tools.test.js +8 -1
- package/dist/tools/tools.test.js.map +1 -1
- package/dist/websocket.d.ts +66 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +196 -0
- package/dist/websocket.js.map +1 -0
- package/package.json +4 -1
- package/src/client.ts +195 -3
- package/src/heartbeat.ts +108 -25
- package/src/index.ts +78 -5
- package/src/state.ts +1 -0
- package/src/tools/index.ts +277 -62
- package/src/tools/tools.test.ts +8 -1
- package/src/websocket.ts +237 -0
package/src/tools/index.ts
CHANGED
|
@@ -2,49 +2,92 @@
|
|
|
2
2
|
* MCP tool definitions and handlers
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { ApiClient, Message } from "../client.js";
|
|
5
|
+
import { ApiClient, Message, TicketTask, TicketTaskContext, CheckpointResponse } from "../client.js";
|
|
6
6
|
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
7
7
|
import * as state from "../state.js";
|
|
8
|
+
import open from "open";
|
|
9
|
+
|
|
10
|
+
export interface PushedItems {
|
|
11
|
+
tasks: PendingTask[];
|
|
12
|
+
messages: Message[];
|
|
13
|
+
}
|
|
8
14
|
|
|
9
15
|
export interface ToolContext {
|
|
10
16
|
getCurrentAgentId: () => string;
|
|
11
17
|
setCurrentAgentId: (id: string) => void;
|
|
12
18
|
stopHeartbeat: () => void;
|
|
13
19
|
getWorkingDir: () => string;
|
|
20
|
+
getPushedItems: () => PushedItems;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface PendingTask {
|
|
24
|
+
id: string;
|
|
25
|
+
assigned_by: string;
|
|
26
|
+
assigned_by_name?: string;
|
|
27
|
+
task: string;
|
|
28
|
+
priority: string;
|
|
29
|
+
created_at: string;
|
|
14
30
|
}
|
|
15
31
|
|
|
16
32
|
interface UrgentAction {
|
|
17
33
|
required: true;
|
|
18
34
|
instruction: string;
|
|
19
|
-
message
|
|
35
|
+
message?: Message;
|
|
36
|
+
task?: PendingTask;
|
|
20
37
|
}
|
|
21
38
|
|
|
22
39
|
interface WrappedResponse {
|
|
23
40
|
result: unknown;
|
|
24
41
|
pending_messages: Message[];
|
|
25
|
-
|
|
26
|
-
|
|
42
|
+
pending_messages_count: number;
|
|
43
|
+
pending_tasks: PendingTask[];
|
|
44
|
+
pending_tasks_count: number;
|
|
27
45
|
urgent_action: UrgentAction | null;
|
|
28
46
|
}
|
|
29
47
|
|
|
30
48
|
/**
|
|
31
|
-
* Wraps a tool response with pending messages
|
|
32
|
-
* This enables automatic
|
|
49
|
+
* Wraps a tool response with pending messages and tasks
|
|
50
|
+
* This enables automatic delivery without extra API calls
|
|
51
|
+
* Includes items pushed via WebSocket
|
|
33
52
|
*/
|
|
34
|
-
async function
|
|
53
|
+
async function wrapWithPendingItems(
|
|
35
54
|
client: ApiClient,
|
|
36
55
|
agentId: string | null,
|
|
37
|
-
result: unknown
|
|
56
|
+
result: unknown,
|
|
57
|
+
context?: ToolContext
|
|
38
58
|
): Promise<WrappedResponse | unknown> {
|
|
39
59
|
// If not registered, return raw result
|
|
40
60
|
if (!agentId) return result;
|
|
41
61
|
|
|
42
62
|
try {
|
|
43
|
-
//
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
// Get items pushed via WebSocket (if connected)
|
|
64
|
+
const pushedItems = context?.getPushedItems() || { tasks: [], messages: [] };
|
|
65
|
+
|
|
66
|
+
// Fetch unread messages and pending tasks in parallel
|
|
67
|
+
const [inbox, tasksResponse] = await Promise.all([
|
|
68
|
+
client.getInbox(agentId, true, 10),
|
|
69
|
+
client.getPendingTasks(agentId),
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
// Merge pushed items with fetched items (deduplicating by ID)
|
|
73
|
+
const fetchedMessages = inbox.messages || [];
|
|
74
|
+
const fetchedTasks = (tasksResponse.tasks || []) as PendingTask[];
|
|
75
|
+
|
|
76
|
+
// Deduplicate messages
|
|
77
|
+
const messageIds = new Set(fetchedMessages.map((m) => m.id));
|
|
78
|
+
const messages = [
|
|
79
|
+
...fetchedMessages,
|
|
80
|
+
...pushedItems.messages.filter((m) => !messageIds.has(m.id)),
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
// Deduplicate tasks
|
|
84
|
+
const taskIds = new Set(fetchedTasks.map((t) => t.id));
|
|
85
|
+
const tasks = [
|
|
86
|
+
...fetchedTasks,
|
|
87
|
+
...pushedItems.tasks.filter((t) => !taskIds.has(t.id)),
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
// Sort messages: urgent first, then by created_at
|
|
48
91
|
messages.sort((a, b) => {
|
|
49
92
|
const aUrgent = isUrgentMessage(a);
|
|
50
93
|
const bUrgent = isUrgentMessage(b);
|
|
@@ -53,28 +96,40 @@ async function wrapWithMessages(
|
|
|
53
96
|
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
|
|
54
97
|
});
|
|
55
98
|
|
|
56
|
-
//
|
|
57
|
-
const urgentMsg = messages.find(isUrgentMessage);
|
|
58
|
-
|
|
99
|
+
// Determine urgent action - tasks take priority over messages
|
|
59
100
|
let urgentAction: UrgentAction | null = null;
|
|
60
|
-
|
|
101
|
+
|
|
102
|
+
// Check for pending tasks first (they're actionable)
|
|
103
|
+
if (tasks.length > 0) {
|
|
104
|
+
const highPriorityTask = tasks.find((t) => t.priority === "high") || tasks[0];
|
|
61
105
|
urgentAction = {
|
|
62
106
|
required: true,
|
|
63
|
-
instruction:
|
|
64
|
-
|
|
107
|
+
instruction: buildTaskInstruction(highPriorityTask),
|
|
108
|
+
task: highPriorityTask,
|
|
65
109
|
};
|
|
110
|
+
} else {
|
|
111
|
+
// Check for urgent messages
|
|
112
|
+
const urgentMsg = messages.find(isUrgentMessage);
|
|
113
|
+
if (urgentMsg) {
|
|
114
|
+
urgentAction = {
|
|
115
|
+
required: true,
|
|
116
|
+
instruction: buildUrgentInstruction(urgentMsg),
|
|
117
|
+
message: urgentMsg,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
66
120
|
}
|
|
67
121
|
|
|
68
122
|
return {
|
|
69
123
|
result,
|
|
70
124
|
pending_messages: messages,
|
|
71
|
-
|
|
72
|
-
|
|
125
|
+
pending_messages_count: messages.length,
|
|
126
|
+
pending_tasks: tasks,
|
|
127
|
+
pending_tasks_count: tasks.length,
|
|
73
128
|
urgent_action: urgentAction,
|
|
74
129
|
};
|
|
75
130
|
} catch (error) {
|
|
76
|
-
// Don't break tool call if
|
|
77
|
-
console.warn("Failed to fetch
|
|
131
|
+
// Don't break tool call if fetch fails
|
|
132
|
+
console.warn("Failed to fetch pending items for wrapping:", error);
|
|
78
133
|
return result;
|
|
79
134
|
}
|
|
80
135
|
}
|
|
@@ -102,6 +157,18 @@ Details: ${msg.body}
|
|
|
102
157
|
Spawn a subagent with Task tool to research and respond to this query. Use send_message to reply when done.`;
|
|
103
158
|
}
|
|
104
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Build instruction text for pending tasks
|
|
162
|
+
*/
|
|
163
|
+
function buildTaskInstruction(task: PendingTask): string {
|
|
164
|
+
const assignerName = task.assigned_by_name || task.assigned_by;
|
|
165
|
+
return `ACTION REQUIRED: You have a task assigned by ${assignerName}.
|
|
166
|
+
|
|
167
|
+
Task: ${task.task}
|
|
168
|
+
|
|
169
|
+
Use accept_task with task_id="${task.id}" to accept and start working, or decline_task to decline with a reason.`;
|
|
170
|
+
}
|
|
171
|
+
|
|
105
172
|
export function registerTools(): Tool[] {
|
|
106
173
|
return [
|
|
107
174
|
// Registration tools
|
|
@@ -141,6 +208,10 @@ export function registerTools(): Tool[] {
|
|
|
141
208
|
type: "string",
|
|
142
209
|
description: "Project name (optional)",
|
|
143
210
|
},
|
|
211
|
+
task_id: {
|
|
212
|
+
type: "string",
|
|
213
|
+
description: "Optional: ID of a ticket system task to link this work to",
|
|
214
|
+
},
|
|
144
215
|
},
|
|
145
216
|
required: ["task"],
|
|
146
217
|
},
|
|
@@ -439,6 +510,77 @@ export function registerTools(): Tool[] {
|
|
|
439
510
|
required: ["task_id", "reason"],
|
|
440
511
|
},
|
|
441
512
|
},
|
|
513
|
+
|
|
514
|
+
// Ticket system tools (for project management integration)
|
|
515
|
+
{
|
|
516
|
+
name: "get_available_tasks",
|
|
517
|
+
description: "Get tasks from the ticket system that are available for you to claim and work on",
|
|
518
|
+
inputSchema: {
|
|
519
|
+
type: "object",
|
|
520
|
+
properties: {
|
|
521
|
+
status: {
|
|
522
|
+
type: "string",
|
|
523
|
+
description: "Filter by status (default: todo)",
|
|
524
|
+
enum: ["backlog", "todo", "in_progress"],
|
|
525
|
+
},
|
|
526
|
+
project_id: {
|
|
527
|
+
type: "string",
|
|
528
|
+
description: "Filter by project ID (optional)",
|
|
529
|
+
},
|
|
530
|
+
priority: {
|
|
531
|
+
type: "string",
|
|
532
|
+
description: "Filter by priority (optional)",
|
|
533
|
+
enum: ["lowest", "low", "medium", "high", "highest"],
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: "get_task_context",
|
|
540
|
+
description: "Get full context for a ticket task including its parent story and epic",
|
|
541
|
+
inputSchema: {
|
|
542
|
+
type: "object",
|
|
543
|
+
properties: {
|
|
544
|
+
task_id: {
|
|
545
|
+
type: "string",
|
|
546
|
+
description: "ID of the task to get context for",
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
required: ["task_id"],
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
name: "claim_ticket_task",
|
|
554
|
+
description: "Claim a ticket task from the project board to work on",
|
|
555
|
+
inputSchema: {
|
|
556
|
+
type: "object",
|
|
557
|
+
properties: {
|
|
558
|
+
task_id: {
|
|
559
|
+
type: "string",
|
|
560
|
+
description: "ID of the task to claim",
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
required: ["task_id"],
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
name: "reach_checkpoint",
|
|
568
|
+
description: "Signal that you've reached a checkpoint and request human review before continuing",
|
|
569
|
+
inputSchema: {
|
|
570
|
+
type: "object",
|
|
571
|
+
properties: {
|
|
572
|
+
task_id: {
|
|
573
|
+
type: "string",
|
|
574
|
+
description: "ID of the task you're working on",
|
|
575
|
+
},
|
|
576
|
+
description: {
|
|
577
|
+
type: "string",
|
|
578
|
+
description: "Description of what you've accomplished and what you're waiting for review on",
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
required: ["task_id", "description"],
|
|
582
|
+
},
|
|
583
|
+
},
|
|
442
584
|
];
|
|
443
585
|
}
|
|
444
586
|
|
|
@@ -468,18 +610,23 @@ export async function handleToolCall(
|
|
|
468
610
|
} else {
|
|
469
611
|
// Try to reconnect
|
|
470
612
|
try {
|
|
613
|
+
// Use provided name, or fall back to stored name
|
|
614
|
+
const reconnectName = (args.name as string) || existingState.name;
|
|
615
|
+
|
|
471
616
|
const reconnectResult = await client.reconnectAgent(
|
|
472
617
|
existingState.agent_id,
|
|
473
618
|
existingState.token,
|
|
474
619
|
owner,
|
|
475
|
-
args.model as string
|
|
620
|
+
args.model as string,
|
|
621
|
+
reconnectName
|
|
476
622
|
);
|
|
477
623
|
|
|
478
624
|
context.setCurrentAgentId(existingState.agent_id);
|
|
479
625
|
|
|
480
|
-
// Update state with
|
|
626
|
+
// Update state with returned name from server
|
|
481
627
|
state.saveState(workingDir, {
|
|
482
628
|
...existingState,
|
|
629
|
+
name: reconnectResult.name,
|
|
483
630
|
last_task: undefined, // Cleared on reconnect
|
|
484
631
|
});
|
|
485
632
|
|
|
@@ -487,8 +634,8 @@ export async function handleToolCall(
|
|
|
487
634
|
...reconnectResult,
|
|
488
635
|
mode: "reconnected",
|
|
489
636
|
message: reconnectResult.was_offline
|
|
490
|
-
? `Reconnected
|
|
491
|
-
: `Reconnected. You have ${reconnectResult.pending_tasks_count} pending tasks and ${reconnectResult.unread_messages_count} unread messages.`,
|
|
637
|
+
? `Reconnected as ${reconnectResult.name || existingState.agent_id}. You have ${reconnectResult.pending_tasks_count} pending tasks and ${reconnectResult.unread_messages_count} unread messages.`
|
|
638
|
+
: `Reconnected as ${reconnectResult.name || existingState.agent_id}. You have ${reconnectResult.pending_tasks_count} pending tasks and ${reconnectResult.unread_messages_count} unread messages.`,
|
|
492
639
|
};
|
|
493
640
|
} catch (error) {
|
|
494
641
|
// Reconnection failed (invalid token, agent not found, etc.)
|
|
@@ -498,34 +645,68 @@ export async function handleToolCall(
|
|
|
498
645
|
}
|
|
499
646
|
}
|
|
500
647
|
|
|
501
|
-
// Fresh registration
|
|
502
|
-
//
|
|
503
|
-
const
|
|
504
|
-
process.env.CLAUDE_MODEL ||
|
|
505
|
-
"claude-opus-4-5-20251101";
|
|
506
|
-
|
|
507
|
-
const result = await client.registerAgent(
|
|
648
|
+
// Fresh registration - use browser-based flow
|
|
649
|
+
// Step 1: Initialize registration session
|
|
650
|
+
const initResult = await client.initRegistration(
|
|
508
651
|
requestedId,
|
|
509
|
-
args.name as string | undefined
|
|
510
|
-
owner,
|
|
511
|
-
workingDir,
|
|
512
|
-
model
|
|
652
|
+
args.name as string | undefined
|
|
513
653
|
);
|
|
514
654
|
|
|
515
|
-
|
|
655
|
+
// Step 2: Open browser to dashboard
|
|
656
|
+
const dashboardUrl = client.getBaseUrl().replace('/api', '') + initResult.dashboard_url;
|
|
657
|
+
console.error(`Opening browser for agent registration: ${dashboardUrl}`);
|
|
658
|
+
await open(dashboardUrl);
|
|
659
|
+
|
|
660
|
+
// Step 3: Poll for completion (max 10 minutes)
|
|
661
|
+
const pollInterval = 3000; // 3 seconds
|
|
662
|
+
const maxPolls = 200; // 10 minutes
|
|
663
|
+
let pollCount = 0;
|
|
664
|
+
let registrationResult: Awaited<ReturnType<typeof client.pollRegistrationCallback>> | null = null;
|
|
665
|
+
|
|
666
|
+
console.error('Waiting for browser registration to complete...');
|
|
667
|
+
|
|
668
|
+
while (pollCount < maxPolls) {
|
|
669
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
670
|
+
pollCount++;
|
|
671
|
+
|
|
672
|
+
try {
|
|
673
|
+
const pollResult = await client.pollRegistrationCallback(initResult.session_token);
|
|
674
|
+
|
|
675
|
+
if (pollResult.status === 'completed') {
|
|
676
|
+
registrationResult = pollResult;
|
|
677
|
+
break;
|
|
678
|
+
} else if (pollResult.status === 'expired') {
|
|
679
|
+
throw new Error('Registration session expired. Please try again.');
|
|
680
|
+
}
|
|
681
|
+
// status === 'pending' - continue polling
|
|
682
|
+
} catch (error) {
|
|
683
|
+
// Non-fatal errors during polling - continue
|
|
684
|
+
console.warn('Poll error:', error);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (!registrationResult || registrationResult.status !== 'completed') {
|
|
689
|
+
throw new Error('Registration timed out. Please try again.');
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const agentId = registrationResult.agent_id!;
|
|
693
|
+
context.setCurrentAgentId(agentId);
|
|
516
694
|
|
|
517
695
|
// Save state for future reconnection
|
|
518
696
|
state.saveState(workingDir, {
|
|
519
|
-
agent_id:
|
|
697
|
+
agent_id: agentId,
|
|
698
|
+
name: registrationResult.agent_name || agentId,
|
|
520
699
|
owner,
|
|
521
|
-
token:
|
|
522
|
-
registered_at:
|
|
700
|
+
token: registrationResult.connect_token!,
|
|
701
|
+
registered_at: new Date().toISOString(),
|
|
523
702
|
});
|
|
524
703
|
|
|
525
704
|
return {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
705
|
+
agent_id: agentId,
|
|
706
|
+
name: registrationResult.agent_name,
|
|
707
|
+
agent_type: registrationResult.agent_type,
|
|
708
|
+
mode: 'registered',
|
|
709
|
+
message: `Successfully registered as ${registrationResult.agent_name || agentId}. You can now use other AgentHub tools.`,
|
|
529
710
|
};
|
|
530
711
|
}
|
|
531
712
|
|
|
@@ -534,15 +715,16 @@ export async function handleToolCall(
|
|
|
534
715
|
const result = await client.startWork(
|
|
535
716
|
agentId,
|
|
536
717
|
args.task as string,
|
|
537
|
-
args.project as string | undefined
|
|
718
|
+
args.project as string | undefined,
|
|
719
|
+
args.task_id as string | undefined
|
|
538
720
|
);
|
|
539
|
-
return
|
|
721
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
540
722
|
}
|
|
541
723
|
|
|
542
724
|
case "agent_set_status": {
|
|
543
725
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
544
726
|
const result = await client.heartbeat(agentId, args.status as "online" | "busy");
|
|
545
|
-
return
|
|
727
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
546
728
|
}
|
|
547
729
|
|
|
548
730
|
case "agent_disconnect": {
|
|
@@ -563,7 +745,7 @@ export async function handleToolCall(
|
|
|
563
745
|
body: args.body as string,
|
|
564
746
|
priority: args.priority as string | undefined,
|
|
565
747
|
});
|
|
566
|
-
return
|
|
748
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
567
749
|
}
|
|
568
750
|
|
|
569
751
|
case "send_to_channel": {
|
|
@@ -575,7 +757,7 @@ export async function handleToolCall(
|
|
|
575
757
|
subject: args.subject as string,
|
|
576
758
|
body: args.body as string,
|
|
577
759
|
});
|
|
578
|
-
return
|
|
760
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
579
761
|
}
|
|
580
762
|
|
|
581
763
|
case "broadcast": {
|
|
@@ -587,7 +769,7 @@ export async function handleToolCall(
|
|
|
587
769
|
subject: args.subject as string,
|
|
588
770
|
body: args.body as string,
|
|
589
771
|
});
|
|
590
|
-
return
|
|
772
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
591
773
|
}
|
|
592
774
|
|
|
593
775
|
case "check_inbox": {
|
|
@@ -598,41 +780,41 @@ export async function handleToolCall(
|
|
|
598
780
|
|
|
599
781
|
case "mark_read": {
|
|
600
782
|
const result = await client.markRead(args.message_id as string);
|
|
601
|
-
return
|
|
783
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
602
784
|
}
|
|
603
785
|
|
|
604
786
|
case "reply": {
|
|
605
787
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
606
788
|
const result = await client.reply(args.message_id as string, agentId, args.body as string);
|
|
607
|
-
return
|
|
789
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
608
790
|
}
|
|
609
791
|
|
|
610
792
|
// Discovery
|
|
611
793
|
case "list_agents": {
|
|
612
794
|
const result = await client.listAgents(args.status as string | undefined);
|
|
613
|
-
return
|
|
795
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
614
796
|
}
|
|
615
797
|
|
|
616
798
|
case "get_agent": {
|
|
617
799
|
const result = await client.getAgent(args.id as string);
|
|
618
|
-
return
|
|
800
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
619
801
|
}
|
|
620
802
|
|
|
621
803
|
case "list_channels": {
|
|
622
804
|
const result = await client.listChannels();
|
|
623
|
-
return
|
|
805
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
624
806
|
}
|
|
625
807
|
|
|
626
808
|
case "join_channel": {
|
|
627
809
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
628
810
|
const result = await client.joinChannel(args.channel as string, agentId);
|
|
629
|
-
return
|
|
811
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
630
812
|
}
|
|
631
813
|
|
|
632
814
|
case "leave_channel": {
|
|
633
815
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
634
816
|
const result = await client.leaveChannel(args.channel as string, agentId);
|
|
635
|
-
return
|
|
817
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
636
818
|
}
|
|
637
819
|
|
|
638
820
|
// Task completion
|
|
@@ -645,25 +827,58 @@ export async function handleToolCall(
|
|
|
645
827
|
time_spent: args.time_spent as string | undefined,
|
|
646
828
|
next_steps: args.next_steps as string | undefined,
|
|
647
829
|
});
|
|
648
|
-
return
|
|
830
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
649
831
|
}
|
|
650
832
|
|
|
651
833
|
case "get_pending_tasks": {
|
|
652
834
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
653
835
|
const result = await client.getPendingTasks(agentId);
|
|
654
|
-
return
|
|
836
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
655
837
|
}
|
|
656
838
|
|
|
657
839
|
case "accept_task": {
|
|
658
840
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
659
841
|
const result = await client.acceptTask(agentId, args.task_id as string);
|
|
660
|
-
return
|
|
842
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
661
843
|
}
|
|
662
844
|
|
|
663
845
|
case "decline_task": {
|
|
664
846
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
665
847
|
const result = await client.declineTask(agentId, args.task_id as string, args.reason as string);
|
|
666
|
-
return
|
|
848
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Ticket system tools
|
|
852
|
+
case "get_available_tasks": {
|
|
853
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
854
|
+
const result = await client.getAvailableTasks({
|
|
855
|
+
status: args.status as string | undefined,
|
|
856
|
+
project_id: args.project_id as string | undefined,
|
|
857
|
+
priority: args.priority as string | undefined,
|
|
858
|
+
});
|
|
859
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
case "get_task_context": {
|
|
863
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
864
|
+
const result = await client.getTicketTask(args.task_id as string);
|
|
865
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
case "claim_ticket_task": {
|
|
869
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
870
|
+
const result = await client.claimTicketTask(args.task_id as string, agentId);
|
|
871
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
case "reach_checkpoint": {
|
|
875
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
876
|
+
const result = await client.createCheckpoint(
|
|
877
|
+
args.task_id as string,
|
|
878
|
+
agentId,
|
|
879
|
+
args.description as string
|
|
880
|
+
);
|
|
881
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
667
882
|
}
|
|
668
883
|
|
|
669
884
|
default:
|
package/src/tools/tools.test.ts
CHANGED
|
@@ -26,11 +26,15 @@ const mockClient = {
|
|
|
26
26
|
listAgents: vi.fn(),
|
|
27
27
|
sendMessage: vi.fn(),
|
|
28
28
|
getInbox: vi.fn(),
|
|
29
|
+
getPendingTasks: vi.fn(),
|
|
29
30
|
markRead: vi.fn(),
|
|
30
31
|
reply: vi.fn(),
|
|
31
32
|
listChannels: vi.fn(),
|
|
32
33
|
joinChannel: vi.fn(),
|
|
33
34
|
leaveChannel: vi.fn(),
|
|
35
|
+
completeTask: vi.fn(),
|
|
36
|
+
acceptTask: vi.fn(),
|
|
37
|
+
declineTask: vi.fn(),
|
|
34
38
|
} as unknown as ApiClient;
|
|
35
39
|
|
|
36
40
|
// Mock context
|
|
@@ -43,6 +47,7 @@ function createMockContext(): ToolContext {
|
|
|
43
47
|
},
|
|
44
48
|
stopHeartbeat: vi.fn(),
|
|
45
49
|
getWorkingDir: () => "/tmp/test-workdir",
|
|
50
|
+
getPushedItems: () => ({ tasks: [], messages: [] }),
|
|
46
51
|
};
|
|
47
52
|
}
|
|
48
53
|
|
|
@@ -89,14 +94,16 @@ describe("handleToolCall", () => {
|
|
|
89
94
|
beforeEach(() => {
|
|
90
95
|
vi.clearAllMocks();
|
|
91
96
|
context = createMockContext();
|
|
92
|
-
// Default
|
|
97
|
+
// Default mocks for wrapping (used by wrapWithPendingItems)
|
|
93
98
|
vi.mocked(mockClient.getInbox).mockResolvedValue({ messages: [], total: 0 });
|
|
99
|
+
vi.mocked(mockClient.getPendingTasks).mockResolvedValue({ tasks: [], total: 0 });
|
|
94
100
|
});
|
|
95
101
|
|
|
96
102
|
describe("agent_register", () => {
|
|
97
103
|
it("should register agent and set current agent ID", async () => {
|
|
98
104
|
const mockResult = {
|
|
99
105
|
agent_id: "test-agent",
|
|
106
|
+
name: "Test Agent",
|
|
100
107
|
token: "test-token-uuid",
|
|
101
108
|
model: "claude-opus-4.5",
|
|
102
109
|
model_provider: "anthropic",
|