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/dist/tools/index.js
CHANGED
|
@@ -2,19 +2,40 @@
|
|
|
2
2
|
* MCP tool definitions and handlers
|
|
3
3
|
*/
|
|
4
4
|
import * as state from "../state.js";
|
|
5
|
+
import open from "open";
|
|
5
6
|
/**
|
|
6
|
-
* Wraps a tool response with pending messages
|
|
7
|
-
* This enables automatic
|
|
7
|
+
* Wraps a tool response with pending messages and tasks
|
|
8
|
+
* This enables automatic delivery without extra API calls
|
|
9
|
+
* Includes items pushed via WebSocket
|
|
8
10
|
*/
|
|
9
|
-
async function
|
|
11
|
+
async function wrapWithPendingItems(client, agentId, result, context) {
|
|
10
12
|
// If not registered, return raw result
|
|
11
13
|
if (!agentId)
|
|
12
14
|
return result;
|
|
13
15
|
try {
|
|
14
|
-
//
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// Get items pushed via WebSocket (if connected)
|
|
17
|
+
const pushedItems = context?.getPushedItems() || { tasks: [], messages: [] };
|
|
18
|
+
// Fetch unread messages and pending tasks in parallel
|
|
19
|
+
const [inbox, tasksResponse] = await Promise.all([
|
|
20
|
+
client.getInbox(agentId, true, 10),
|
|
21
|
+
client.getPendingTasks(agentId),
|
|
22
|
+
]);
|
|
23
|
+
// Merge pushed items with fetched items (deduplicating by ID)
|
|
24
|
+
const fetchedMessages = inbox.messages || [];
|
|
25
|
+
const fetchedTasks = (tasksResponse.tasks || []);
|
|
26
|
+
// Deduplicate messages
|
|
27
|
+
const messageIds = new Set(fetchedMessages.map((m) => m.id));
|
|
28
|
+
const messages = [
|
|
29
|
+
...fetchedMessages,
|
|
30
|
+
...pushedItems.messages.filter((m) => !messageIds.has(m.id)),
|
|
31
|
+
];
|
|
32
|
+
// Deduplicate tasks
|
|
33
|
+
const taskIds = new Set(fetchedTasks.map((t) => t.id));
|
|
34
|
+
const tasks = [
|
|
35
|
+
...fetchedTasks,
|
|
36
|
+
...pushedItems.tasks.filter((t) => !taskIds.has(t.id)),
|
|
37
|
+
];
|
|
38
|
+
// Sort messages: urgent first, then by created_at
|
|
18
39
|
messages.sort((a, b) => {
|
|
19
40
|
const aUrgent = isUrgentMessage(a);
|
|
20
41
|
const bUrgent = isUrgentMessage(b);
|
|
@@ -24,27 +45,40 @@ async function wrapWithMessages(client, agentId, result) {
|
|
|
24
45
|
return 1;
|
|
25
46
|
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
|
|
26
47
|
});
|
|
27
|
-
//
|
|
28
|
-
const urgentMsg = messages.find(isUrgentMessage);
|
|
48
|
+
// Determine urgent action - tasks take priority over messages
|
|
29
49
|
let urgentAction = null;
|
|
30
|
-
|
|
50
|
+
// Check for pending tasks first (they're actionable)
|
|
51
|
+
if (tasks.length > 0) {
|
|
52
|
+
const highPriorityTask = tasks.find((t) => t.priority === "high") || tasks[0];
|
|
31
53
|
urgentAction = {
|
|
32
54
|
required: true,
|
|
33
|
-
instruction:
|
|
34
|
-
|
|
55
|
+
instruction: buildTaskInstruction(highPriorityTask),
|
|
56
|
+
task: highPriorityTask,
|
|
35
57
|
};
|
|
36
58
|
}
|
|
59
|
+
else {
|
|
60
|
+
// Check for urgent messages
|
|
61
|
+
const urgentMsg = messages.find(isUrgentMessage);
|
|
62
|
+
if (urgentMsg) {
|
|
63
|
+
urgentAction = {
|
|
64
|
+
required: true,
|
|
65
|
+
instruction: buildUrgentInstruction(urgentMsg),
|
|
66
|
+
message: urgentMsg,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
37
70
|
return {
|
|
38
71
|
result,
|
|
39
72
|
pending_messages: messages,
|
|
40
|
-
|
|
41
|
-
|
|
73
|
+
pending_messages_count: messages.length,
|
|
74
|
+
pending_tasks: tasks,
|
|
75
|
+
pending_tasks_count: tasks.length,
|
|
42
76
|
urgent_action: urgentAction,
|
|
43
77
|
};
|
|
44
78
|
}
|
|
45
79
|
catch (error) {
|
|
46
|
-
// Don't break tool call if
|
|
47
|
-
console.warn("Failed to fetch
|
|
80
|
+
// Don't break tool call if fetch fails
|
|
81
|
+
console.warn("Failed to fetch pending items for wrapping:", error);
|
|
48
82
|
return result;
|
|
49
83
|
}
|
|
50
84
|
}
|
|
@@ -67,6 +101,17 @@ Details: ${msg.body}
|
|
|
67
101
|
|
|
68
102
|
Spawn a subagent with Task tool to research and respond to this query. Use send_message to reply when done.`;
|
|
69
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Build instruction text for pending tasks
|
|
106
|
+
*/
|
|
107
|
+
function buildTaskInstruction(task) {
|
|
108
|
+
const assignerName = task.assigned_by_name || task.assigned_by;
|
|
109
|
+
return `ACTION REQUIRED: You have a task assigned by ${assignerName}.
|
|
110
|
+
|
|
111
|
+
Task: ${task.task}
|
|
112
|
+
|
|
113
|
+
Use accept_task with task_id="${task.id}" to accept and start working, or decline_task to decline with a reason.`;
|
|
114
|
+
}
|
|
70
115
|
export function registerTools() {
|
|
71
116
|
return [
|
|
72
117
|
// Registration tools
|
|
@@ -106,6 +151,10 @@ export function registerTools() {
|
|
|
106
151
|
type: "string",
|
|
107
152
|
description: "Project name (optional)",
|
|
108
153
|
},
|
|
154
|
+
task_id: {
|
|
155
|
+
type: "string",
|
|
156
|
+
description: "Optional: ID of a ticket system task to link this work to",
|
|
157
|
+
},
|
|
109
158
|
},
|
|
110
159
|
required: ["task"],
|
|
111
160
|
},
|
|
@@ -399,6 +448,228 @@ export function registerTools() {
|
|
|
399
448
|
required: ["task_id", "reason"],
|
|
400
449
|
},
|
|
401
450
|
},
|
|
451
|
+
// Ticket system tools (for project management integration)
|
|
452
|
+
{
|
|
453
|
+
name: "get_available_tasks",
|
|
454
|
+
description: "Get tasks from the ticket system that are available for you to claim and work on",
|
|
455
|
+
inputSchema: {
|
|
456
|
+
type: "object",
|
|
457
|
+
properties: {
|
|
458
|
+
status: {
|
|
459
|
+
type: "string",
|
|
460
|
+
description: "Filter by status (default: todo)",
|
|
461
|
+
enum: ["backlog", "todo", "in_progress"],
|
|
462
|
+
},
|
|
463
|
+
project_id: {
|
|
464
|
+
type: "string",
|
|
465
|
+
description: "Filter by project ID (optional)",
|
|
466
|
+
},
|
|
467
|
+
priority: {
|
|
468
|
+
type: "string",
|
|
469
|
+
description: "Filter by priority (optional)",
|
|
470
|
+
enum: ["lowest", "low", "medium", "high", "highest"],
|
|
471
|
+
},
|
|
472
|
+
required_type: {
|
|
473
|
+
type: "string",
|
|
474
|
+
description: "Filter by required skill type - matches your agent type",
|
|
475
|
+
enum: ["frontend", "backend", "android", "ios", "ai", "design-specs"],
|
|
476
|
+
},
|
|
477
|
+
match_my_type: {
|
|
478
|
+
type: "boolean",
|
|
479
|
+
description: "If true, only show tasks matching your registered agent type",
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: "get_task_context",
|
|
486
|
+
description: "Get full context for a ticket task including its parent story and epic",
|
|
487
|
+
inputSchema: {
|
|
488
|
+
type: "object",
|
|
489
|
+
properties: {
|
|
490
|
+
task_id: {
|
|
491
|
+
type: "string",
|
|
492
|
+
description: "ID of the task to get context for",
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
required: ["task_id"],
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
name: "claim_ticket_task",
|
|
500
|
+
description: "Claim a ticket task from the project board to work on",
|
|
501
|
+
inputSchema: {
|
|
502
|
+
type: "object",
|
|
503
|
+
properties: {
|
|
504
|
+
task_id: {
|
|
505
|
+
type: "string",
|
|
506
|
+
description: "ID of the task to claim",
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
required: ["task_id"],
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: "reach_checkpoint",
|
|
514
|
+
description: "Signal that you've reached a checkpoint and request human review before continuing",
|
|
515
|
+
inputSchema: {
|
|
516
|
+
type: "object",
|
|
517
|
+
properties: {
|
|
518
|
+
task_id: {
|
|
519
|
+
type: "string",
|
|
520
|
+
description: "ID of the task you're working on",
|
|
521
|
+
},
|
|
522
|
+
description: {
|
|
523
|
+
type: "string",
|
|
524
|
+
description: "Description of what you've accomplished and what you're waiting for review on",
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
required: ["task_id", "description"],
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
// Ticket creation tools
|
|
531
|
+
{
|
|
532
|
+
name: "create_epic",
|
|
533
|
+
description: "Create an epic (large body of work) in a project",
|
|
534
|
+
inputSchema: {
|
|
535
|
+
type: "object",
|
|
536
|
+
properties: {
|
|
537
|
+
project_id: {
|
|
538
|
+
type: "string",
|
|
539
|
+
description: "Project ID to create epic in",
|
|
540
|
+
},
|
|
541
|
+
title: {
|
|
542
|
+
type: "string",
|
|
543
|
+
description: "Epic title",
|
|
544
|
+
},
|
|
545
|
+
description: {
|
|
546
|
+
type: "string",
|
|
547
|
+
description: "Epic description (supports markdown)",
|
|
548
|
+
},
|
|
549
|
+
source_file: {
|
|
550
|
+
type: "string",
|
|
551
|
+
description: "Optional: path to OpenSpec file this came from",
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
required: ["project_id", "title"],
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
name: "create_story",
|
|
559
|
+
description: "Create a story under an epic",
|
|
560
|
+
inputSchema: {
|
|
561
|
+
type: "object",
|
|
562
|
+
properties: {
|
|
563
|
+
project_id: {
|
|
564
|
+
type: "string",
|
|
565
|
+
description: "Project ID",
|
|
566
|
+
},
|
|
567
|
+
epic_id: {
|
|
568
|
+
type: "string",
|
|
569
|
+
description: "Epic ID to add story to",
|
|
570
|
+
},
|
|
571
|
+
title: {
|
|
572
|
+
type: "string",
|
|
573
|
+
description: "Story title",
|
|
574
|
+
},
|
|
575
|
+
description: {
|
|
576
|
+
type: "string",
|
|
577
|
+
description: "Story description",
|
|
578
|
+
},
|
|
579
|
+
required_type: {
|
|
580
|
+
type: "string",
|
|
581
|
+
enum: ["frontend", "backend", "android", "ios", "ai", "design-specs"],
|
|
582
|
+
description: "Required skill type for this story",
|
|
583
|
+
},
|
|
584
|
+
labels: {
|
|
585
|
+
type: "array",
|
|
586
|
+
items: { type: "string" },
|
|
587
|
+
description: "Additional labels",
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
required: ["project_id", "title"],
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
name: "create_task",
|
|
595
|
+
description: "Create a task under a story",
|
|
596
|
+
inputSchema: {
|
|
597
|
+
type: "object",
|
|
598
|
+
properties: {
|
|
599
|
+
project_id: {
|
|
600
|
+
type: "string",
|
|
601
|
+
description: "Project ID",
|
|
602
|
+
},
|
|
603
|
+
story_id: {
|
|
604
|
+
type: "string",
|
|
605
|
+
description: "Story ID to add task to",
|
|
606
|
+
},
|
|
607
|
+
title: {
|
|
608
|
+
type: "string",
|
|
609
|
+
description: "Task title",
|
|
610
|
+
},
|
|
611
|
+
description: {
|
|
612
|
+
type: "string",
|
|
613
|
+
description: "Task description",
|
|
614
|
+
},
|
|
615
|
+
required_type: {
|
|
616
|
+
type: "string",
|
|
617
|
+
enum: ["frontend", "backend", "android", "ios", "ai", "design-specs"],
|
|
618
|
+
description: "Required skill type",
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
required: ["project_id", "title"],
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
name: "upload_attachment",
|
|
626
|
+
description: "Upload a file (screenshot, log, etc.) as proof of work to a ticket",
|
|
627
|
+
inputSchema: {
|
|
628
|
+
type: "object",
|
|
629
|
+
properties: {
|
|
630
|
+
ticket_id: {
|
|
631
|
+
type: "string",
|
|
632
|
+
description: "Ticket ID to attach file to",
|
|
633
|
+
},
|
|
634
|
+
ticket_type: {
|
|
635
|
+
type: "string",
|
|
636
|
+
enum: ["epic", "story", "task"],
|
|
637
|
+
description: "Type of ticket",
|
|
638
|
+
},
|
|
639
|
+
ticket_key: {
|
|
640
|
+
type: "string",
|
|
641
|
+
description: "Ticket key (e.g., PROJ-T42)",
|
|
642
|
+
},
|
|
643
|
+
file_path: {
|
|
644
|
+
type: "string",
|
|
645
|
+
description: "Local path to file to upload",
|
|
646
|
+
},
|
|
647
|
+
description: {
|
|
648
|
+
type: "string",
|
|
649
|
+
description: "Optional description of the file",
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
required: ["ticket_id", "ticket_type", "ticket_key", "file_path"],
|
|
653
|
+
},
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
name: "add_comment",
|
|
657
|
+
description: "Add a comment to any ticket (epic, story, task)",
|
|
658
|
+
inputSchema: {
|
|
659
|
+
type: "object",
|
|
660
|
+
properties: {
|
|
661
|
+
ticket_id: {
|
|
662
|
+
type: "string",
|
|
663
|
+
description: "Ticket ID to comment on",
|
|
664
|
+
},
|
|
665
|
+
body: {
|
|
666
|
+
type: "string",
|
|
667
|
+
description: "Comment body (supports markdown)",
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
required: ["ticket_id", "body"],
|
|
671
|
+
},
|
|
672
|
+
},
|
|
402
673
|
];
|
|
403
674
|
}
|
|
404
675
|
export async function handleToolCall(name, args, client, context) {
|
|
@@ -445,38 +716,71 @@ export async function handleToolCall(name, args, client, context) {
|
|
|
445
716
|
}
|
|
446
717
|
}
|
|
447
718
|
}
|
|
448
|
-
// Fresh registration
|
|
449
|
-
//
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
//
|
|
719
|
+
// Fresh registration - use browser-based flow
|
|
720
|
+
// Step 1: Initialize registration session
|
|
721
|
+
const initResult = await client.initRegistration(requestedId, args.name);
|
|
722
|
+
// Step 2: Open browser to dashboard
|
|
723
|
+
const dashboardUrl = client.getBaseUrl().replace('/api', '') + initResult.dashboard_url;
|
|
724
|
+
console.error(`Opening browser for agent registration: ${dashboardUrl}`);
|
|
725
|
+
await open(dashboardUrl);
|
|
726
|
+
// Step 3: Poll for completion (max 10 minutes)
|
|
727
|
+
const pollInterval = 3000; // 3 seconds
|
|
728
|
+
const maxPolls = 200; // 10 minutes
|
|
729
|
+
let pollCount = 0;
|
|
730
|
+
let registrationResult = null;
|
|
731
|
+
console.error('Waiting for browser registration to complete...');
|
|
732
|
+
while (pollCount < maxPolls) {
|
|
733
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
734
|
+
pollCount++;
|
|
735
|
+
try {
|
|
736
|
+
const pollResult = await client.pollRegistrationCallback(initResult.session_token);
|
|
737
|
+
if (pollResult.status === 'completed') {
|
|
738
|
+
registrationResult = pollResult;
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
else if (pollResult.status === 'expired') {
|
|
742
|
+
throw new Error('Registration session expired. Please try again.');
|
|
743
|
+
}
|
|
744
|
+
// status === 'pending' - continue polling
|
|
745
|
+
}
|
|
746
|
+
catch (error) {
|
|
747
|
+
// Non-fatal errors during polling - continue
|
|
748
|
+
console.warn('Poll error:', error);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (!registrationResult || registrationResult.status !== 'completed') {
|
|
752
|
+
throw new Error('Registration timed out. Please try again.');
|
|
753
|
+
}
|
|
754
|
+
const agentId = registrationResult.agent_id;
|
|
755
|
+
context.setCurrentAgentId(agentId);
|
|
756
|
+
// Save state for future reconnection
|
|
456
757
|
state.saveState(workingDir, {
|
|
457
|
-
agent_id:
|
|
458
|
-
name:
|
|
758
|
+
agent_id: agentId,
|
|
759
|
+
name: registrationResult.agent_name || agentId,
|
|
459
760
|
owner,
|
|
460
|
-
token:
|
|
461
|
-
|
|
761
|
+
token: registrationResult.connect_token,
|
|
762
|
+
agent_type: registrationResult.agent_type,
|
|
763
|
+
registered_at: new Date().toISOString(),
|
|
462
764
|
});
|
|
463
765
|
return {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
766
|
+
agent_id: agentId,
|
|
767
|
+
name: registrationResult.agent_name,
|
|
768
|
+
agent_type: registrationResult.agent_type,
|
|
769
|
+
mode: 'registered',
|
|
770
|
+
message: `Successfully registered as ${registrationResult.agent_name || agentId}. You can now use other AgentHub tools.`,
|
|
467
771
|
};
|
|
468
772
|
}
|
|
469
773
|
case "agent_start_work": {
|
|
470
774
|
if (!agentId)
|
|
471
775
|
throw new Error("Not registered. Call agent_register first.");
|
|
472
|
-
const result = await client.startWork(agentId, args.task, args.project);
|
|
473
|
-
return
|
|
776
|
+
const result = await client.startWork(agentId, args.task, args.project, args.task_id);
|
|
777
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
474
778
|
}
|
|
475
779
|
case "agent_set_status": {
|
|
476
780
|
if (!agentId)
|
|
477
781
|
throw new Error("Not registered. Call agent_register first.");
|
|
478
782
|
const result = await client.heartbeat(agentId, args.status);
|
|
479
|
-
return
|
|
783
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
480
784
|
}
|
|
481
785
|
case "agent_disconnect": {
|
|
482
786
|
// No wrapping - agent is going offline
|
|
@@ -497,7 +801,7 @@ export async function handleToolCall(name, args, client, context) {
|
|
|
497
801
|
body: args.body,
|
|
498
802
|
priority: args.priority,
|
|
499
803
|
});
|
|
500
|
-
return
|
|
804
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
501
805
|
}
|
|
502
806
|
case "send_to_channel": {
|
|
503
807
|
if (!agentId)
|
|
@@ -509,7 +813,7 @@ export async function handleToolCall(name, args, client, context) {
|
|
|
509
813
|
subject: args.subject,
|
|
510
814
|
body: args.body,
|
|
511
815
|
});
|
|
512
|
-
return
|
|
816
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
513
817
|
}
|
|
514
818
|
case "broadcast": {
|
|
515
819
|
if (!agentId)
|
|
@@ -521,7 +825,7 @@ export async function handleToolCall(name, args, client, context) {
|
|
|
521
825
|
subject: args.subject,
|
|
522
826
|
body: args.body,
|
|
523
827
|
});
|
|
524
|
-
return
|
|
828
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
525
829
|
}
|
|
526
830
|
case "check_inbox": {
|
|
527
831
|
// No wrapping - already fetching messages
|
|
@@ -531,38 +835,38 @@ export async function handleToolCall(name, args, client, context) {
|
|
|
531
835
|
}
|
|
532
836
|
case "mark_read": {
|
|
533
837
|
const result = await client.markRead(args.message_id);
|
|
534
|
-
return
|
|
838
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
535
839
|
}
|
|
536
840
|
case "reply": {
|
|
537
841
|
if (!agentId)
|
|
538
842
|
throw new Error("Not registered. Call agent_register first.");
|
|
539
843
|
const result = await client.reply(args.message_id, agentId, args.body);
|
|
540
|
-
return
|
|
844
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
541
845
|
}
|
|
542
846
|
// Discovery
|
|
543
847
|
case "list_agents": {
|
|
544
848
|
const result = await client.listAgents(args.status);
|
|
545
|
-
return
|
|
849
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
546
850
|
}
|
|
547
851
|
case "get_agent": {
|
|
548
852
|
const result = await client.getAgent(args.id);
|
|
549
|
-
return
|
|
853
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
550
854
|
}
|
|
551
855
|
case "list_channels": {
|
|
552
856
|
const result = await client.listChannels();
|
|
553
|
-
return
|
|
857
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
554
858
|
}
|
|
555
859
|
case "join_channel": {
|
|
556
860
|
if (!agentId)
|
|
557
861
|
throw new Error("Not registered. Call agent_register first.");
|
|
558
862
|
const result = await client.joinChannel(args.channel, agentId);
|
|
559
|
-
return
|
|
863
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
560
864
|
}
|
|
561
865
|
case "leave_channel": {
|
|
562
866
|
if (!agentId)
|
|
563
867
|
throw new Error("Not registered. Call agent_register first.");
|
|
564
868
|
const result = await client.leaveChannel(args.channel, agentId);
|
|
565
|
-
return
|
|
869
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
566
870
|
}
|
|
567
871
|
// Task completion
|
|
568
872
|
case "agent_complete_task": {
|
|
@@ -575,25 +879,131 @@ export async function handleToolCall(name, args, client, context) {
|
|
|
575
879
|
time_spent: args.time_spent,
|
|
576
880
|
next_steps: args.next_steps,
|
|
577
881
|
});
|
|
578
|
-
return
|
|
882
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
579
883
|
}
|
|
580
884
|
case "get_pending_tasks": {
|
|
581
885
|
if (!agentId)
|
|
582
886
|
throw new Error("Not registered. Call agent_register first.");
|
|
583
887
|
const result = await client.getPendingTasks(agentId);
|
|
584
|
-
return
|
|
888
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
585
889
|
}
|
|
586
890
|
case "accept_task": {
|
|
587
891
|
if (!agentId)
|
|
588
892
|
throw new Error("Not registered. Call agent_register first.");
|
|
589
893
|
const result = await client.acceptTask(agentId, args.task_id);
|
|
590
|
-
return
|
|
894
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
591
895
|
}
|
|
592
896
|
case "decline_task": {
|
|
593
897
|
if (!agentId)
|
|
594
898
|
throw new Error("Not registered. Call agent_register first.");
|
|
595
899
|
const result = await client.declineTask(agentId, args.task_id, args.reason);
|
|
596
|
-
return
|
|
900
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
901
|
+
}
|
|
902
|
+
// Ticket system tools
|
|
903
|
+
case "get_available_tasks": {
|
|
904
|
+
if (!agentId)
|
|
905
|
+
throw new Error("Not registered. Call agent_register first.");
|
|
906
|
+
let requiredType = args.required_type;
|
|
907
|
+
// If match_my_type is true, get agent's type from state
|
|
908
|
+
if (args.match_my_type === true) {
|
|
909
|
+
const workingDir = context.getWorkingDir();
|
|
910
|
+
const agentState = state.loadState(workingDir);
|
|
911
|
+
// Use stored agent_type if available, otherwise keep the explicit required_type
|
|
912
|
+
requiredType = requiredType || agentState?.agent_type;
|
|
913
|
+
}
|
|
914
|
+
const result = await client.getAvailableTasks({
|
|
915
|
+
status: args.status,
|
|
916
|
+
project_id: args.project_id,
|
|
917
|
+
priority: args.priority,
|
|
918
|
+
required_type: requiredType,
|
|
919
|
+
});
|
|
920
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
921
|
+
}
|
|
922
|
+
case "get_task_context": {
|
|
923
|
+
if (!agentId)
|
|
924
|
+
throw new Error("Not registered. Call agent_register first.");
|
|
925
|
+
const result = await client.getTicketTask(args.task_id);
|
|
926
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
927
|
+
}
|
|
928
|
+
case "claim_ticket_task": {
|
|
929
|
+
if (!agentId)
|
|
930
|
+
throw new Error("Not registered. Call agent_register first.");
|
|
931
|
+
const result = await client.claimTicketTask(args.task_id, agentId);
|
|
932
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
933
|
+
}
|
|
934
|
+
case "reach_checkpoint": {
|
|
935
|
+
if (!agentId)
|
|
936
|
+
throw new Error("Not registered. Call agent_register first.");
|
|
937
|
+
const result = await client.createCheckpoint(args.task_id, agentId, args.description);
|
|
938
|
+
return wrapWithPendingItems(client, agentId, result, context);
|
|
939
|
+
}
|
|
940
|
+
// Ticket creation tools
|
|
941
|
+
case "create_epic": {
|
|
942
|
+
const result = await client.createEpic({
|
|
943
|
+
project_id: args.project_id,
|
|
944
|
+
title: args.title,
|
|
945
|
+
description: args.description,
|
|
946
|
+
source_file: args.source_file,
|
|
947
|
+
});
|
|
948
|
+
return {
|
|
949
|
+
...result,
|
|
950
|
+
message: `Created epic ${result.key}: ${args.title}`,
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
case "create_story": {
|
|
954
|
+
const result = await client.createStory({
|
|
955
|
+
project_id: args.project_id,
|
|
956
|
+
epic_id: args.epic_id,
|
|
957
|
+
title: args.title,
|
|
958
|
+
description: args.description,
|
|
959
|
+
required_type: args.required_type,
|
|
960
|
+
labels: args.labels,
|
|
961
|
+
});
|
|
962
|
+
return {
|
|
963
|
+
...result,
|
|
964
|
+
message: `Created story ${result.key}: ${args.title}`,
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
case "create_task": {
|
|
968
|
+
const result = await client.createTask({
|
|
969
|
+
project_id: args.project_id,
|
|
970
|
+
story_id: args.story_id,
|
|
971
|
+
title: args.title,
|
|
972
|
+
description: args.description,
|
|
973
|
+
required_type: args.required_type,
|
|
974
|
+
});
|
|
975
|
+
return {
|
|
976
|
+
...result,
|
|
977
|
+
message: `Created task ${result.key}: ${args.title}`,
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
case "upload_attachment": {
|
|
981
|
+
if (!agentId)
|
|
982
|
+
throw new Error("Not registered. Call agent_register first.");
|
|
983
|
+
const result = await client.uploadAttachment({
|
|
984
|
+
ticket_id: args.ticket_id,
|
|
985
|
+
ticket_type: args.ticket_type,
|
|
986
|
+
ticket_key: args.ticket_key,
|
|
987
|
+
file_path: args.file_path,
|
|
988
|
+
description: args.description,
|
|
989
|
+
agent_id: agentId,
|
|
990
|
+
});
|
|
991
|
+
return {
|
|
992
|
+
...result,
|
|
993
|
+
message: `Uploaded ${result.filename} to ${args.ticket_key}`,
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
case "add_comment": {
|
|
997
|
+
if (!agentId)
|
|
998
|
+
throw new Error("Not registered. Call agent_register first.");
|
|
999
|
+
const result = await client.addComment({
|
|
1000
|
+
ticket_id: args.ticket_id,
|
|
1001
|
+
body: args.body,
|
|
1002
|
+
});
|
|
1003
|
+
return {
|
|
1004
|
+
...result,
|
|
1005
|
+
message: `Added comment to ticket`,
|
|
1006
|
+
};
|
|
597
1007
|
}
|
|
598
1008
|
default:
|
|
599
1009
|
throw new Error(`Unknown tool: ${name}`);
|