agenthub-multiagent-mcp 1.1.5 → 1.4.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/README.md +25 -3
- package/dist/client.d.ts +162 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +139 -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/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 +458 -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 +6 -1
- package/src/client.ts +318 -2
- package/src/heartbeat.ts +108 -25
- package/src/index.ts +77 -4
- package/src/state.ts +1 -0
- package/src/tools/index.ts +505 -59
- 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
|
-
|
|
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
|
+
];
|
|
46
82
|
|
|
47
|
-
//
|
|
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,230 @@ 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
|
+
required_type: {
|
|
536
|
+
type: "string",
|
|
537
|
+
description: "Filter by required skill type - matches your agent type",
|
|
538
|
+
enum: ["frontend", "backend", "android", "ios", "ai", "design-specs"],
|
|
539
|
+
},
|
|
540
|
+
match_my_type: {
|
|
541
|
+
type: "boolean",
|
|
542
|
+
description: "If true, only show tasks matching your registered agent type",
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: "get_task_context",
|
|
549
|
+
description: "Get full context for a ticket task including its parent story and epic",
|
|
550
|
+
inputSchema: {
|
|
551
|
+
type: "object",
|
|
552
|
+
properties: {
|
|
553
|
+
task_id: {
|
|
554
|
+
type: "string",
|
|
555
|
+
description: "ID of the task to get context for",
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
required: ["task_id"],
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
name: "claim_ticket_task",
|
|
563
|
+
description: "Claim a ticket task from the project board to work on",
|
|
564
|
+
inputSchema: {
|
|
565
|
+
type: "object",
|
|
566
|
+
properties: {
|
|
567
|
+
task_id: {
|
|
568
|
+
type: "string",
|
|
569
|
+
description: "ID of the task to claim",
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
required: ["task_id"],
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
name: "reach_checkpoint",
|
|
577
|
+
description: "Signal that you've reached a checkpoint and request human review before continuing",
|
|
578
|
+
inputSchema: {
|
|
579
|
+
type: "object",
|
|
580
|
+
properties: {
|
|
581
|
+
task_id: {
|
|
582
|
+
type: "string",
|
|
583
|
+
description: "ID of the task you're working on",
|
|
584
|
+
},
|
|
585
|
+
description: {
|
|
586
|
+
type: "string",
|
|
587
|
+
description: "Description of what you've accomplished and what you're waiting for review on",
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
required: ["task_id", "description"],
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
|
|
594
|
+
// Ticket creation tools
|
|
595
|
+
{
|
|
596
|
+
name: "create_epic",
|
|
597
|
+
description: "Create an epic (large body of work) in a project",
|
|
598
|
+
inputSchema: {
|
|
599
|
+
type: "object",
|
|
600
|
+
properties: {
|
|
601
|
+
project_id: {
|
|
602
|
+
type: "string",
|
|
603
|
+
description: "Project ID to create epic in",
|
|
604
|
+
},
|
|
605
|
+
title: {
|
|
606
|
+
type: "string",
|
|
607
|
+
description: "Epic title",
|
|
608
|
+
},
|
|
609
|
+
description: {
|
|
610
|
+
type: "string",
|
|
611
|
+
description: "Epic description (supports markdown)",
|
|
612
|
+
},
|
|
613
|
+
source_file: {
|
|
614
|
+
type: "string",
|
|
615
|
+
description: "Optional: path to OpenSpec file this came from",
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
required: ["project_id", "title"],
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
name: "create_story",
|
|
623
|
+
description: "Create a story under an epic",
|
|
624
|
+
inputSchema: {
|
|
625
|
+
type: "object",
|
|
626
|
+
properties: {
|
|
627
|
+
project_id: {
|
|
628
|
+
type: "string",
|
|
629
|
+
description: "Project ID",
|
|
630
|
+
},
|
|
631
|
+
epic_id: {
|
|
632
|
+
type: "string",
|
|
633
|
+
description: "Epic ID to add story to",
|
|
634
|
+
},
|
|
635
|
+
title: {
|
|
636
|
+
type: "string",
|
|
637
|
+
description: "Story title",
|
|
638
|
+
},
|
|
639
|
+
description: {
|
|
640
|
+
type: "string",
|
|
641
|
+
description: "Story description",
|
|
642
|
+
},
|
|
643
|
+
required_type: {
|
|
644
|
+
type: "string",
|
|
645
|
+
enum: ["frontend", "backend", "android", "ios", "ai", "design-specs"],
|
|
646
|
+
description: "Required skill type for this story",
|
|
647
|
+
},
|
|
648
|
+
labels: {
|
|
649
|
+
type: "array",
|
|
650
|
+
items: { type: "string" },
|
|
651
|
+
description: "Additional labels",
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
required: ["project_id", "title"],
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
name: "create_task",
|
|
659
|
+
description: "Create a task under a story",
|
|
660
|
+
inputSchema: {
|
|
661
|
+
type: "object",
|
|
662
|
+
properties: {
|
|
663
|
+
project_id: {
|
|
664
|
+
type: "string",
|
|
665
|
+
description: "Project ID",
|
|
666
|
+
},
|
|
667
|
+
story_id: {
|
|
668
|
+
type: "string",
|
|
669
|
+
description: "Story ID to add task to",
|
|
670
|
+
},
|
|
671
|
+
title: {
|
|
672
|
+
type: "string",
|
|
673
|
+
description: "Task title",
|
|
674
|
+
},
|
|
675
|
+
description: {
|
|
676
|
+
type: "string",
|
|
677
|
+
description: "Task description",
|
|
678
|
+
},
|
|
679
|
+
required_type: {
|
|
680
|
+
type: "string",
|
|
681
|
+
enum: ["frontend", "backend", "android", "ios", "ai", "design-specs"],
|
|
682
|
+
description: "Required skill type",
|
|
683
|
+
},
|
|
684
|
+
},
|
|
685
|
+
required: ["project_id", "title"],
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
name: "upload_attachment",
|
|
690
|
+
description: "Upload a file (screenshot, log, etc.) as proof of work to a ticket",
|
|
691
|
+
inputSchema: {
|
|
692
|
+
type: "object",
|
|
693
|
+
properties: {
|
|
694
|
+
ticket_id: {
|
|
695
|
+
type: "string",
|
|
696
|
+
description: "Ticket ID to attach file to",
|
|
697
|
+
},
|
|
698
|
+
ticket_type: {
|
|
699
|
+
type: "string",
|
|
700
|
+
enum: ["epic", "story", "task"],
|
|
701
|
+
description: "Type of ticket",
|
|
702
|
+
},
|
|
703
|
+
ticket_key: {
|
|
704
|
+
type: "string",
|
|
705
|
+
description: "Ticket key (e.g., PROJ-T42)",
|
|
706
|
+
},
|
|
707
|
+
file_path: {
|
|
708
|
+
type: "string",
|
|
709
|
+
description: "Local path to file to upload",
|
|
710
|
+
},
|
|
711
|
+
description: {
|
|
712
|
+
type: "string",
|
|
713
|
+
description: "Optional description of the file",
|
|
714
|
+
},
|
|
715
|
+
},
|
|
716
|
+
required: ["ticket_id", "ticket_type", "ticket_key", "file_path"],
|
|
717
|
+
},
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
name: "add_comment",
|
|
721
|
+
description: "Add a comment to any ticket (epic, story, task)",
|
|
722
|
+
inputSchema: {
|
|
723
|
+
type: "object",
|
|
724
|
+
properties: {
|
|
725
|
+
ticket_id: {
|
|
726
|
+
type: "string",
|
|
727
|
+
description: "Ticket ID to comment on",
|
|
728
|
+
},
|
|
729
|
+
body: {
|
|
730
|
+
type: "string",
|
|
731
|
+
description: "Comment body (supports markdown)",
|
|
732
|
+
},
|
|
733
|
+
},
|
|
734
|
+
required: ["ticket_id", "body"],
|
|
735
|
+
},
|
|
736
|
+
},
|
|
442
737
|
];
|
|
443
738
|
}
|
|
444
739
|
|
|
@@ -503,35 +798,69 @@ export async function handleToolCall(
|
|
|
503
798
|
}
|
|
504
799
|
}
|
|
505
800
|
|
|
506
|
-
// Fresh registration
|
|
507
|
-
//
|
|
508
|
-
const
|
|
509
|
-
process.env.CLAUDE_MODEL ||
|
|
510
|
-
"claude-opus-4-5-20251101";
|
|
511
|
-
|
|
512
|
-
const result = await client.registerAgent(
|
|
801
|
+
// Fresh registration - use browser-based flow
|
|
802
|
+
// Step 1: Initialize registration session
|
|
803
|
+
const initResult = await client.initRegistration(
|
|
513
804
|
requestedId,
|
|
514
|
-
args.name as string | undefined
|
|
515
|
-
owner,
|
|
516
|
-
workingDir,
|
|
517
|
-
model
|
|
805
|
+
args.name as string | undefined
|
|
518
806
|
);
|
|
519
807
|
|
|
520
|
-
|
|
808
|
+
// Step 2: Open browser to dashboard
|
|
809
|
+
const dashboardUrl = client.getBaseUrl().replace('/api', '') + initResult.dashboard_url;
|
|
810
|
+
console.error(`Opening browser for agent registration: ${dashboardUrl}`);
|
|
811
|
+
await open(dashboardUrl);
|
|
812
|
+
|
|
813
|
+
// Step 3: Poll for completion (max 10 minutes)
|
|
814
|
+
const pollInterval = 3000; // 3 seconds
|
|
815
|
+
const maxPolls = 200; // 10 minutes
|
|
816
|
+
let pollCount = 0;
|
|
817
|
+
let registrationResult: Awaited<ReturnType<typeof client.pollRegistrationCallback>> | null = null;
|
|
818
|
+
|
|
819
|
+
console.error('Waiting for browser registration to complete...');
|
|
820
|
+
|
|
821
|
+
while (pollCount < maxPolls) {
|
|
822
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
823
|
+
pollCount++;
|
|
824
|
+
|
|
825
|
+
try {
|
|
826
|
+
const pollResult = await client.pollRegistrationCallback(initResult.session_token);
|
|
827
|
+
|
|
828
|
+
if (pollResult.status === 'completed') {
|
|
829
|
+
registrationResult = pollResult;
|
|
830
|
+
break;
|
|
831
|
+
} else if (pollResult.status === 'expired') {
|
|
832
|
+
throw new Error('Registration session expired. Please try again.');
|
|
833
|
+
}
|
|
834
|
+
// status === 'pending' - continue polling
|
|
835
|
+
} catch (error) {
|
|
836
|
+
// Non-fatal errors during polling - continue
|
|
837
|
+
console.warn('Poll error:', error);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (!registrationResult || registrationResult.status !== 'completed') {
|
|
842
|
+
throw new Error('Registration timed out. Please try again.');
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const agentId = registrationResult.agent_id!;
|
|
846
|
+
context.setCurrentAgentId(agentId);
|
|
521
847
|
|
|
522
|
-
// Save state for future reconnection
|
|
848
|
+
// Save state for future reconnection
|
|
523
849
|
state.saveState(workingDir, {
|
|
524
|
-
agent_id:
|
|
525
|
-
name:
|
|
850
|
+
agent_id: agentId,
|
|
851
|
+
name: registrationResult.agent_name || agentId,
|
|
526
852
|
owner,
|
|
527
|
-
token:
|
|
528
|
-
|
|
853
|
+
token: registrationResult.connect_token!,
|
|
854
|
+
agent_type: registrationResult.agent_type,
|
|
855
|
+
registered_at: new Date().toISOString(),
|
|
529
856
|
});
|
|
530
857
|
|
|
531
858
|
return {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
859
|
+
agent_id: agentId,
|
|
860
|
+
name: registrationResult.agent_name,
|
|
861
|
+
agent_type: registrationResult.agent_type,
|
|
862
|
+
mode: 'registered',
|
|
863
|
+
message: `Successfully registered as ${registrationResult.agent_name || agentId}. You can now use other AgentHub tools.`,
|
|
535
864
|
};
|
|
536
865
|
}
|
|
537
866
|
|
|
@@ -540,15 +869,16 @@ export async function handleToolCall(
|
|
|
540
869
|
const result = await client.startWork(
|
|
541
870
|
agentId,
|
|
542
871
|
args.task as string,
|
|
543
|
-
args.project as string | undefined
|
|
872
|
+
args.project as string | undefined,
|
|
873
|
+
args.task_id as string | undefined
|
|
544
874
|
);
|
|
545
|
-
return
|
|
875
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
546
876
|
}
|
|
547
877
|
|
|
548
878
|
case "agent_set_status": {
|
|
549
879
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
550
880
|
const result = await client.heartbeat(agentId, args.status as "online" | "busy");
|
|
551
|
-
return
|
|
881
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
552
882
|
}
|
|
553
883
|
|
|
554
884
|
case "agent_disconnect": {
|
|
@@ -569,7 +899,7 @@ export async function handleToolCall(
|
|
|
569
899
|
body: args.body as string,
|
|
570
900
|
priority: args.priority as string | undefined,
|
|
571
901
|
});
|
|
572
|
-
return
|
|
902
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
573
903
|
}
|
|
574
904
|
|
|
575
905
|
case "send_to_channel": {
|
|
@@ -581,7 +911,7 @@ export async function handleToolCall(
|
|
|
581
911
|
subject: args.subject as string,
|
|
582
912
|
body: args.body as string,
|
|
583
913
|
});
|
|
584
|
-
return
|
|
914
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
585
915
|
}
|
|
586
916
|
|
|
587
917
|
case "broadcast": {
|
|
@@ -593,7 +923,7 @@ export async function handleToolCall(
|
|
|
593
923
|
subject: args.subject as string,
|
|
594
924
|
body: args.body as string,
|
|
595
925
|
});
|
|
596
|
-
return
|
|
926
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
597
927
|
}
|
|
598
928
|
|
|
599
929
|
case "check_inbox": {
|
|
@@ -604,41 +934,41 @@ export async function handleToolCall(
|
|
|
604
934
|
|
|
605
935
|
case "mark_read": {
|
|
606
936
|
const result = await client.markRead(args.message_id as string);
|
|
607
|
-
return
|
|
937
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
608
938
|
}
|
|
609
939
|
|
|
610
940
|
case "reply": {
|
|
611
941
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
612
942
|
const result = await client.reply(args.message_id as string, agentId, args.body as string);
|
|
613
|
-
return
|
|
943
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
614
944
|
}
|
|
615
945
|
|
|
616
946
|
// Discovery
|
|
617
947
|
case "list_agents": {
|
|
618
948
|
const result = await client.listAgents(args.status as string | undefined);
|
|
619
|
-
return
|
|
949
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
620
950
|
}
|
|
621
951
|
|
|
622
952
|
case "get_agent": {
|
|
623
953
|
const result = await client.getAgent(args.id as string);
|
|
624
|
-
return
|
|
954
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
625
955
|
}
|
|
626
956
|
|
|
627
957
|
case "list_channels": {
|
|
628
958
|
const result = await client.listChannels();
|
|
629
|
-
return
|
|
959
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
630
960
|
}
|
|
631
961
|
|
|
632
962
|
case "join_channel": {
|
|
633
963
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
634
964
|
const result = await client.joinChannel(args.channel as string, agentId);
|
|
635
|
-
return
|
|
965
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
636
966
|
}
|
|
637
967
|
|
|
638
968
|
case "leave_channel": {
|
|
639
969
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
640
970
|
const result = await client.leaveChannel(args.channel as string, agentId);
|
|
641
|
-
return
|
|
971
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
642
972
|
}
|
|
643
973
|
|
|
644
974
|
// Task completion
|
|
@@ -651,25 +981,141 @@ export async function handleToolCall(
|
|
|
651
981
|
time_spent: args.time_spent as string | undefined,
|
|
652
982
|
next_steps: args.next_steps as string | undefined,
|
|
653
983
|
});
|
|
654
|
-
return
|
|
984
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
655
985
|
}
|
|
656
986
|
|
|
657
987
|
case "get_pending_tasks": {
|
|
658
988
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
659
989
|
const result = await client.getPendingTasks(agentId);
|
|
660
|
-
return
|
|
990
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
661
991
|
}
|
|
662
992
|
|
|
663
993
|
case "accept_task": {
|
|
664
994
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
665
995
|
const result = await client.acceptTask(agentId, args.task_id as string);
|
|
666
|
-
return
|
|
996
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
667
997
|
}
|
|
668
998
|
|
|
669
999
|
case "decline_task": {
|
|
670
1000
|
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
671
1001
|
const result = await client.declineTask(agentId, args.task_id as string, args.reason as string);
|
|
672
|
-
return
|
|
1002
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// Ticket system tools
|
|
1006
|
+
case "get_available_tasks": {
|
|
1007
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
1008
|
+
|
|
1009
|
+
let requiredType = args.required_type as string | undefined;
|
|
1010
|
+
|
|
1011
|
+
// If match_my_type is true, get agent's type from state
|
|
1012
|
+
if (args.match_my_type === true) {
|
|
1013
|
+
const workingDir = context.getWorkingDir();
|
|
1014
|
+
const agentState = state.loadState(workingDir);
|
|
1015
|
+
// Use stored agent_type if available, otherwise keep the explicit required_type
|
|
1016
|
+
requiredType = requiredType || agentState?.agent_type;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const result = await client.getAvailableTasks({
|
|
1020
|
+
status: args.status as string | undefined,
|
|
1021
|
+
project_id: args.project_id as string | undefined,
|
|
1022
|
+
priority: args.priority as string | undefined,
|
|
1023
|
+
required_type: requiredType,
|
|
1024
|
+
});
|
|
1025
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
case "get_task_context": {
|
|
1029
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
1030
|
+
const result = await client.getTicketTask(args.task_id as string);
|
|
1031
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
case "claim_ticket_task": {
|
|
1035
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
1036
|
+
const result = await client.claimTicketTask(args.task_id as string, agentId);
|
|
1037
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
case "reach_checkpoint": {
|
|
1041
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
1042
|
+
const result = await client.createCheckpoint(
|
|
1043
|
+
args.task_id as string,
|
|
1044
|
+
agentId,
|
|
1045
|
+
args.description as string
|
|
1046
|
+
);
|
|
1047
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Ticket creation tools
|
|
1051
|
+
case "create_epic": {
|
|
1052
|
+
const result = await client.createEpic({
|
|
1053
|
+
project_id: args.project_id as string,
|
|
1054
|
+
title: args.title as string,
|
|
1055
|
+
description: args.description as string | undefined,
|
|
1056
|
+
source_file: args.source_file as string | undefined,
|
|
1057
|
+
});
|
|
1058
|
+
return {
|
|
1059
|
+
...result,
|
|
1060
|
+
message: `Created epic ${result.key}: ${args.title}`,
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
case "create_story": {
|
|
1065
|
+
const result = await client.createStory({
|
|
1066
|
+
project_id: args.project_id as string,
|
|
1067
|
+
epic_id: args.epic_id as string | undefined,
|
|
1068
|
+
title: args.title as string,
|
|
1069
|
+
description: args.description as string | undefined,
|
|
1070
|
+
required_type: args.required_type as string | undefined,
|
|
1071
|
+
labels: args.labels as string[] | undefined,
|
|
1072
|
+
});
|
|
1073
|
+
return {
|
|
1074
|
+
...result,
|
|
1075
|
+
message: `Created story ${result.key}: ${args.title}`,
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
case "create_task": {
|
|
1080
|
+
const result = await client.createTask({
|
|
1081
|
+
project_id: args.project_id as string,
|
|
1082
|
+
story_id: args.story_id as string | undefined,
|
|
1083
|
+
title: args.title as string,
|
|
1084
|
+
description: args.description as string | undefined,
|
|
1085
|
+
required_type: args.required_type as string | undefined,
|
|
1086
|
+
});
|
|
1087
|
+
return {
|
|
1088
|
+
...result,
|
|
1089
|
+
message: `Created task ${result.key}: ${args.title}`,
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
case "upload_attachment": {
|
|
1094
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
1095
|
+
const result = await client.uploadAttachment({
|
|
1096
|
+
ticket_id: args.ticket_id as string,
|
|
1097
|
+
ticket_type: args.ticket_type as string,
|
|
1098
|
+
ticket_key: args.ticket_key as string,
|
|
1099
|
+
file_path: args.file_path as string,
|
|
1100
|
+
description: args.description as string | undefined,
|
|
1101
|
+
agent_id: agentId,
|
|
1102
|
+
});
|
|
1103
|
+
return {
|
|
1104
|
+
...result,
|
|
1105
|
+
message: `Uploaded ${result.filename} to ${args.ticket_key}`,
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
case "add_comment": {
|
|
1110
|
+
if (!agentId) throw new Error("Not registered. Call agent_register first.");
|
|
1111
|
+
const result = await client.addComment({
|
|
1112
|
+
ticket_id: args.ticket_id as string,
|
|
1113
|
+
body: args.body as string,
|
|
1114
|
+
});
|
|
1115
|
+
return {
|
|
1116
|
+
...result,
|
|
1117
|
+
message: `Added comment to ticket`,
|
|
1118
|
+
};
|
|
673
1119
|
}
|
|
674
1120
|
|
|
675
1121
|
default:
|