@sparkleideas/claims 3.5.2-patch.1
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 +261 -0
- package/package.json +84 -0
- package/src/api/cli-commands.ts +1459 -0
- package/src/api/cli-types.ts +154 -0
- package/src/api/index.ts +24 -0
- package/src/api/mcp-tools.ts +1977 -0
- package/src/application/claim-service.ts +753 -0
- package/src/application/index.ts +46 -0
- package/src/application/load-balancer.ts +840 -0
- package/src/application/work-stealing-service.ts +807 -0
- package/src/domain/events.ts +779 -0
- package/src/domain/index.ts +214 -0
- package/src/domain/repositories.ts +239 -0
- package/src/domain/rules.ts +526 -0
- package/src/domain/types.ts +826 -0
- package/src/index.ts +79 -0
- package/src/infrastructure/claim-repository.ts +358 -0
- package/src/infrastructure/event-store.ts +297 -0
- package/src/infrastructure/index.ts +21 -0
|
@@ -0,0 +1,1977 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 MCP Claims Tools
|
|
3
|
+
*
|
|
4
|
+
* MCP tools for issue claiming and work coordination:
|
|
5
|
+
*
|
|
6
|
+
* Core Claiming (7 tools):
|
|
7
|
+
* - claims/issue_claim - Claim an issue to work on
|
|
8
|
+
* - claims/issue_release - Release a claim
|
|
9
|
+
* - claims/issue_handoff - Request handoff to another agent/human
|
|
10
|
+
* - claims/issue_status_update - Update claim status
|
|
11
|
+
* - claims/issue_list_available - List unclaimed issues
|
|
12
|
+
* - claims/issue_list_mine - List my claims
|
|
13
|
+
* - claims/issue_board - View claim board (who's working on what)
|
|
14
|
+
*
|
|
15
|
+
* Work Stealing (4 tools):
|
|
16
|
+
* - claims/issue_mark_stealable - Mark my claim as stealable
|
|
17
|
+
* - claims/issue_steal - Steal a stealable issue
|
|
18
|
+
* - claims/issue_get_stealable - List stealable issues
|
|
19
|
+
* - claims/issue_contest_steal - Contest a steal
|
|
20
|
+
*
|
|
21
|
+
* Load Balancing (3 tools):
|
|
22
|
+
* - claims/agent_load_info - Get agent's current load
|
|
23
|
+
* - claims/swarm_rebalance - Trigger swarm rebalancing
|
|
24
|
+
* - claims/swarm_load_overview - Get swarm-wide load distribution
|
|
25
|
+
*
|
|
26
|
+
* Additionally provides:
|
|
27
|
+
* - claims/claim_history - Get claim history for an issue
|
|
28
|
+
* - claims/claim_metrics - Get claiming metrics
|
|
29
|
+
* - claims/claim_config - Configure claiming behavior
|
|
30
|
+
*
|
|
31
|
+
* Implements ADR-005: MCP-First API Design
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { z } from 'zod';
|
|
35
|
+
import { randomBytes } from 'crypto';
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Type Definitions (compatible with v3/mcp/types.ts)
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* JSON Schema type for tool input
|
|
43
|
+
*/
|
|
44
|
+
interface JSONSchema {
|
|
45
|
+
type: string;
|
|
46
|
+
properties?: Record<string, JSONSchema>;
|
|
47
|
+
required?: string[];
|
|
48
|
+
items?: JSONSchema;
|
|
49
|
+
enum?: string[];
|
|
50
|
+
description?: string;
|
|
51
|
+
default?: unknown;
|
|
52
|
+
minimum?: number;
|
|
53
|
+
maximum?: number;
|
|
54
|
+
minLength?: number;
|
|
55
|
+
maxLength?: number;
|
|
56
|
+
pattern?: string;
|
|
57
|
+
additionalProperties?: boolean | JSONSchema;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Tool execution context
|
|
62
|
+
*/
|
|
63
|
+
interface ToolContext {
|
|
64
|
+
sessionId: string;
|
|
65
|
+
requestId?: string | number | null;
|
|
66
|
+
orchestrator?: unknown;
|
|
67
|
+
swarmCoordinator?: unknown;
|
|
68
|
+
agentManager?: unknown;
|
|
69
|
+
claimsService?: ClaimsService;
|
|
70
|
+
metadata?: Record<string, unknown>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Tool handler function type
|
|
75
|
+
*/
|
|
76
|
+
type ToolHandler<TInput = unknown, TOutput = unknown> = (
|
|
77
|
+
input: TInput,
|
|
78
|
+
context?: ToolContext
|
|
79
|
+
) => Promise<TOutput>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* MCP Tool definition
|
|
83
|
+
*/
|
|
84
|
+
interface MCPTool<TInput = unknown, TOutput = unknown> {
|
|
85
|
+
name: string;
|
|
86
|
+
description: string;
|
|
87
|
+
inputSchema: JSONSchema;
|
|
88
|
+
handler: ToolHandler<TInput, TOutput>;
|
|
89
|
+
category?: string;
|
|
90
|
+
tags?: string[];
|
|
91
|
+
version?: string;
|
|
92
|
+
deprecated?: boolean;
|
|
93
|
+
cacheable?: boolean;
|
|
94
|
+
cacheTTL?: number;
|
|
95
|
+
timeout?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// Claims-Specific Types
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
type ClaimantType = 'human' | 'agent';
|
|
103
|
+
type ClaimStatus = 'active' | 'blocked' | 'in-review' | 'completed' | 'released' | 'stolen';
|
|
104
|
+
type IssuePriority = 'critical' | 'high' | 'medium' | 'low';
|
|
105
|
+
type HandoffReason = 'blocked' | 'expertise-needed' | 'capacity' | 'reassignment' | 'other';
|
|
106
|
+
|
|
107
|
+
interface Claim {
|
|
108
|
+
id: string;
|
|
109
|
+
issueId: string;
|
|
110
|
+
claimantType: ClaimantType;
|
|
111
|
+
claimantId: string;
|
|
112
|
+
status: ClaimStatus;
|
|
113
|
+
priority: IssuePriority;
|
|
114
|
+
stealable: boolean;
|
|
115
|
+
stealableReason?: string;
|
|
116
|
+
claimedAt: string;
|
|
117
|
+
lastActivityAt: string;
|
|
118
|
+
expiresAt?: string;
|
|
119
|
+
metadata?: Record<string, unknown>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface Issue {
|
|
123
|
+
id: string;
|
|
124
|
+
title: string;
|
|
125
|
+
description?: string;
|
|
126
|
+
priority: IssuePriority;
|
|
127
|
+
labels?: string[];
|
|
128
|
+
repository?: string;
|
|
129
|
+
createdAt: string;
|
|
130
|
+
updatedAt?: string;
|
|
131
|
+
claimedBy?: string;
|
|
132
|
+
metadata?: Record<string, unknown>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface AgentLoad {
|
|
136
|
+
agentId: string;
|
|
137
|
+
agentType: string;
|
|
138
|
+
currentClaims: number;
|
|
139
|
+
maxClaims: number;
|
|
140
|
+
utilizationPercent: number;
|
|
141
|
+
activeTasks: number;
|
|
142
|
+
queuedTasks: number;
|
|
143
|
+
averageTaskDuration: number;
|
|
144
|
+
lastActivityAt: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
interface ClaimHistoryEntry {
|
|
148
|
+
timestamp: string;
|
|
149
|
+
action: string;
|
|
150
|
+
actorId: string;
|
|
151
|
+
actorType: ClaimantType;
|
|
152
|
+
details?: Record<string, unknown>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Claims Service Interface
|
|
157
|
+
* Defines the contract for claims management operations
|
|
158
|
+
*/
|
|
159
|
+
interface ClaimsService {
|
|
160
|
+
claimIssue(params: {
|
|
161
|
+
issueId: string;
|
|
162
|
+
claimantType: ClaimantType;
|
|
163
|
+
claimantId: string;
|
|
164
|
+
priority?: IssuePriority;
|
|
165
|
+
expiresInMs?: number;
|
|
166
|
+
}): Promise<Claim>;
|
|
167
|
+
|
|
168
|
+
releaseClaim(params: {
|
|
169
|
+
issueId: string;
|
|
170
|
+
claimantId: string;
|
|
171
|
+
reason?: string;
|
|
172
|
+
}): Promise<{ released: boolean; releasedAt: string }>;
|
|
173
|
+
|
|
174
|
+
requestHandoff(params: {
|
|
175
|
+
issueId: string;
|
|
176
|
+
fromId: string;
|
|
177
|
+
toId?: string;
|
|
178
|
+
toType?: ClaimantType;
|
|
179
|
+
reason: HandoffReason;
|
|
180
|
+
notes?: string;
|
|
181
|
+
}): Promise<{ handoffId: string; status: string }>;
|
|
182
|
+
|
|
183
|
+
updateClaimStatus(params: {
|
|
184
|
+
issueId: string;
|
|
185
|
+
claimantId: string;
|
|
186
|
+
status: ClaimStatus;
|
|
187
|
+
progress?: number;
|
|
188
|
+
notes?: string;
|
|
189
|
+
}): Promise<Claim>;
|
|
190
|
+
|
|
191
|
+
listAvailableIssues(params: {
|
|
192
|
+
priority?: IssuePriority;
|
|
193
|
+
labels?: string[];
|
|
194
|
+
repository?: string;
|
|
195
|
+
limit?: number;
|
|
196
|
+
offset?: number;
|
|
197
|
+
}): Promise<{ issues: Issue[]; total: number }>;
|
|
198
|
+
|
|
199
|
+
listMyClaims(params: {
|
|
200
|
+
claimantId: string;
|
|
201
|
+
status?: ClaimStatus;
|
|
202
|
+
limit?: number;
|
|
203
|
+
offset?: number;
|
|
204
|
+
}): Promise<{ claims: Claim[]; total: number }>;
|
|
205
|
+
|
|
206
|
+
getClaimBoard(params: {
|
|
207
|
+
includeAgents?: boolean;
|
|
208
|
+
includeHumans?: boolean;
|
|
209
|
+
groupBy?: 'claimant' | 'priority' | 'status';
|
|
210
|
+
}): Promise<{
|
|
211
|
+
claims: Claim[];
|
|
212
|
+
byClaimant?: Record<string, Claim[]>;
|
|
213
|
+
byPriority?: Record<IssuePriority, Claim[]>;
|
|
214
|
+
byStatus?: Record<ClaimStatus, Claim[]>;
|
|
215
|
+
}>;
|
|
216
|
+
|
|
217
|
+
markStealable(params: {
|
|
218
|
+
issueId: string;
|
|
219
|
+
claimantId: string;
|
|
220
|
+
reason?: string;
|
|
221
|
+
}): Promise<{ marked: boolean; markedAt: string }>;
|
|
222
|
+
|
|
223
|
+
stealClaim(params: {
|
|
224
|
+
issueId: string;
|
|
225
|
+
stealerId: string;
|
|
226
|
+
stealerType: ClaimantType;
|
|
227
|
+
reason?: string;
|
|
228
|
+
}): Promise<{
|
|
229
|
+
stolen: boolean;
|
|
230
|
+
claim: Claim;
|
|
231
|
+
previousClaimant: string;
|
|
232
|
+
contestWindow: number;
|
|
233
|
+
}>;
|
|
234
|
+
|
|
235
|
+
getStealableIssues(params: {
|
|
236
|
+
priority?: IssuePriority;
|
|
237
|
+
limit?: number;
|
|
238
|
+
}): Promise<{ issues: Array<Issue & { stealableReason?: string }>; total: number }>;
|
|
239
|
+
|
|
240
|
+
contestSteal(params: {
|
|
241
|
+
issueId: string;
|
|
242
|
+
contesterId: string;
|
|
243
|
+
reason: string;
|
|
244
|
+
}): Promise<{
|
|
245
|
+
contested: boolean;
|
|
246
|
+
resolution: 'pending' | 'upheld' | 'reversed';
|
|
247
|
+
resolvedAt?: string;
|
|
248
|
+
}>;
|
|
249
|
+
|
|
250
|
+
getAgentLoad(params: {
|
|
251
|
+
agentId: string;
|
|
252
|
+
}): Promise<AgentLoad>;
|
|
253
|
+
|
|
254
|
+
rebalanceSwarm(params: {
|
|
255
|
+
strategy?: 'round-robin' | 'least-loaded' | 'priority-based' | 'capability-based';
|
|
256
|
+
dryRun?: boolean;
|
|
257
|
+
}): Promise<{
|
|
258
|
+
rebalanced: boolean;
|
|
259
|
+
changes: Array<{ issueId: string; from: string; to: string }>;
|
|
260
|
+
dryRun: boolean;
|
|
261
|
+
}>;
|
|
262
|
+
|
|
263
|
+
getLoadOverview(): Promise<{
|
|
264
|
+
totalAgents: number;
|
|
265
|
+
totalClaims: number;
|
|
266
|
+
averageLoad: number;
|
|
267
|
+
agents: AgentLoad[];
|
|
268
|
+
bottlenecks: string[];
|
|
269
|
+
recommendations: string[];
|
|
270
|
+
}>;
|
|
271
|
+
|
|
272
|
+
getClaimHistory(params: {
|
|
273
|
+
issueId: string;
|
|
274
|
+
limit?: number;
|
|
275
|
+
}): Promise<{ history: ClaimHistoryEntry[]; total: number }>;
|
|
276
|
+
|
|
277
|
+
getMetrics(): Promise<{
|
|
278
|
+
totalClaims: number;
|
|
279
|
+
activeClaims: number;
|
|
280
|
+
completedClaims: number;
|
|
281
|
+
stolenClaims: number;
|
|
282
|
+
averageClaimDuration: number;
|
|
283
|
+
claimsByPriority: Record<IssuePriority, number>;
|
|
284
|
+
claimsByStatus: Record<ClaimStatus, number>;
|
|
285
|
+
}>;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ============================================================================
|
|
289
|
+
// Secure ID Generation
|
|
290
|
+
// ============================================================================
|
|
291
|
+
|
|
292
|
+
function generateSecureId(prefix: string): string {
|
|
293
|
+
const timestamp = Date.now().toString(36);
|
|
294
|
+
const random = randomBytes(12).toString('hex');
|
|
295
|
+
return `${prefix}-${timestamp}-${random}`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ============================================================================
|
|
299
|
+
// In-Memory Store (for simple implementation without service)
|
|
300
|
+
// ============================================================================
|
|
301
|
+
|
|
302
|
+
const claimStore = new Map<string, Claim>();
|
|
303
|
+
const issueStore = new Map<string, Issue>();
|
|
304
|
+
|
|
305
|
+
// Initialize with some mock data
|
|
306
|
+
function initializeMockData(): void {
|
|
307
|
+
if (issueStore.size === 0) {
|
|
308
|
+
const mockIssues: Issue[] = [
|
|
309
|
+
{
|
|
310
|
+
id: 'issue-1',
|
|
311
|
+
title: 'Implement user authentication',
|
|
312
|
+
priority: 'high',
|
|
313
|
+
labels: ['feature', 'security'],
|
|
314
|
+
createdAt: new Date().toISOString(),
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
id: 'issue-2',
|
|
318
|
+
title: 'Fix memory leak in agent coordinator',
|
|
319
|
+
priority: 'critical',
|
|
320
|
+
labels: ['bug', 'performance'],
|
|
321
|
+
createdAt: new Date().toISOString(),
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: 'issue-3',
|
|
325
|
+
title: 'Add unit tests for claims module',
|
|
326
|
+
priority: 'medium',
|
|
327
|
+
labels: ['testing'],
|
|
328
|
+
createdAt: new Date().toISOString(),
|
|
329
|
+
},
|
|
330
|
+
];
|
|
331
|
+
mockIssues.forEach(issue => issueStore.set(issue.id, issue));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ============================================================================
|
|
336
|
+
// Input Schemas
|
|
337
|
+
// ============================================================================
|
|
338
|
+
|
|
339
|
+
// Core Claiming Schemas
|
|
340
|
+
const issueClaimSchema = z.object({
|
|
341
|
+
issueId: z.string().min(1).describe('Issue ID to claim'),
|
|
342
|
+
claimantType: z.enum(['human', 'agent']).describe('Type of claimant'),
|
|
343
|
+
claimantId: z.string().min(1).describe('ID of the claimant'),
|
|
344
|
+
priority: z.enum(['critical', 'high', 'medium', 'low']).optional()
|
|
345
|
+
.describe('Override priority for the claim'),
|
|
346
|
+
expiresInMs: z.number().int().positive().optional()
|
|
347
|
+
.describe('Claim expiration time in milliseconds'),
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const issueReleaseSchema = z.object({
|
|
351
|
+
issueId: z.string().min(1).describe('Issue ID to release'),
|
|
352
|
+
claimantId: z.string().min(1).describe('ID of the current claimant'),
|
|
353
|
+
reason: z.string().optional().describe('Reason for releasing the claim'),
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const issueHandoffSchema = z.object({
|
|
357
|
+
issueId: z.string().min(1).describe('Issue ID for handoff'),
|
|
358
|
+
fromId: z.string().min(1).describe('Current claimant ID'),
|
|
359
|
+
toId: z.string().optional().describe('Target claimant ID (optional for open handoff)'),
|
|
360
|
+
toType: z.enum(['human', 'agent']).optional().describe('Target claimant type'),
|
|
361
|
+
reason: z.enum(['blocked', 'expertise-needed', 'capacity', 'reassignment', 'other'])
|
|
362
|
+
.describe('Reason for handoff'),
|
|
363
|
+
notes: z.string().optional().describe('Additional notes for handoff'),
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const issueStatusUpdateSchema = z.object({
|
|
367
|
+
issueId: z.string().min(1).describe('Issue ID to update'),
|
|
368
|
+
claimantId: z.string().min(1).describe('Current claimant ID'),
|
|
369
|
+
status: z.enum(['active', 'blocked', 'in-review', 'completed']).describe('New status'),
|
|
370
|
+
progress: z.number().min(0).max(100).optional().describe('Progress percentage (0-100)'),
|
|
371
|
+
notes: z.string().optional().describe('Status update notes'),
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const issueListAvailableSchema = z.object({
|
|
375
|
+
priority: z.enum(['critical', 'high', 'medium', 'low']).optional()
|
|
376
|
+
.describe('Filter by priority'),
|
|
377
|
+
labels: z.array(z.string()).optional().describe('Filter by labels'),
|
|
378
|
+
repository: z.string().optional().describe('Filter by repository'),
|
|
379
|
+
limit: z.number().int().positive().max(100).default(50).describe('Maximum results'),
|
|
380
|
+
offset: z.number().int().nonnegative().default(0).describe('Pagination offset'),
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const issueListMineSchema = z.object({
|
|
384
|
+
claimantId: z.string().min(1).describe('Claimant ID'),
|
|
385
|
+
status: z.enum(['active', 'blocked', 'in-review', 'completed', 'released', 'stolen']).optional()
|
|
386
|
+
.describe('Filter by status'),
|
|
387
|
+
limit: z.number().int().positive().max(100).default(50).describe('Maximum results'),
|
|
388
|
+
offset: z.number().int().nonnegative().default(0).describe('Pagination offset'),
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const issueBoardSchema = z.object({
|
|
392
|
+
includeAgents: z.boolean().default(true).describe('Include agent claims'),
|
|
393
|
+
includeHumans: z.boolean().default(true).describe('Include human claims'),
|
|
394
|
+
groupBy: z.enum(['claimant', 'priority', 'status']).optional()
|
|
395
|
+
.describe('Group claims by field'),
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Work Stealing Schemas
|
|
399
|
+
const issueMarkStealableSchema = z.object({
|
|
400
|
+
issueId: z.string().min(1).describe('Issue ID to mark as stealable'),
|
|
401
|
+
claimantId: z.string().min(1).describe('Current claimant ID'),
|
|
402
|
+
reason: z.string().optional().describe('Reason for making stealable'),
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const issueStealSchema = z.object({
|
|
406
|
+
issueId: z.string().min(1).describe('Issue ID to steal'),
|
|
407
|
+
stealerId: z.string().min(1).describe('ID of the stealer'),
|
|
408
|
+
stealerType: z.enum(['human', 'agent']).describe('Type of stealer'),
|
|
409
|
+
reason: z.string().optional().describe('Reason for stealing'),
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const issueGetStealableSchema = z.object({
|
|
413
|
+
priority: z.enum(['critical', 'high', 'medium', 'low']).optional()
|
|
414
|
+
.describe('Filter by priority'),
|
|
415
|
+
limit: z.number().int().positive().max(100).default(50).describe('Maximum results'),
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const issueContestStealSchema = z.object({
|
|
419
|
+
issueId: z.string().min(1).describe('Issue ID being contested'),
|
|
420
|
+
contesterId: z.string().min(1).describe('ID of the contester'),
|
|
421
|
+
reason: z.string().min(1).describe('Reason for contesting'),
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Load Balancing Schemas
|
|
425
|
+
const agentLoadInfoSchema = z.object({
|
|
426
|
+
agentId: z.string().min(1).describe('Agent ID to get load info for'),
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const swarmRebalanceSchema = z.object({
|
|
430
|
+
strategy: z.enum(['round-robin', 'least-loaded', 'priority-based', 'capability-based'])
|
|
431
|
+
.default('least-loaded').describe('Rebalancing strategy'),
|
|
432
|
+
dryRun: z.boolean().default(false).describe('Simulate without making changes'),
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const swarmLoadOverviewSchema = z.object({
|
|
436
|
+
includeRecommendations: z.boolean().default(true)
|
|
437
|
+
.describe('Include optimization recommendations'),
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Additional Tools Schemas
|
|
441
|
+
const claimHistorySchema = z.object({
|
|
442
|
+
issueId: z.string().min(1).describe('Issue ID to get history for'),
|
|
443
|
+
limit: z.number().int().positive().max(100).default(50).describe('Maximum entries'),
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const claimMetricsSchema = z.object({
|
|
447
|
+
timeRange: z.enum(['1h', '24h', '7d', '30d', 'all']).default('24h')
|
|
448
|
+
.describe('Time range for metrics'),
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const claimConfigSchema = z.object({
|
|
452
|
+
action: z.enum(['get', 'set']).describe('Get or set configuration'),
|
|
453
|
+
config: z.object({
|
|
454
|
+
defaultExpirationMs: z.number().int().positive().optional(),
|
|
455
|
+
maxClaimsPerAgent: z.number().int().positive().optional(),
|
|
456
|
+
contestWindowMs: z.number().int().positive().optional(),
|
|
457
|
+
autoReleaseOnInactivityMs: z.number().int().positive().optional(),
|
|
458
|
+
}).optional().describe('Configuration values (for set action)'),
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// ============================================================================
|
|
462
|
+
// Tool Handlers
|
|
463
|
+
// ============================================================================
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Claim an issue to work on
|
|
467
|
+
*/
|
|
468
|
+
async function handleIssueClaim(
|
|
469
|
+
input: z.infer<typeof issueClaimSchema>,
|
|
470
|
+
context?: ToolContext
|
|
471
|
+
): Promise<{
|
|
472
|
+
claimId: string;
|
|
473
|
+
issueId: string;
|
|
474
|
+
claimantId: string;
|
|
475
|
+
claimantType: ClaimantType;
|
|
476
|
+
status: ClaimStatus;
|
|
477
|
+
claimedAt: string;
|
|
478
|
+
expiresAt?: string;
|
|
479
|
+
}> {
|
|
480
|
+
initializeMockData();
|
|
481
|
+
|
|
482
|
+
// Try to use claims service if available
|
|
483
|
+
if (context?.claimsService) {
|
|
484
|
+
const claim = await context.claimsService.claimIssue(input);
|
|
485
|
+
return {
|
|
486
|
+
claimId: claim.id,
|
|
487
|
+
issueId: claim.issueId,
|
|
488
|
+
claimantId: claim.claimantId,
|
|
489
|
+
claimantType: claim.claimantType,
|
|
490
|
+
status: claim.status,
|
|
491
|
+
claimedAt: claim.claimedAt,
|
|
492
|
+
expiresAt: claim.expiresAt,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Simple implementation
|
|
497
|
+
const issue = issueStore.get(input.issueId);
|
|
498
|
+
if (!issue) {
|
|
499
|
+
throw new Error(`Issue not found: ${input.issueId}`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (issue.claimedBy) {
|
|
503
|
+
throw new Error(`Issue ${input.issueId} is already claimed by ${issue.claimedBy}`);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const claimId = generateSecureId('claim');
|
|
507
|
+
const claimedAt = new Date().toISOString();
|
|
508
|
+
const expiresAt = input.expiresInMs
|
|
509
|
+
? new Date(Date.now() + input.expiresInMs).toISOString()
|
|
510
|
+
: undefined;
|
|
511
|
+
|
|
512
|
+
const claim: Claim = {
|
|
513
|
+
id: claimId,
|
|
514
|
+
issueId: input.issueId,
|
|
515
|
+
claimantType: input.claimantType,
|
|
516
|
+
claimantId: input.claimantId,
|
|
517
|
+
status: 'active',
|
|
518
|
+
priority: input.priority || issue.priority,
|
|
519
|
+
stealable: false,
|
|
520
|
+
claimedAt,
|
|
521
|
+
lastActivityAt: claimedAt,
|
|
522
|
+
expiresAt,
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
claimStore.set(claimId, claim);
|
|
526
|
+
issue.claimedBy = input.claimantId;
|
|
527
|
+
|
|
528
|
+
return {
|
|
529
|
+
claimId,
|
|
530
|
+
issueId: input.issueId,
|
|
531
|
+
claimantId: input.claimantId,
|
|
532
|
+
claimantType: input.claimantType,
|
|
533
|
+
status: 'active',
|
|
534
|
+
claimedAt,
|
|
535
|
+
expiresAt,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Release a claim
|
|
541
|
+
*/
|
|
542
|
+
async function handleIssueRelease(
|
|
543
|
+
input: z.infer<typeof issueReleaseSchema>,
|
|
544
|
+
context?: ToolContext
|
|
545
|
+
): Promise<{
|
|
546
|
+
released: boolean;
|
|
547
|
+
issueId: string;
|
|
548
|
+
releasedAt: string;
|
|
549
|
+
reason?: string;
|
|
550
|
+
}> {
|
|
551
|
+
if (context?.claimsService) {
|
|
552
|
+
const result = await context.claimsService.releaseClaim(input);
|
|
553
|
+
return {
|
|
554
|
+
released: result.released,
|
|
555
|
+
issueId: input.issueId,
|
|
556
|
+
releasedAt: result.releasedAt,
|
|
557
|
+
reason: input.reason,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Simple implementation
|
|
562
|
+
const issue = issueStore.get(input.issueId);
|
|
563
|
+
if (!issue) {
|
|
564
|
+
throw new Error(`Issue not found: ${input.issueId}`);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (issue.claimedBy !== input.claimantId) {
|
|
568
|
+
throw new Error(`Issue ${input.issueId} is not claimed by ${input.claimantId}`);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Find and update the claim
|
|
572
|
+
for (const claim of claimStore.values()) {
|
|
573
|
+
if (claim.issueId === input.issueId && claim.claimantId === input.claimantId) {
|
|
574
|
+
claim.status = 'released';
|
|
575
|
+
claim.lastActivityAt = new Date().toISOString();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
issue.claimedBy = undefined;
|
|
580
|
+
|
|
581
|
+
return {
|
|
582
|
+
released: true,
|
|
583
|
+
issueId: input.issueId,
|
|
584
|
+
releasedAt: new Date().toISOString(),
|
|
585
|
+
reason: input.reason,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Request handoff to another agent/human
|
|
591
|
+
*/
|
|
592
|
+
async function handleIssueHandoff(
|
|
593
|
+
input: z.infer<typeof issueHandoffSchema>,
|
|
594
|
+
context?: ToolContext
|
|
595
|
+
): Promise<{
|
|
596
|
+
handoffId: string;
|
|
597
|
+
issueId: string;
|
|
598
|
+
fromId: string;
|
|
599
|
+
toId?: string;
|
|
600
|
+
toType?: ClaimantType;
|
|
601
|
+
status: 'pending' | 'accepted' | 'rejected';
|
|
602
|
+
reason: HandoffReason;
|
|
603
|
+
createdAt: string;
|
|
604
|
+
}> {
|
|
605
|
+
if (context?.claimsService) {
|
|
606
|
+
const result = await context.claimsService.requestHandoff(input);
|
|
607
|
+
return {
|
|
608
|
+
handoffId: result.handoffId,
|
|
609
|
+
issueId: input.issueId,
|
|
610
|
+
fromId: input.fromId,
|
|
611
|
+
toId: input.toId,
|
|
612
|
+
toType: input.toType,
|
|
613
|
+
status: result.status as 'pending' | 'accepted' | 'rejected',
|
|
614
|
+
reason: input.reason,
|
|
615
|
+
createdAt: new Date().toISOString(),
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Simple implementation
|
|
620
|
+
const handoffId = generateSecureId('handoff');
|
|
621
|
+
|
|
622
|
+
return {
|
|
623
|
+
handoffId,
|
|
624
|
+
issueId: input.issueId,
|
|
625
|
+
fromId: input.fromId,
|
|
626
|
+
toId: input.toId,
|
|
627
|
+
toType: input.toType,
|
|
628
|
+
status: 'pending',
|
|
629
|
+
reason: input.reason,
|
|
630
|
+
createdAt: new Date().toISOString(),
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Update claim status
|
|
636
|
+
*/
|
|
637
|
+
async function handleIssueStatusUpdate(
|
|
638
|
+
input: z.infer<typeof issueStatusUpdateSchema>,
|
|
639
|
+
context?: ToolContext
|
|
640
|
+
): Promise<{
|
|
641
|
+
issueId: string;
|
|
642
|
+
status: ClaimStatus;
|
|
643
|
+
progress?: number;
|
|
644
|
+
updatedAt: string;
|
|
645
|
+
notes?: string;
|
|
646
|
+
}> {
|
|
647
|
+
if (context?.claimsService) {
|
|
648
|
+
const claim = await context.claimsService.updateClaimStatus(input);
|
|
649
|
+
return {
|
|
650
|
+
issueId: claim.issueId,
|
|
651
|
+
status: claim.status,
|
|
652
|
+
progress: input.progress,
|
|
653
|
+
updatedAt: claim.lastActivityAt,
|
|
654
|
+
notes: input.notes,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Simple implementation
|
|
659
|
+
for (const claim of claimStore.values()) {
|
|
660
|
+
if (claim.issueId === input.issueId && claim.claimantId === input.claimantId) {
|
|
661
|
+
claim.status = input.status;
|
|
662
|
+
claim.lastActivityAt = new Date().toISOString();
|
|
663
|
+
if (input.progress !== undefined) {
|
|
664
|
+
claim.metadata = { ...claim.metadata, progress: input.progress };
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
issueId: input.issueId,
|
|
668
|
+
status: input.status,
|
|
669
|
+
progress: input.progress,
|
|
670
|
+
updatedAt: claim.lastActivityAt,
|
|
671
|
+
notes: input.notes,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
throw new Error(`No active claim found for issue ${input.issueId} by ${input.claimantId}`);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* List unclaimed issues
|
|
681
|
+
*/
|
|
682
|
+
async function handleIssueListAvailable(
|
|
683
|
+
input: z.infer<typeof issueListAvailableSchema>,
|
|
684
|
+
context?: ToolContext
|
|
685
|
+
): Promise<{
|
|
686
|
+
issues: Issue[];
|
|
687
|
+
total: number;
|
|
688
|
+
limit: number;
|
|
689
|
+
offset: number;
|
|
690
|
+
}> {
|
|
691
|
+
initializeMockData();
|
|
692
|
+
|
|
693
|
+
if (context?.claimsService) {
|
|
694
|
+
const result = await context.claimsService.listAvailableIssues(input);
|
|
695
|
+
return {
|
|
696
|
+
...result,
|
|
697
|
+
limit: input.limit,
|
|
698
|
+
offset: input.offset,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Simple implementation
|
|
703
|
+
let issues = Array.from(issueStore.values()).filter(issue => !issue.claimedBy);
|
|
704
|
+
|
|
705
|
+
if (input.priority) {
|
|
706
|
+
issues = issues.filter(issue => issue.priority === input.priority);
|
|
707
|
+
}
|
|
708
|
+
if (input.labels && input.labels.length > 0) {
|
|
709
|
+
issues = issues.filter(issue =>
|
|
710
|
+
input.labels!.some(label => issue.labels?.includes(label))
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
if (input.repository) {
|
|
714
|
+
issues = issues.filter(issue => issue.repository === input.repository);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const total = issues.length;
|
|
718
|
+
const paginated = issues.slice(input.offset, input.offset + input.limit);
|
|
719
|
+
|
|
720
|
+
return {
|
|
721
|
+
issues: paginated,
|
|
722
|
+
total,
|
|
723
|
+
limit: input.limit,
|
|
724
|
+
offset: input.offset,
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* List my claims
|
|
730
|
+
*/
|
|
731
|
+
async function handleIssueListMine(
|
|
732
|
+
input: z.infer<typeof issueListMineSchema>,
|
|
733
|
+
context?: ToolContext
|
|
734
|
+
): Promise<{
|
|
735
|
+
claims: Claim[];
|
|
736
|
+
total: number;
|
|
737
|
+
limit: number;
|
|
738
|
+
offset: number;
|
|
739
|
+
}> {
|
|
740
|
+
if (context?.claimsService) {
|
|
741
|
+
const result = await context.claimsService.listMyClaims(input);
|
|
742
|
+
return {
|
|
743
|
+
...result,
|
|
744
|
+
limit: input.limit,
|
|
745
|
+
offset: input.offset,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Simple implementation
|
|
750
|
+
let claims = Array.from(claimStore.values())
|
|
751
|
+
.filter(claim => claim.claimantId === input.claimantId);
|
|
752
|
+
|
|
753
|
+
if (input.status) {
|
|
754
|
+
claims = claims.filter(claim => claim.status === input.status);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const total = claims.length;
|
|
758
|
+
const paginated = claims.slice(input.offset, input.offset + input.limit);
|
|
759
|
+
|
|
760
|
+
return {
|
|
761
|
+
claims: paginated,
|
|
762
|
+
total,
|
|
763
|
+
limit: input.limit,
|
|
764
|
+
offset: input.offset,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* View claim board
|
|
770
|
+
*/
|
|
771
|
+
async function handleIssueBoard(
|
|
772
|
+
input: z.infer<typeof issueBoardSchema>,
|
|
773
|
+
context?: ToolContext
|
|
774
|
+
): Promise<{
|
|
775
|
+
claims: Claim[];
|
|
776
|
+
totalClaims: number;
|
|
777
|
+
byClaimant?: Record<string, number>;
|
|
778
|
+
byPriority?: Record<IssuePriority, number>;
|
|
779
|
+
byStatus?: Record<ClaimStatus, number>;
|
|
780
|
+
}> {
|
|
781
|
+
if (context?.claimsService) {
|
|
782
|
+
const result = await context.claimsService.getClaimBoard(input);
|
|
783
|
+
return {
|
|
784
|
+
claims: result.claims,
|
|
785
|
+
totalClaims: result.claims.length,
|
|
786
|
+
byClaimant: result.byClaimant
|
|
787
|
+
? Object.fromEntries(Object.entries(result.byClaimant).map(([k, v]) => [k, v.length]))
|
|
788
|
+
: undefined,
|
|
789
|
+
byPriority: result.byPriority
|
|
790
|
+
? Object.fromEntries(Object.entries(result.byPriority).map(([k, v]) => [k, v.length])) as Record<IssuePriority, number>
|
|
791
|
+
: undefined,
|
|
792
|
+
byStatus: result.byStatus
|
|
793
|
+
? Object.fromEntries(Object.entries(result.byStatus).map(([k, v]) => [k, v.length])) as Record<ClaimStatus, number>
|
|
794
|
+
: undefined,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Simple implementation
|
|
799
|
+
let claims = Array.from(claimStore.values());
|
|
800
|
+
|
|
801
|
+
if (!input.includeAgents) {
|
|
802
|
+
claims = claims.filter(c => c.claimantType !== 'agent');
|
|
803
|
+
}
|
|
804
|
+
if (!input.includeHumans) {
|
|
805
|
+
claims = claims.filter(c => c.claimantType !== 'human');
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const result: {
|
|
809
|
+
claims: Claim[];
|
|
810
|
+
totalClaims: number;
|
|
811
|
+
byClaimant?: Record<string, number>;
|
|
812
|
+
byPriority?: Record<IssuePriority, number>;
|
|
813
|
+
byStatus?: Record<ClaimStatus, number>;
|
|
814
|
+
} = {
|
|
815
|
+
claims,
|
|
816
|
+
totalClaims: claims.length,
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
if (input.groupBy === 'claimant') {
|
|
820
|
+
result.byClaimant = {};
|
|
821
|
+
claims.forEach(c => {
|
|
822
|
+
result.byClaimant![c.claimantId] = (result.byClaimant![c.claimantId] || 0) + 1;
|
|
823
|
+
});
|
|
824
|
+
} else if (input.groupBy === 'priority') {
|
|
825
|
+
result.byPriority = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
826
|
+
claims.forEach(c => {
|
|
827
|
+
result.byPriority![c.priority]++;
|
|
828
|
+
});
|
|
829
|
+
} else if (input.groupBy === 'status') {
|
|
830
|
+
result.byStatus = { active: 0, blocked: 0, 'in-review': 0, completed: 0, released: 0, stolen: 0 };
|
|
831
|
+
claims.forEach(c => {
|
|
832
|
+
result.byStatus![c.status]++;
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
return result;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Mark claim as stealable
|
|
841
|
+
*/
|
|
842
|
+
async function handleIssueMarkStealable(
|
|
843
|
+
input: z.infer<typeof issueMarkStealableSchema>,
|
|
844
|
+
context?: ToolContext
|
|
845
|
+
): Promise<{
|
|
846
|
+
marked: boolean;
|
|
847
|
+
issueId: string;
|
|
848
|
+
markedAt: string;
|
|
849
|
+
reason?: string;
|
|
850
|
+
}> {
|
|
851
|
+
if (context?.claimsService) {
|
|
852
|
+
const result = await context.claimsService.markStealable(input);
|
|
853
|
+
return {
|
|
854
|
+
marked: result.marked,
|
|
855
|
+
issueId: input.issueId,
|
|
856
|
+
markedAt: result.markedAt,
|
|
857
|
+
reason: input.reason,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Simple implementation
|
|
862
|
+
for (const claim of claimStore.values()) {
|
|
863
|
+
if (claim.issueId === input.issueId && claim.claimantId === input.claimantId) {
|
|
864
|
+
claim.stealable = true;
|
|
865
|
+
claim.stealableReason = input.reason;
|
|
866
|
+
claim.lastActivityAt = new Date().toISOString();
|
|
867
|
+
return {
|
|
868
|
+
marked: true,
|
|
869
|
+
issueId: input.issueId,
|
|
870
|
+
markedAt: claim.lastActivityAt,
|
|
871
|
+
reason: input.reason,
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
throw new Error(`No active claim found for issue ${input.issueId} by ${input.claimantId}`);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Steal a stealable issue
|
|
881
|
+
*/
|
|
882
|
+
async function handleIssueSteal(
|
|
883
|
+
input: z.infer<typeof issueStealSchema>,
|
|
884
|
+
context?: ToolContext
|
|
885
|
+
): Promise<{
|
|
886
|
+
stolen: boolean;
|
|
887
|
+
issueId: string;
|
|
888
|
+
newClaimId: string;
|
|
889
|
+
previousClaimant: string;
|
|
890
|
+
contestWindowMs: number;
|
|
891
|
+
stolenAt: string;
|
|
892
|
+
}> {
|
|
893
|
+
if (context?.claimsService) {
|
|
894
|
+
const result = await context.claimsService.stealClaim(input);
|
|
895
|
+
return {
|
|
896
|
+
stolen: result.stolen,
|
|
897
|
+
issueId: input.issueId,
|
|
898
|
+
newClaimId: result.claim.id,
|
|
899
|
+
previousClaimant: result.previousClaimant,
|
|
900
|
+
contestWindowMs: result.contestWindow,
|
|
901
|
+
stolenAt: result.claim.claimedAt,
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// Simple implementation
|
|
906
|
+
for (const claim of claimStore.values()) {
|
|
907
|
+
if (claim.issueId === input.issueId && claim.stealable) {
|
|
908
|
+
const previousClaimant = claim.claimantId;
|
|
909
|
+
|
|
910
|
+
// Update old claim
|
|
911
|
+
claim.status = 'stolen';
|
|
912
|
+
claim.lastActivityAt = new Date().toISOString();
|
|
913
|
+
|
|
914
|
+
// Create new claim
|
|
915
|
+
const newClaimId = generateSecureId('claim');
|
|
916
|
+
const stolenAt = new Date().toISOString();
|
|
917
|
+
const newClaim: Claim = {
|
|
918
|
+
id: newClaimId,
|
|
919
|
+
issueId: input.issueId,
|
|
920
|
+
claimantType: input.stealerType,
|
|
921
|
+
claimantId: input.stealerId,
|
|
922
|
+
status: 'active',
|
|
923
|
+
priority: claim.priority,
|
|
924
|
+
stealable: false,
|
|
925
|
+
claimedAt: stolenAt,
|
|
926
|
+
lastActivityAt: stolenAt,
|
|
927
|
+
};
|
|
928
|
+
claimStore.set(newClaimId, newClaim);
|
|
929
|
+
|
|
930
|
+
// Update issue
|
|
931
|
+
const issue = issueStore.get(input.issueId);
|
|
932
|
+
if (issue) {
|
|
933
|
+
issue.claimedBy = input.stealerId;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
return {
|
|
937
|
+
stolen: true,
|
|
938
|
+
issueId: input.issueId,
|
|
939
|
+
newClaimId,
|
|
940
|
+
previousClaimant,
|
|
941
|
+
contestWindowMs: 300000, // 5 minutes
|
|
942
|
+
stolenAt,
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
throw new Error(`Issue ${input.issueId} is not stealable or not claimed`);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Get stealable issues
|
|
952
|
+
*/
|
|
953
|
+
async function handleIssueGetStealable(
|
|
954
|
+
input: z.infer<typeof issueGetStealableSchema>,
|
|
955
|
+
context?: ToolContext
|
|
956
|
+
): Promise<{
|
|
957
|
+
issues: Array<{
|
|
958
|
+
issueId: string;
|
|
959
|
+
title: string;
|
|
960
|
+
priority: IssuePriority;
|
|
961
|
+
currentClaimant: string;
|
|
962
|
+
stealableReason?: string;
|
|
963
|
+
}>;
|
|
964
|
+
total: number;
|
|
965
|
+
}> {
|
|
966
|
+
if (context?.claimsService) {
|
|
967
|
+
const result = await context.claimsService.getStealableIssues(input);
|
|
968
|
+
return {
|
|
969
|
+
issues: result.issues.map(issue => ({
|
|
970
|
+
issueId: issue.id,
|
|
971
|
+
title: issue.title,
|
|
972
|
+
priority: issue.priority,
|
|
973
|
+
currentClaimant: issue.claimedBy || 'unknown',
|
|
974
|
+
stealableReason: issue.stealableReason,
|
|
975
|
+
})),
|
|
976
|
+
total: result.total,
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// Simple implementation
|
|
981
|
+
let stealableClaims = Array.from(claimStore.values()).filter(c => c.stealable);
|
|
982
|
+
|
|
983
|
+
if (input.priority) {
|
|
984
|
+
stealableClaims = stealableClaims.filter(c => c.priority === input.priority);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const issues = stealableClaims.slice(0, input.limit).map(claim => {
|
|
988
|
+
const issue = issueStore.get(claim.issueId);
|
|
989
|
+
return {
|
|
990
|
+
issueId: claim.issueId,
|
|
991
|
+
title: issue?.title || 'Unknown',
|
|
992
|
+
priority: claim.priority,
|
|
993
|
+
currentClaimant: claim.claimantId,
|
|
994
|
+
stealableReason: claim.stealableReason,
|
|
995
|
+
};
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
return {
|
|
999
|
+
issues,
|
|
1000
|
+
total: stealableClaims.length,
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Contest a steal
|
|
1006
|
+
*/
|
|
1007
|
+
async function handleIssueContestSteal(
|
|
1008
|
+
input: z.infer<typeof issueContestStealSchema>,
|
|
1009
|
+
context?: ToolContext
|
|
1010
|
+
): Promise<{
|
|
1011
|
+
contested: boolean;
|
|
1012
|
+
contestId: string;
|
|
1013
|
+
issueId: string;
|
|
1014
|
+
contesterId: string;
|
|
1015
|
+
status: 'pending' | 'upheld' | 'reversed';
|
|
1016
|
+
contestedAt: string;
|
|
1017
|
+
}> {
|
|
1018
|
+
if (context?.claimsService) {
|
|
1019
|
+
const result = await context.claimsService.contestSteal(input);
|
|
1020
|
+
return {
|
|
1021
|
+
contested: result.contested,
|
|
1022
|
+
contestId: generateSecureId('contest'),
|
|
1023
|
+
issueId: input.issueId,
|
|
1024
|
+
contesterId: input.contesterId,
|
|
1025
|
+
status: result.resolution,
|
|
1026
|
+
contestedAt: result.resolvedAt || new Date().toISOString(),
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// Simple implementation
|
|
1031
|
+
return {
|
|
1032
|
+
contested: true,
|
|
1033
|
+
contestId: generateSecureId('contest'),
|
|
1034
|
+
issueId: input.issueId,
|
|
1035
|
+
contesterId: input.contesterId,
|
|
1036
|
+
status: 'pending',
|
|
1037
|
+
contestedAt: new Date().toISOString(),
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Get agent load info
|
|
1043
|
+
*/
|
|
1044
|
+
async function handleAgentLoadInfo(
|
|
1045
|
+
input: z.infer<typeof agentLoadInfoSchema>,
|
|
1046
|
+
context?: ToolContext
|
|
1047
|
+
): Promise<AgentLoad> {
|
|
1048
|
+
if (context?.claimsService) {
|
|
1049
|
+
return context.claimsService.getAgentLoad(input);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// Simple implementation
|
|
1053
|
+
const claims = Array.from(claimStore.values())
|
|
1054
|
+
.filter(c => c.claimantId === input.agentId && c.status === 'active');
|
|
1055
|
+
|
|
1056
|
+
return {
|
|
1057
|
+
agentId: input.agentId,
|
|
1058
|
+
agentType: 'worker',
|
|
1059
|
+
currentClaims: claims.length,
|
|
1060
|
+
maxClaims: 5,
|
|
1061
|
+
utilizationPercent: Math.min(100, (claims.length / 5) * 100),
|
|
1062
|
+
activeTasks: claims.length,
|
|
1063
|
+
queuedTasks: 0,
|
|
1064
|
+
averageTaskDuration: 3600000, // 1 hour
|
|
1065
|
+
lastActivityAt: new Date().toISOString(),
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* Trigger swarm rebalancing
|
|
1071
|
+
*/
|
|
1072
|
+
async function handleSwarmRebalance(
|
|
1073
|
+
input: z.infer<typeof swarmRebalanceSchema>,
|
|
1074
|
+
context?: ToolContext
|
|
1075
|
+
): Promise<{
|
|
1076
|
+
rebalanced: boolean;
|
|
1077
|
+
strategy: string;
|
|
1078
|
+
changes: Array<{ issueId: string; from: string; to: string }>;
|
|
1079
|
+
dryRun: boolean;
|
|
1080
|
+
rebalancedAt: string;
|
|
1081
|
+
}> {
|
|
1082
|
+
if (context?.claimsService) {
|
|
1083
|
+
const result = await context.claimsService.rebalanceSwarm(input);
|
|
1084
|
+
return {
|
|
1085
|
+
rebalanced: result.rebalanced,
|
|
1086
|
+
strategy: input.strategy,
|
|
1087
|
+
changes: result.changes,
|
|
1088
|
+
dryRun: result.dryRun,
|
|
1089
|
+
rebalancedAt: new Date().toISOString(),
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// Simple implementation - no actual rebalancing
|
|
1094
|
+
return {
|
|
1095
|
+
rebalanced: !input.dryRun,
|
|
1096
|
+
strategy: input.strategy,
|
|
1097
|
+
changes: [],
|
|
1098
|
+
dryRun: input.dryRun,
|
|
1099
|
+
rebalancedAt: new Date().toISOString(),
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Get swarm-wide load overview
|
|
1105
|
+
*/
|
|
1106
|
+
async function handleSwarmLoadOverview(
|
|
1107
|
+
input: z.infer<typeof swarmLoadOverviewSchema>,
|
|
1108
|
+
context?: ToolContext
|
|
1109
|
+
): Promise<{
|
|
1110
|
+
totalAgents: number;
|
|
1111
|
+
totalClaims: number;
|
|
1112
|
+
averageLoad: number;
|
|
1113
|
+
agents: Array<{ agentId: string; currentClaims: number; utilizationPercent: number }>;
|
|
1114
|
+
bottlenecks: string[];
|
|
1115
|
+
recommendations: string[];
|
|
1116
|
+
}> {
|
|
1117
|
+
if (context?.claimsService) {
|
|
1118
|
+
const result = await context.claimsService.getLoadOverview();
|
|
1119
|
+
return {
|
|
1120
|
+
...result,
|
|
1121
|
+
agents: result.agents.map(a => ({
|
|
1122
|
+
agentId: a.agentId,
|
|
1123
|
+
currentClaims: a.currentClaims,
|
|
1124
|
+
utilizationPercent: a.utilizationPercent,
|
|
1125
|
+
})),
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Simple implementation
|
|
1130
|
+
const claimsByAgent = new Map<string, number>();
|
|
1131
|
+
for (const claim of claimStore.values()) {
|
|
1132
|
+
if (claim.status === 'active') {
|
|
1133
|
+
claimsByAgent.set(claim.claimantId, (claimsByAgent.get(claim.claimantId) || 0) + 1);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
const agents = Array.from(claimsByAgent.entries()).map(([agentId, claims]) => ({
|
|
1138
|
+
agentId,
|
|
1139
|
+
currentClaims: claims,
|
|
1140
|
+
utilizationPercent: Math.min(100, (claims / 5) * 100),
|
|
1141
|
+
}));
|
|
1142
|
+
|
|
1143
|
+
const totalClaims = Array.from(claimsByAgent.values()).reduce((a, b) => a + b, 0);
|
|
1144
|
+
const avgLoad = agents.length > 0
|
|
1145
|
+
? agents.reduce((a, b) => a + b.utilizationPercent, 0) / agents.length
|
|
1146
|
+
: 0;
|
|
1147
|
+
|
|
1148
|
+
const result: {
|
|
1149
|
+
totalAgents: number;
|
|
1150
|
+
totalClaims: number;
|
|
1151
|
+
averageLoad: number;
|
|
1152
|
+
agents: Array<{ agentId: string; currentClaims: number; utilizationPercent: number }>;
|
|
1153
|
+
bottlenecks: string[];
|
|
1154
|
+
recommendations: string[];
|
|
1155
|
+
} = {
|
|
1156
|
+
totalAgents: agents.length,
|
|
1157
|
+
totalClaims,
|
|
1158
|
+
averageLoad: Math.round(avgLoad * 100) / 100,
|
|
1159
|
+
agents,
|
|
1160
|
+
bottlenecks: [],
|
|
1161
|
+
recommendations: [],
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
if (input.includeRecommendations) {
|
|
1165
|
+
const overloaded = agents.filter(a => a.utilizationPercent > 80);
|
|
1166
|
+
if (overloaded.length > 0) {
|
|
1167
|
+
result.bottlenecks = overloaded.map(a => a.agentId);
|
|
1168
|
+
result.recommendations.push('Consider rebalancing claims to reduce load on overloaded agents');
|
|
1169
|
+
}
|
|
1170
|
+
if (avgLoad > 70) {
|
|
1171
|
+
result.recommendations.push('Swarm is under high load. Consider scaling up agent count.');
|
|
1172
|
+
}
|
|
1173
|
+
if (avgLoad < 30 && agents.length > 1) {
|
|
1174
|
+
result.recommendations.push('Swarm has low utilization. Consider consolidating agents.');
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
return result;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Get claim history
|
|
1183
|
+
*/
|
|
1184
|
+
async function handleClaimHistory(
|
|
1185
|
+
input: z.infer<typeof claimHistorySchema>,
|
|
1186
|
+
context?: ToolContext
|
|
1187
|
+
): Promise<{
|
|
1188
|
+
issueId: string;
|
|
1189
|
+
history: ClaimHistoryEntry[];
|
|
1190
|
+
total: number;
|
|
1191
|
+
}> {
|
|
1192
|
+
if (context?.claimsService) {
|
|
1193
|
+
const result = await context.claimsService.getClaimHistory(input);
|
|
1194
|
+
return {
|
|
1195
|
+
issueId: input.issueId,
|
|
1196
|
+
history: result.history,
|
|
1197
|
+
total: result.total,
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// Simple implementation - mock history
|
|
1202
|
+
const claims = Array.from(claimStore.values())
|
|
1203
|
+
.filter(c => c.issueId === input.issueId)
|
|
1204
|
+
.sort((a, b) => new Date(b.claimedAt).getTime() - new Date(a.claimedAt).getTime());
|
|
1205
|
+
|
|
1206
|
+
const history: ClaimHistoryEntry[] = claims.flatMap(claim => [
|
|
1207
|
+
{
|
|
1208
|
+
timestamp: claim.claimedAt,
|
|
1209
|
+
action: 'claimed',
|
|
1210
|
+
actorId: claim.claimantId,
|
|
1211
|
+
actorType: claim.claimantType,
|
|
1212
|
+
},
|
|
1213
|
+
...(claim.status !== 'active' ? [{
|
|
1214
|
+
timestamp: claim.lastActivityAt,
|
|
1215
|
+
action: claim.status,
|
|
1216
|
+
actorId: claim.claimantId,
|
|
1217
|
+
actorType: claim.claimantType,
|
|
1218
|
+
}] : []),
|
|
1219
|
+
]).slice(0, input.limit);
|
|
1220
|
+
|
|
1221
|
+
return {
|
|
1222
|
+
issueId: input.issueId,
|
|
1223
|
+
history,
|
|
1224
|
+
total: history.length,
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Get claim metrics
|
|
1230
|
+
*/
|
|
1231
|
+
async function handleClaimMetrics(
|
|
1232
|
+
input: z.infer<typeof claimMetricsSchema>,
|
|
1233
|
+
context?: ToolContext
|
|
1234
|
+
): Promise<{
|
|
1235
|
+
timeRange: string;
|
|
1236
|
+
totalClaims: number;
|
|
1237
|
+
activeClaims: number;
|
|
1238
|
+
completedClaims: number;
|
|
1239
|
+
stolenClaims: number;
|
|
1240
|
+
averageClaimDurationMs: number;
|
|
1241
|
+
claimsByPriority: Record<IssuePriority, number>;
|
|
1242
|
+
claimsByStatus: Record<ClaimStatus, number>;
|
|
1243
|
+
}> {
|
|
1244
|
+
if (context?.claimsService) {
|
|
1245
|
+
const metrics = await context.claimsService.getMetrics();
|
|
1246
|
+
return {
|
|
1247
|
+
timeRange: input.timeRange,
|
|
1248
|
+
totalClaims: metrics.totalClaims,
|
|
1249
|
+
activeClaims: metrics.activeClaims,
|
|
1250
|
+
completedClaims: metrics.completedClaims,
|
|
1251
|
+
stolenClaims: metrics.stolenClaims,
|
|
1252
|
+
averageClaimDurationMs: metrics.averageClaimDuration,
|
|
1253
|
+
claimsByPriority: metrics.claimsByPriority,
|
|
1254
|
+
claimsByStatus: metrics.claimsByStatus,
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// Simple implementation
|
|
1259
|
+
const claims = Array.from(claimStore.values());
|
|
1260
|
+
const byPriority: Record<IssuePriority, number> = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
1261
|
+
const byStatus: Record<ClaimStatus, number> = {
|
|
1262
|
+
active: 0, blocked: 0, 'in-review': 0, completed: 0, released: 0, stolen: 0,
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
claims.forEach(c => {
|
|
1266
|
+
byPriority[c.priority]++;
|
|
1267
|
+
byStatus[c.status]++;
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
return {
|
|
1271
|
+
timeRange: input.timeRange,
|
|
1272
|
+
totalClaims: claims.length,
|
|
1273
|
+
activeClaims: byStatus.active,
|
|
1274
|
+
completedClaims: byStatus.completed,
|
|
1275
|
+
stolenClaims: byStatus.stolen,
|
|
1276
|
+
averageClaimDurationMs: 3600000, // 1 hour mock
|
|
1277
|
+
claimsByPriority: byPriority,
|
|
1278
|
+
claimsByStatus: byStatus,
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
/**
|
|
1283
|
+
* Get/set claim configuration
|
|
1284
|
+
*/
|
|
1285
|
+
async function handleClaimConfig(
|
|
1286
|
+
input: z.infer<typeof claimConfigSchema>,
|
|
1287
|
+
_context?: ToolContext
|
|
1288
|
+
): Promise<{
|
|
1289
|
+
action: 'get' | 'set';
|
|
1290
|
+
config: {
|
|
1291
|
+
defaultExpirationMs: number;
|
|
1292
|
+
maxClaimsPerAgent: number;
|
|
1293
|
+
contestWindowMs: number;
|
|
1294
|
+
autoReleaseOnInactivityMs: number;
|
|
1295
|
+
};
|
|
1296
|
+
updatedAt?: string;
|
|
1297
|
+
}> {
|
|
1298
|
+
// Default configuration
|
|
1299
|
+
const defaultConfig = {
|
|
1300
|
+
defaultExpirationMs: 86400000, // 24 hours
|
|
1301
|
+
maxClaimsPerAgent: 5,
|
|
1302
|
+
contestWindowMs: 300000, // 5 minutes
|
|
1303
|
+
autoReleaseOnInactivityMs: 7200000, // 2 hours
|
|
1304
|
+
};
|
|
1305
|
+
|
|
1306
|
+
if (input.action === 'get') {
|
|
1307
|
+
return {
|
|
1308
|
+
action: 'get',
|
|
1309
|
+
config: defaultConfig,
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Set action
|
|
1314
|
+
const newConfig = {
|
|
1315
|
+
...defaultConfig,
|
|
1316
|
+
...input.config,
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
return {
|
|
1320
|
+
action: 'set',
|
|
1321
|
+
config: newConfig,
|
|
1322
|
+
updatedAt: new Date().toISOString(),
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// ============================================================================
|
|
1327
|
+
// Tool Definitions
|
|
1328
|
+
// ============================================================================
|
|
1329
|
+
|
|
1330
|
+
// Core Claiming Tools
|
|
1331
|
+
|
|
1332
|
+
export const issueClaimTool: MCPTool = {
|
|
1333
|
+
name: 'claims/issue_claim',
|
|
1334
|
+
description: 'Claim an issue to work on. Prevents duplicate work by ensuring only one agent/human works on an issue at a time.',
|
|
1335
|
+
inputSchema: {
|
|
1336
|
+
type: 'object',
|
|
1337
|
+
properties: {
|
|
1338
|
+
issueId: { type: 'string', description: 'Issue ID to claim' },
|
|
1339
|
+
claimantType: { type: 'string', enum: ['human', 'agent'], description: 'Type of claimant' },
|
|
1340
|
+
claimantId: { type: 'string', description: 'ID of the claimant' },
|
|
1341
|
+
priority: {
|
|
1342
|
+
type: 'string',
|
|
1343
|
+
enum: ['critical', 'high', 'medium', 'low'],
|
|
1344
|
+
description: 'Override priority for the claim',
|
|
1345
|
+
},
|
|
1346
|
+
expiresInMs: {
|
|
1347
|
+
type: 'number',
|
|
1348
|
+
description: 'Claim expiration time in milliseconds',
|
|
1349
|
+
minimum: 1,
|
|
1350
|
+
},
|
|
1351
|
+
},
|
|
1352
|
+
required: ['issueId', 'claimantType', 'claimantId'],
|
|
1353
|
+
},
|
|
1354
|
+
handler: async (input, context) => {
|
|
1355
|
+
const validated = issueClaimSchema.parse(input);
|
|
1356
|
+
return handleIssueClaim(validated, context);
|
|
1357
|
+
},
|
|
1358
|
+
category: 'claims',
|
|
1359
|
+
tags: ['claims', 'issue', 'coordination'],
|
|
1360
|
+
version: '1.0.0',
|
|
1361
|
+
};
|
|
1362
|
+
|
|
1363
|
+
export const issueReleaseTool: MCPTool = {
|
|
1364
|
+
name: 'claims/issue_release',
|
|
1365
|
+
description: 'Release a claim on an issue, making it available for others to work on.',
|
|
1366
|
+
inputSchema: {
|
|
1367
|
+
type: 'object',
|
|
1368
|
+
properties: {
|
|
1369
|
+
issueId: { type: 'string', description: 'Issue ID to release' },
|
|
1370
|
+
claimantId: { type: 'string', description: 'ID of the current claimant' },
|
|
1371
|
+
reason: { type: 'string', description: 'Reason for releasing the claim' },
|
|
1372
|
+
},
|
|
1373
|
+
required: ['issueId', 'claimantId'],
|
|
1374
|
+
},
|
|
1375
|
+
handler: async (input, context) => {
|
|
1376
|
+
const validated = issueReleaseSchema.parse(input);
|
|
1377
|
+
return handleIssueRelease(validated, context);
|
|
1378
|
+
},
|
|
1379
|
+
category: 'claims',
|
|
1380
|
+
tags: ['claims', 'issue', 'release'],
|
|
1381
|
+
version: '1.0.0',
|
|
1382
|
+
};
|
|
1383
|
+
|
|
1384
|
+
export const issueHandoffTool: MCPTool = {
|
|
1385
|
+
name: 'claims/issue_handoff',
|
|
1386
|
+
description: 'Request handoff of an issue to another agent or human. Useful when blocked or needing specific expertise.',
|
|
1387
|
+
inputSchema: {
|
|
1388
|
+
type: 'object',
|
|
1389
|
+
properties: {
|
|
1390
|
+
issueId: { type: 'string', description: 'Issue ID for handoff' },
|
|
1391
|
+
fromId: { type: 'string', description: 'Current claimant ID' },
|
|
1392
|
+
toId: { type: 'string', description: 'Target claimant ID (optional for open handoff)' },
|
|
1393
|
+
toType: { type: 'string', enum: ['human', 'agent'], description: 'Target claimant type' },
|
|
1394
|
+
reason: {
|
|
1395
|
+
type: 'string',
|
|
1396
|
+
enum: ['blocked', 'expertise-needed', 'capacity', 'reassignment', 'other'],
|
|
1397
|
+
description: 'Reason for handoff',
|
|
1398
|
+
},
|
|
1399
|
+
notes: { type: 'string', description: 'Additional notes for handoff' },
|
|
1400
|
+
},
|
|
1401
|
+
required: ['issueId', 'fromId', 'reason'],
|
|
1402
|
+
},
|
|
1403
|
+
handler: async (input, context) => {
|
|
1404
|
+
const validated = issueHandoffSchema.parse(input);
|
|
1405
|
+
return handleIssueHandoff(validated, context);
|
|
1406
|
+
},
|
|
1407
|
+
category: 'claims',
|
|
1408
|
+
tags: ['claims', 'issue', 'handoff', 'coordination'],
|
|
1409
|
+
version: '1.0.0',
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1412
|
+
export const issueStatusUpdateTool: MCPTool = {
|
|
1413
|
+
name: 'claims/issue_status_update',
|
|
1414
|
+
description: 'Update the status of a claimed issue. Track progress and communicate blockers.',
|
|
1415
|
+
inputSchema: {
|
|
1416
|
+
type: 'object',
|
|
1417
|
+
properties: {
|
|
1418
|
+
issueId: { type: 'string', description: 'Issue ID to update' },
|
|
1419
|
+
claimantId: { type: 'string', description: 'Current claimant ID' },
|
|
1420
|
+
status: {
|
|
1421
|
+
type: 'string',
|
|
1422
|
+
enum: ['active', 'blocked', 'in-review', 'completed'],
|
|
1423
|
+
description: 'New status',
|
|
1424
|
+
},
|
|
1425
|
+
progress: {
|
|
1426
|
+
type: 'number',
|
|
1427
|
+
description: 'Progress percentage (0-100)',
|
|
1428
|
+
minimum: 0,
|
|
1429
|
+
maximum: 100,
|
|
1430
|
+
},
|
|
1431
|
+
notes: { type: 'string', description: 'Status update notes' },
|
|
1432
|
+
},
|
|
1433
|
+
required: ['issueId', 'claimantId', 'status'],
|
|
1434
|
+
},
|
|
1435
|
+
handler: async (input, context) => {
|
|
1436
|
+
const validated = issueStatusUpdateSchema.parse(input);
|
|
1437
|
+
return handleIssueStatusUpdate(validated, context);
|
|
1438
|
+
},
|
|
1439
|
+
category: 'claims',
|
|
1440
|
+
tags: ['claims', 'issue', 'status', 'progress'],
|
|
1441
|
+
version: '1.0.0',
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
export const issueListAvailableTool: MCPTool = {
|
|
1445
|
+
name: 'claims/issue_list_available',
|
|
1446
|
+
description: 'List all unclaimed issues available for work. Filter by priority, labels, or repository.',
|
|
1447
|
+
inputSchema: {
|
|
1448
|
+
type: 'object',
|
|
1449
|
+
properties: {
|
|
1450
|
+
priority: {
|
|
1451
|
+
type: 'string',
|
|
1452
|
+
enum: ['critical', 'high', 'medium', 'low'],
|
|
1453
|
+
description: 'Filter by priority',
|
|
1454
|
+
},
|
|
1455
|
+
labels: {
|
|
1456
|
+
type: 'array',
|
|
1457
|
+
items: { type: 'string' },
|
|
1458
|
+
description: 'Filter by labels',
|
|
1459
|
+
},
|
|
1460
|
+
repository: { type: 'string', description: 'Filter by repository' },
|
|
1461
|
+
limit: {
|
|
1462
|
+
type: 'number',
|
|
1463
|
+
description: 'Maximum results',
|
|
1464
|
+
minimum: 1,
|
|
1465
|
+
maximum: 100,
|
|
1466
|
+
default: 50,
|
|
1467
|
+
},
|
|
1468
|
+
offset: {
|
|
1469
|
+
type: 'number',
|
|
1470
|
+
description: 'Pagination offset',
|
|
1471
|
+
minimum: 0,
|
|
1472
|
+
default: 0,
|
|
1473
|
+
},
|
|
1474
|
+
},
|
|
1475
|
+
},
|
|
1476
|
+
handler: async (input, context) => {
|
|
1477
|
+
const validated = issueListAvailableSchema.parse(input);
|
|
1478
|
+
return handleIssueListAvailable(validated, context);
|
|
1479
|
+
},
|
|
1480
|
+
category: 'claims',
|
|
1481
|
+
tags: ['claims', 'issue', 'list', 'available'],
|
|
1482
|
+
version: '1.0.0',
|
|
1483
|
+
cacheable: true,
|
|
1484
|
+
cacheTTL: 5000,
|
|
1485
|
+
};
|
|
1486
|
+
|
|
1487
|
+
export const issueListMineTool: MCPTool = {
|
|
1488
|
+
name: 'claims/issue_list_mine',
|
|
1489
|
+
description: 'List all issues claimed by a specific claimant. Filter by status.',
|
|
1490
|
+
inputSchema: {
|
|
1491
|
+
type: 'object',
|
|
1492
|
+
properties: {
|
|
1493
|
+
claimantId: { type: 'string', description: 'Claimant ID' },
|
|
1494
|
+
status: {
|
|
1495
|
+
type: 'string',
|
|
1496
|
+
enum: ['active', 'blocked', 'in-review', 'completed', 'released', 'stolen'],
|
|
1497
|
+
description: 'Filter by status',
|
|
1498
|
+
},
|
|
1499
|
+
limit: {
|
|
1500
|
+
type: 'number',
|
|
1501
|
+
description: 'Maximum results',
|
|
1502
|
+
minimum: 1,
|
|
1503
|
+
maximum: 100,
|
|
1504
|
+
default: 50,
|
|
1505
|
+
},
|
|
1506
|
+
offset: {
|
|
1507
|
+
type: 'number',
|
|
1508
|
+
description: 'Pagination offset',
|
|
1509
|
+
minimum: 0,
|
|
1510
|
+
default: 0,
|
|
1511
|
+
},
|
|
1512
|
+
},
|
|
1513
|
+
required: ['claimantId'],
|
|
1514
|
+
},
|
|
1515
|
+
handler: async (input, context) => {
|
|
1516
|
+
const validated = issueListMineSchema.parse(input);
|
|
1517
|
+
return handleIssueListMine(validated, context);
|
|
1518
|
+
},
|
|
1519
|
+
category: 'claims',
|
|
1520
|
+
tags: ['claims', 'issue', 'list', 'my-claims'],
|
|
1521
|
+
version: '1.0.0',
|
|
1522
|
+
cacheable: true,
|
|
1523
|
+
cacheTTL: 2000,
|
|
1524
|
+
};
|
|
1525
|
+
|
|
1526
|
+
export const issueBoardTool: MCPTool = {
|
|
1527
|
+
name: 'claims/issue_board',
|
|
1528
|
+
description: 'View the claim board showing who is working on what. Group by claimant, priority, or status.',
|
|
1529
|
+
inputSchema: {
|
|
1530
|
+
type: 'object',
|
|
1531
|
+
properties: {
|
|
1532
|
+
includeAgents: {
|
|
1533
|
+
type: 'boolean',
|
|
1534
|
+
description: 'Include agent claims',
|
|
1535
|
+
default: true,
|
|
1536
|
+
},
|
|
1537
|
+
includeHumans: {
|
|
1538
|
+
type: 'boolean',
|
|
1539
|
+
description: 'Include human claims',
|
|
1540
|
+
default: true,
|
|
1541
|
+
},
|
|
1542
|
+
groupBy: {
|
|
1543
|
+
type: 'string',
|
|
1544
|
+
enum: ['claimant', 'priority', 'status'],
|
|
1545
|
+
description: 'Group claims by field',
|
|
1546
|
+
},
|
|
1547
|
+
},
|
|
1548
|
+
},
|
|
1549
|
+
handler: async (input, context) => {
|
|
1550
|
+
const validated = issueBoardSchema.parse(input);
|
|
1551
|
+
return handleIssueBoard(validated, context);
|
|
1552
|
+
},
|
|
1553
|
+
category: 'claims',
|
|
1554
|
+
tags: ['claims', 'board', 'overview', 'coordination'],
|
|
1555
|
+
version: '1.0.0',
|
|
1556
|
+
cacheable: true,
|
|
1557
|
+
cacheTTL: 5000,
|
|
1558
|
+
};
|
|
1559
|
+
|
|
1560
|
+
// Work Stealing Tools
|
|
1561
|
+
|
|
1562
|
+
export const issueMarkStealableTool: MCPTool = {
|
|
1563
|
+
name: 'claims/issue_mark_stealable',
|
|
1564
|
+
description: 'Mark a claimed issue as stealable, allowing other agents/humans to take over the work.',
|
|
1565
|
+
inputSchema: {
|
|
1566
|
+
type: 'object',
|
|
1567
|
+
properties: {
|
|
1568
|
+
issueId: { type: 'string', description: 'Issue ID to mark as stealable' },
|
|
1569
|
+
claimantId: { type: 'string', description: 'Current claimant ID' },
|
|
1570
|
+
reason: { type: 'string', description: 'Reason for making stealable' },
|
|
1571
|
+
},
|
|
1572
|
+
required: ['issueId', 'claimantId'],
|
|
1573
|
+
},
|
|
1574
|
+
handler: async (input, context) => {
|
|
1575
|
+
const validated = issueMarkStealableSchema.parse(input);
|
|
1576
|
+
return handleIssueMarkStealable(validated, context);
|
|
1577
|
+
},
|
|
1578
|
+
category: 'claims',
|
|
1579
|
+
tags: ['claims', 'stealing', 'mark'],
|
|
1580
|
+
version: '1.0.0',
|
|
1581
|
+
};
|
|
1582
|
+
|
|
1583
|
+
export const issueStealTool: MCPTool = {
|
|
1584
|
+
name: 'claims/issue_steal',
|
|
1585
|
+
description: 'Steal a stealable issue from another claimant. The previous claimant has a contest window to object.',
|
|
1586
|
+
inputSchema: {
|
|
1587
|
+
type: 'object',
|
|
1588
|
+
properties: {
|
|
1589
|
+
issueId: { type: 'string', description: 'Issue ID to steal' },
|
|
1590
|
+
stealerId: { type: 'string', description: 'ID of the stealer' },
|
|
1591
|
+
stealerType: { type: 'string', enum: ['human', 'agent'], description: 'Type of stealer' },
|
|
1592
|
+
reason: { type: 'string', description: 'Reason for stealing' },
|
|
1593
|
+
},
|
|
1594
|
+
required: ['issueId', 'stealerId', 'stealerType'],
|
|
1595
|
+
},
|
|
1596
|
+
handler: async (input, context) => {
|
|
1597
|
+
const validated = issueStealSchema.parse(input);
|
|
1598
|
+
return handleIssueSteal(validated, context);
|
|
1599
|
+
},
|
|
1600
|
+
category: 'claims',
|
|
1601
|
+
tags: ['claims', 'stealing', 'takeover'],
|
|
1602
|
+
version: '1.0.0',
|
|
1603
|
+
};
|
|
1604
|
+
|
|
1605
|
+
export const issueGetStealableTool: MCPTool = {
|
|
1606
|
+
name: 'claims/issue_get_stealable',
|
|
1607
|
+
description: 'List all issues marked as stealable. Filter by priority.',
|
|
1608
|
+
inputSchema: {
|
|
1609
|
+
type: 'object',
|
|
1610
|
+
properties: {
|
|
1611
|
+
priority: {
|
|
1612
|
+
type: 'string',
|
|
1613
|
+
enum: ['critical', 'high', 'medium', 'low'],
|
|
1614
|
+
description: 'Filter by priority',
|
|
1615
|
+
},
|
|
1616
|
+
limit: {
|
|
1617
|
+
type: 'number',
|
|
1618
|
+
description: 'Maximum results',
|
|
1619
|
+
minimum: 1,
|
|
1620
|
+
maximum: 100,
|
|
1621
|
+
default: 50,
|
|
1622
|
+
},
|
|
1623
|
+
},
|
|
1624
|
+
},
|
|
1625
|
+
handler: async (input, context) => {
|
|
1626
|
+
const validated = issueGetStealableSchema.parse(input);
|
|
1627
|
+
return handleIssueGetStealable(validated, context);
|
|
1628
|
+
},
|
|
1629
|
+
category: 'claims',
|
|
1630
|
+
tags: ['claims', 'stealing', 'list'],
|
|
1631
|
+
version: '1.0.0',
|
|
1632
|
+
cacheable: true,
|
|
1633
|
+
cacheTTL: 5000,
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
export const issueContestStealTool: MCPTool = {
|
|
1637
|
+
name: 'claims/issue_contest_steal',
|
|
1638
|
+
description: 'Contest a steal within the contest window. Provide a reason for the contest.',
|
|
1639
|
+
inputSchema: {
|
|
1640
|
+
type: 'object',
|
|
1641
|
+
properties: {
|
|
1642
|
+
issueId: { type: 'string', description: 'Issue ID being contested' },
|
|
1643
|
+
contesterId: { type: 'string', description: 'ID of the contester' },
|
|
1644
|
+
reason: { type: 'string', description: 'Reason for contesting' },
|
|
1645
|
+
},
|
|
1646
|
+
required: ['issueId', 'contesterId', 'reason'],
|
|
1647
|
+
},
|
|
1648
|
+
handler: async (input, context) => {
|
|
1649
|
+
const validated = issueContestStealSchema.parse(input);
|
|
1650
|
+
return handleIssueContestSteal(validated, context);
|
|
1651
|
+
},
|
|
1652
|
+
category: 'claims',
|
|
1653
|
+
tags: ['claims', 'stealing', 'contest'],
|
|
1654
|
+
version: '1.0.0',
|
|
1655
|
+
};
|
|
1656
|
+
|
|
1657
|
+
// Load Balancing Tools
|
|
1658
|
+
|
|
1659
|
+
export const agentLoadInfoTool: MCPTool = {
|
|
1660
|
+
name: 'claims/agent_load_info',
|
|
1661
|
+
description: 'Get current load information for a specific agent including claims, tasks, and utilization.',
|
|
1662
|
+
inputSchema: {
|
|
1663
|
+
type: 'object',
|
|
1664
|
+
properties: {
|
|
1665
|
+
agentId: { type: 'string', description: 'Agent ID to get load info for' },
|
|
1666
|
+
},
|
|
1667
|
+
required: ['agentId'],
|
|
1668
|
+
},
|
|
1669
|
+
handler: async (input, context) => {
|
|
1670
|
+
const validated = agentLoadInfoSchema.parse(input);
|
|
1671
|
+
return handleAgentLoadInfo(validated, context);
|
|
1672
|
+
},
|
|
1673
|
+
category: 'claims',
|
|
1674
|
+
tags: ['claims', 'load', 'agent', 'metrics'],
|
|
1675
|
+
version: '1.0.0',
|
|
1676
|
+
cacheable: true,
|
|
1677
|
+
cacheTTL: 2000,
|
|
1678
|
+
};
|
|
1679
|
+
|
|
1680
|
+
export const swarmRebalanceTool: MCPTool = {
|
|
1681
|
+
name: 'claims/swarm_rebalance',
|
|
1682
|
+
description: 'Trigger rebalancing of claims across the swarm to optimize load distribution.',
|
|
1683
|
+
inputSchema: {
|
|
1684
|
+
type: 'object',
|
|
1685
|
+
properties: {
|
|
1686
|
+
strategy: {
|
|
1687
|
+
type: 'string',
|
|
1688
|
+
enum: ['round-robin', 'least-loaded', 'priority-based', 'capability-based'],
|
|
1689
|
+
description: 'Rebalancing strategy',
|
|
1690
|
+
default: 'least-loaded',
|
|
1691
|
+
},
|
|
1692
|
+
dryRun: {
|
|
1693
|
+
type: 'boolean',
|
|
1694
|
+
description: 'Simulate without making changes',
|
|
1695
|
+
default: false,
|
|
1696
|
+
},
|
|
1697
|
+
},
|
|
1698
|
+
},
|
|
1699
|
+
handler: async (input, context) => {
|
|
1700
|
+
const validated = swarmRebalanceSchema.parse(input);
|
|
1701
|
+
return handleSwarmRebalance(validated, context);
|
|
1702
|
+
},
|
|
1703
|
+
category: 'claims',
|
|
1704
|
+
tags: ['claims', 'load', 'swarm', 'rebalance'],
|
|
1705
|
+
version: '1.0.0',
|
|
1706
|
+
};
|
|
1707
|
+
|
|
1708
|
+
export const swarmLoadOverviewTool: MCPTool = {
|
|
1709
|
+
name: 'claims/swarm_load_overview',
|
|
1710
|
+
description: 'Get swarm-wide load distribution including all agents, bottlenecks, and optimization recommendations.',
|
|
1711
|
+
inputSchema: {
|
|
1712
|
+
type: 'object',
|
|
1713
|
+
properties: {
|
|
1714
|
+
includeRecommendations: {
|
|
1715
|
+
type: 'boolean',
|
|
1716
|
+
description: 'Include optimization recommendations',
|
|
1717
|
+
default: true,
|
|
1718
|
+
},
|
|
1719
|
+
},
|
|
1720
|
+
},
|
|
1721
|
+
handler: async (input, context) => {
|
|
1722
|
+
const validated = swarmLoadOverviewSchema.parse(input);
|
|
1723
|
+
return handleSwarmLoadOverview(validated, context);
|
|
1724
|
+
},
|
|
1725
|
+
category: 'claims',
|
|
1726
|
+
tags: ['claims', 'load', 'swarm', 'overview', 'metrics'],
|
|
1727
|
+
version: '1.0.0',
|
|
1728
|
+
cacheable: true,
|
|
1729
|
+
cacheTTL: 5000,
|
|
1730
|
+
};
|
|
1731
|
+
|
|
1732
|
+
// Additional Tools
|
|
1733
|
+
|
|
1734
|
+
export const claimHistoryTool: MCPTool = {
|
|
1735
|
+
name: 'claims/claim_history',
|
|
1736
|
+
description: 'Get the claim history for a specific issue showing all past claims and actions.',
|
|
1737
|
+
inputSchema: {
|
|
1738
|
+
type: 'object',
|
|
1739
|
+
properties: {
|
|
1740
|
+
issueId: { type: 'string', description: 'Issue ID to get history for' },
|
|
1741
|
+
limit: {
|
|
1742
|
+
type: 'number',
|
|
1743
|
+
description: 'Maximum entries',
|
|
1744
|
+
minimum: 1,
|
|
1745
|
+
maximum: 100,
|
|
1746
|
+
default: 50,
|
|
1747
|
+
},
|
|
1748
|
+
},
|
|
1749
|
+
required: ['issueId'],
|
|
1750
|
+
},
|
|
1751
|
+
handler: async (input, context) => {
|
|
1752
|
+
const validated = claimHistorySchema.parse(input);
|
|
1753
|
+
return handleClaimHistory(validated, context);
|
|
1754
|
+
},
|
|
1755
|
+
category: 'claims',
|
|
1756
|
+
tags: ['claims', 'history', 'audit'],
|
|
1757
|
+
version: '1.0.0',
|
|
1758
|
+
cacheable: true,
|
|
1759
|
+
cacheTTL: 10000,
|
|
1760
|
+
};
|
|
1761
|
+
|
|
1762
|
+
export const claimMetricsTool: MCPTool = {
|
|
1763
|
+
name: 'claims/claim_metrics',
|
|
1764
|
+
description: 'Get claiming metrics including totals, averages, and distributions by priority and status.',
|
|
1765
|
+
inputSchema: {
|
|
1766
|
+
type: 'object',
|
|
1767
|
+
properties: {
|
|
1768
|
+
timeRange: {
|
|
1769
|
+
type: 'string',
|
|
1770
|
+
enum: ['1h', '24h', '7d', '30d', 'all'],
|
|
1771
|
+
description: 'Time range for metrics',
|
|
1772
|
+
default: '24h',
|
|
1773
|
+
},
|
|
1774
|
+
},
|
|
1775
|
+
},
|
|
1776
|
+
handler: async (input, context) => {
|
|
1777
|
+
const validated = claimMetricsSchema.parse(input);
|
|
1778
|
+
return handleClaimMetrics(validated, context);
|
|
1779
|
+
},
|
|
1780
|
+
category: 'claims',
|
|
1781
|
+
tags: ['claims', 'metrics', 'analytics'],
|
|
1782
|
+
version: '1.0.0',
|
|
1783
|
+
cacheable: true,
|
|
1784
|
+
cacheTTL: 30000,
|
|
1785
|
+
};
|
|
1786
|
+
|
|
1787
|
+
export const claimConfigTool: MCPTool = {
|
|
1788
|
+
name: 'claims/claim_config',
|
|
1789
|
+
description: 'Get or set claiming configuration including expiration times, limits, and contest windows.',
|
|
1790
|
+
inputSchema: {
|
|
1791
|
+
type: 'object',
|
|
1792
|
+
properties: {
|
|
1793
|
+
action: { type: 'string', enum: ['get', 'set'], description: 'Get or set configuration' },
|
|
1794
|
+
config: {
|
|
1795
|
+
type: 'object',
|
|
1796
|
+
description: 'Configuration values (for set action)',
|
|
1797
|
+
properties: {
|
|
1798
|
+
defaultExpirationMs: { type: 'number', minimum: 1 },
|
|
1799
|
+
maxClaimsPerAgent: { type: 'number', minimum: 1 },
|
|
1800
|
+
contestWindowMs: { type: 'number', minimum: 1 },
|
|
1801
|
+
autoReleaseOnInactivityMs: { type: 'number', minimum: 1 },
|
|
1802
|
+
},
|
|
1803
|
+
},
|
|
1804
|
+
},
|
|
1805
|
+
required: ['action'],
|
|
1806
|
+
},
|
|
1807
|
+
handler: async (input, context) => {
|
|
1808
|
+
const validated = claimConfigSchema.parse(input);
|
|
1809
|
+
return handleClaimConfig(validated, context);
|
|
1810
|
+
},
|
|
1811
|
+
category: 'claims',
|
|
1812
|
+
tags: ['claims', 'config', 'settings'],
|
|
1813
|
+
version: '1.0.0',
|
|
1814
|
+
};
|
|
1815
|
+
|
|
1816
|
+
// ============================================================================
|
|
1817
|
+
// Tool Collections
|
|
1818
|
+
// ============================================================================
|
|
1819
|
+
|
|
1820
|
+
/**
|
|
1821
|
+
* Core claiming tools (7 tools)
|
|
1822
|
+
*/
|
|
1823
|
+
export const coreClaimingTools: MCPTool[] = [
|
|
1824
|
+
issueClaimTool,
|
|
1825
|
+
issueReleaseTool,
|
|
1826
|
+
issueHandoffTool,
|
|
1827
|
+
issueStatusUpdateTool,
|
|
1828
|
+
issueListAvailableTool,
|
|
1829
|
+
issueListMineTool,
|
|
1830
|
+
issueBoardTool,
|
|
1831
|
+
];
|
|
1832
|
+
|
|
1833
|
+
/**
|
|
1834
|
+
* Work stealing tools (4 tools)
|
|
1835
|
+
*/
|
|
1836
|
+
export const workStealingTools: MCPTool[] = [
|
|
1837
|
+
issueMarkStealableTool,
|
|
1838
|
+
issueStealTool,
|
|
1839
|
+
issueGetStealableTool,
|
|
1840
|
+
issueContestStealTool,
|
|
1841
|
+
];
|
|
1842
|
+
|
|
1843
|
+
/**
|
|
1844
|
+
* Load balancing tools (3 tools)
|
|
1845
|
+
*/
|
|
1846
|
+
export const loadBalancingTools: MCPTool[] = [
|
|
1847
|
+
agentLoadInfoTool,
|
|
1848
|
+
swarmRebalanceTool,
|
|
1849
|
+
swarmLoadOverviewTool,
|
|
1850
|
+
];
|
|
1851
|
+
|
|
1852
|
+
/**
|
|
1853
|
+
* Additional tools (3 tools)
|
|
1854
|
+
*/
|
|
1855
|
+
export const additionalClaimsTools: MCPTool[] = [
|
|
1856
|
+
claimHistoryTool,
|
|
1857
|
+
claimMetricsTool,
|
|
1858
|
+
claimConfigTool,
|
|
1859
|
+
];
|
|
1860
|
+
|
|
1861
|
+
/**
|
|
1862
|
+
* All claims tools (17 tools total)
|
|
1863
|
+
*/
|
|
1864
|
+
export const claimsTools: MCPTool[] = [
|
|
1865
|
+
...coreClaimingTools,
|
|
1866
|
+
...workStealingTools,
|
|
1867
|
+
...loadBalancingTools,
|
|
1868
|
+
...additionalClaimsTools,
|
|
1869
|
+
];
|
|
1870
|
+
|
|
1871
|
+
// ============================================================================
|
|
1872
|
+
// Registration Function
|
|
1873
|
+
// ============================================================================
|
|
1874
|
+
|
|
1875
|
+
/**
|
|
1876
|
+
* Register all claims tools with an MCP server or tool registry
|
|
1877
|
+
*
|
|
1878
|
+
* @param registry - Tool registry or server to register with
|
|
1879
|
+
* @returns Number of tools registered
|
|
1880
|
+
*
|
|
1881
|
+
* @example
|
|
1882
|
+
* ```typescript
|
|
1883
|
+
* import { registerClaimsTools, claimsTools } from '@sparkleideas/claims';
|
|
1884
|
+
*
|
|
1885
|
+
* // Register all tools
|
|
1886
|
+
* const count = registerClaimsTools(server);
|
|
1887
|
+
* console.log(`Registered ${count} claims tools`);
|
|
1888
|
+
*
|
|
1889
|
+
* // Or use tools directly
|
|
1890
|
+
* server.registerTools(claimsTools);
|
|
1891
|
+
* ```
|
|
1892
|
+
*/
|
|
1893
|
+
export function registerClaimsTools(
|
|
1894
|
+
registry: { registerTool?: (tool: MCPTool) => void; register?: (tool: MCPTool) => void }
|
|
1895
|
+
): number {
|
|
1896
|
+
const registerFn = registry.registerTool || registry.register;
|
|
1897
|
+
|
|
1898
|
+
if (!registerFn) {
|
|
1899
|
+
throw new Error('Registry must have a registerTool or register method');
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
claimsTools.forEach(tool => registerFn.call(registry, tool));
|
|
1903
|
+
|
|
1904
|
+
return claimsTools.length;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
/**
|
|
1908
|
+
* Get claims tools by category
|
|
1909
|
+
*
|
|
1910
|
+
* @param category - Category name: 'core', 'stealing', 'load', or 'additional'
|
|
1911
|
+
* @returns Array of tools in that category
|
|
1912
|
+
*/
|
|
1913
|
+
export function getClaimsToolsByCategory(
|
|
1914
|
+
category: 'core' | 'stealing' | 'load' | 'additional'
|
|
1915
|
+
): MCPTool[] {
|
|
1916
|
+
switch (category) {
|
|
1917
|
+
case 'core':
|
|
1918
|
+
return coreClaimingTools;
|
|
1919
|
+
case 'stealing':
|
|
1920
|
+
return workStealingTools;
|
|
1921
|
+
case 'load':
|
|
1922
|
+
return loadBalancingTools;
|
|
1923
|
+
case 'additional':
|
|
1924
|
+
return additionalClaimsTools;
|
|
1925
|
+
default:
|
|
1926
|
+
return [];
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
/**
|
|
1931
|
+
* Get a specific claims tool by name
|
|
1932
|
+
*
|
|
1933
|
+
* @param name - Tool name (e.g., 'claims/issue_claim')
|
|
1934
|
+
* @returns The tool if found, undefined otherwise
|
|
1935
|
+
*/
|
|
1936
|
+
export function getClaimsToolByName(name: string): MCPTool | undefined {
|
|
1937
|
+
return claimsTools.find(tool => tool.name === name);
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
// ============================================================================
|
|
1941
|
+
// Default Export
|
|
1942
|
+
// ============================================================================
|
|
1943
|
+
|
|
1944
|
+
export default {
|
|
1945
|
+
// All tools
|
|
1946
|
+
claimsTools,
|
|
1947
|
+
|
|
1948
|
+
// Tool categories
|
|
1949
|
+
coreClaimingTools,
|
|
1950
|
+
workStealingTools,
|
|
1951
|
+
loadBalancingTools,
|
|
1952
|
+
additionalClaimsTools,
|
|
1953
|
+
|
|
1954
|
+
// Individual tools
|
|
1955
|
+
issueClaimTool,
|
|
1956
|
+
issueReleaseTool,
|
|
1957
|
+
issueHandoffTool,
|
|
1958
|
+
issueStatusUpdateTool,
|
|
1959
|
+
issueListAvailableTool,
|
|
1960
|
+
issueListMineTool,
|
|
1961
|
+
issueBoardTool,
|
|
1962
|
+
issueMarkStealableTool,
|
|
1963
|
+
issueStealTool,
|
|
1964
|
+
issueGetStealableTool,
|
|
1965
|
+
issueContestStealTool,
|
|
1966
|
+
agentLoadInfoTool,
|
|
1967
|
+
swarmRebalanceTool,
|
|
1968
|
+
swarmLoadOverviewTool,
|
|
1969
|
+
claimHistoryTool,
|
|
1970
|
+
claimMetricsTool,
|
|
1971
|
+
claimConfigTool,
|
|
1972
|
+
|
|
1973
|
+
// Utility functions
|
|
1974
|
+
registerClaimsTools,
|
|
1975
|
+
getClaimsToolsByCategory,
|
|
1976
|
+
getClaimsToolByName,
|
|
1977
|
+
};
|