agenthub-multiagent-mcp 1.1.5 → 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 +99 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +69 -1
- 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 +68 -4
- package/dist/index.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 +228 -48
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/tools.test.js +7 -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 +190 -2
- package/src/heartbeat.ts +108 -25
- package/src/index.ts +77 -4
- package/src/tools/index.ts +269 -60
- package/src/tools/tools.test.ts +7 -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
|
|
|
@@ -503,35 +645,68 @@ export async function handleToolCall(
|
|
|
503
645
|
}
|
|
504
646
|
}
|
|
505
647
|
|
|
506
|
-
// Fresh registration
|
|
507
|
-
//
|
|
508
|
-
const
|
|
509
|
-
process.env.CLAUDE_MODEL ||
|
|
510
|
-
"claude-opus-4-5-20251101";
|
|
511
|
-
|
|
512
|
-
const result = await client.registerAgent(
|
|
648
|
+
// Fresh registration - use browser-based flow
|
|
649
|
+
// Step 1: Initialize registration session
|
|
650
|
+
const initResult = await client.initRegistration(
|
|
513
651
|
requestedId,
|
|
514
|
-
args.name as string | undefined
|
|
515
|
-
owner,
|
|
516
|
-
workingDir,
|
|
517
|
-
model
|
|
652
|
+
args.name as string | undefined
|
|
518
653
|
);
|
|
519
654
|
|
|
520
|
-
|
|
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);
|
|
521
694
|
|
|
522
|
-
// Save state for future reconnection
|
|
695
|
+
// Save state for future reconnection
|
|
523
696
|
state.saveState(workingDir, {
|
|
524
|
-
agent_id:
|
|
525
|
-
name:
|
|
697
|
+
agent_id: agentId,
|
|
698
|
+
name: registrationResult.agent_name || agentId,
|
|
526
699
|
owner,
|
|
527
|
-
token:
|
|
528
|
-
registered_at:
|
|
700
|
+
token: registrationResult.connect_token!,
|
|
701
|
+
registered_at: new Date().toISOString(),
|
|
529
702
|
});
|
|
530
703
|
|
|
531
704
|
return {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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.`,
|
|
535
710
|
};
|
|
536
711
|
}
|
|
537
712
|
|
|
@@ -540,15 +715,16 @@ export async function handleToolCall(
|
|
|
540
715
|
const result = await client.startWork(
|
|
541
716
|
agentId,
|
|
542
717
|
args.task as string,
|
|
543
|
-
args.project as string | undefined
|
|
718
|
+
args.project as string | undefined,
|
|
719
|
+
args.task_id as string | undefined
|
|
544
720
|
);
|
|
545
|
-
return
|
|
721
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
546
722
|
}
|
|
547
723
|
|
|
548
724
|
case "agent_set_status": {
|
|
549
725
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
550
726
|
const result = await client.heartbeat(agentId, args.status as "online" | "busy");
|
|
551
|
-
return
|
|
727
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
552
728
|
}
|
|
553
729
|
|
|
554
730
|
case "agent_disconnect": {
|
|
@@ -569,7 +745,7 @@ export async function handleToolCall(
|
|
|
569
745
|
body: args.body as string,
|
|
570
746
|
priority: args.priority as string | undefined,
|
|
571
747
|
});
|
|
572
|
-
return
|
|
748
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
573
749
|
}
|
|
574
750
|
|
|
575
751
|
case "send_to_channel": {
|
|
@@ -581,7 +757,7 @@ export async function handleToolCall(
|
|
|
581
757
|
subject: args.subject as string,
|
|
582
758
|
body: args.body as string,
|
|
583
759
|
});
|
|
584
|
-
return
|
|
760
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
585
761
|
}
|
|
586
762
|
|
|
587
763
|
case "broadcast": {
|
|
@@ -593,7 +769,7 @@ export async function handleToolCall(
|
|
|
593
769
|
subject: args.subject as string,
|
|
594
770
|
body: args.body as string,
|
|
595
771
|
});
|
|
596
|
-
return
|
|
772
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
597
773
|
}
|
|
598
774
|
|
|
599
775
|
case "check_inbox": {
|
|
@@ -604,41 +780,41 @@ export async function handleToolCall(
|
|
|
604
780
|
|
|
605
781
|
case "mark_read": {
|
|
606
782
|
const result = await client.markRead(args.message_id as string);
|
|
607
|
-
return
|
|
783
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
608
784
|
}
|
|
609
785
|
|
|
610
786
|
case "reply": {
|
|
611
787
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
612
788
|
const result = await client.reply(args.message_id as string, agentId, args.body as string);
|
|
613
|
-
return
|
|
789
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
614
790
|
}
|
|
615
791
|
|
|
616
792
|
// Discovery
|
|
617
793
|
case "list_agents": {
|
|
618
794
|
const result = await client.listAgents(args.status as string | undefined);
|
|
619
|
-
return
|
|
795
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
620
796
|
}
|
|
621
797
|
|
|
622
798
|
case "get_agent": {
|
|
623
799
|
const result = await client.getAgent(args.id as string);
|
|
624
|
-
return
|
|
800
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
625
801
|
}
|
|
626
802
|
|
|
627
803
|
case "list_channels": {
|
|
628
804
|
const result = await client.listChannels();
|
|
629
|
-
return
|
|
805
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
630
806
|
}
|
|
631
807
|
|
|
632
808
|
case "join_channel": {
|
|
633
809
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
634
810
|
const result = await client.joinChannel(args.channel as string, agentId);
|
|
635
|
-
return
|
|
811
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
636
812
|
}
|
|
637
813
|
|
|
638
814
|
case "leave_channel": {
|
|
639
815
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
640
816
|
const result = await client.leaveChannel(args.channel as string, agentId);
|
|
641
|
-
return
|
|
817
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
642
818
|
}
|
|
643
819
|
|
|
644
820
|
// Task completion
|
|
@@ -651,25 +827,58 @@ export async function handleToolCall(
|
|
|
651
827
|
time_spent: args.time_spent as string | undefined,
|
|
652
828
|
next_steps: args.next_steps as string | undefined,
|
|
653
829
|
});
|
|
654
|
-
return
|
|
830
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
655
831
|
}
|
|
656
832
|
|
|
657
833
|
case "get_pending_tasks": {
|
|
658
834
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
659
835
|
const result = await client.getPendingTasks(agentId);
|
|
660
|
-
return
|
|
836
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
661
837
|
}
|
|
662
838
|
|
|
663
839
|
case "accept_task": {
|
|
664
840
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
665
841
|
const result = await client.acceptTask(agentId, args.task_id as string);
|
|
666
|
-
return
|
|
842
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
667
843
|
}
|
|
668
844
|
|
|
669
845
|
case "decline_task": {
|
|
670
846
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
671
847
|
const result = await client.declineTask(agentId, args.task_id as string, args.reason as string);
|
|
672
|
-
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);
|
|
673
882
|
}
|
|
674
883
|
|
|
675
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,8 +94,9 @@ 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", () => {
|