@claude-flow/cli 3.0.0-alpha.79 → 3.0.0-alpha.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp-client.d.ts.map +1 -1
- package/dist/src/mcp-client.js +2 -0
- package/dist/src/mcp-client.js.map +1 -1
- package/dist/src/mcp-tools/claims-tools.d.ts +12 -0
- package/dist/src/mcp-tools/claims-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/claims-tools.js +732 -0
- package/dist/src/mcp-tools/claims-tools.js.map +1 -0
- package/dist/src/mcp-tools/index.d.ts +1 -0
- package/dist/src/mcp-tools/index.d.ts.map +1 -1
- package/dist/src/mcp-tools/index.js +1 -0
- package/dist/src/mcp-tools/index.js.map +1 -1
- package/dist/src/production/circuit-breaker.d.ts +101 -0
- package/dist/src/production/circuit-breaker.d.ts.map +1 -0
- package/dist/src/production/circuit-breaker.js +241 -0
- package/dist/src/production/circuit-breaker.js.map +1 -0
- package/dist/src/production/error-handler.d.ts +92 -0
- package/dist/src/production/error-handler.d.ts.map +1 -0
- package/dist/src/production/error-handler.js +299 -0
- package/dist/src/production/error-handler.js.map +1 -0
- package/dist/src/production/index.d.ts +23 -0
- package/dist/src/production/index.d.ts.map +1 -0
- package/dist/src/production/index.js +18 -0
- package/dist/src/production/index.js.map +1 -0
- package/dist/src/production/monitoring.d.ts +161 -0
- package/dist/src/production/monitoring.d.ts.map +1 -0
- package/dist/src/production/monitoring.js +356 -0
- package/dist/src/production/monitoring.js.map +1 -0
- package/dist/src/production/rate-limiter.d.ts +80 -0
- package/dist/src/production/rate-limiter.d.ts.map +1 -0
- package/dist/src/production/rate-limiter.js +201 -0
- package/dist/src/production/rate-limiter.js.map +1 -0
- package/dist/src/production/retry.d.ts +48 -0
- package/dist/src/production/retry.d.ts.map +1 -0
- package/dist/src/production/retry.js +179 -0
- package/dist/src/production/retry.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claims MCP Tools for CLI
|
|
3
|
+
*
|
|
4
|
+
* Implements MCP tools for ADR-016: Collaborative Issue Claims
|
|
5
|
+
* Provides programmatic access to claim operations for MCP clients.
|
|
6
|
+
*
|
|
7
|
+
* @module @claude-flow/cli/mcp-tools/claims
|
|
8
|
+
*/
|
|
9
|
+
// File-based persistence
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
11
|
+
import { join, resolve } from 'path';
|
|
12
|
+
const CLAIMS_DIR = '.claude-flow/claims';
|
|
13
|
+
const CLAIMS_FILE = 'claims.json';
|
|
14
|
+
function getClaimsPath() {
|
|
15
|
+
return resolve(join(CLAIMS_DIR, CLAIMS_FILE));
|
|
16
|
+
}
|
|
17
|
+
function ensureClaimsDir() {
|
|
18
|
+
const dir = resolve(CLAIMS_DIR);
|
|
19
|
+
if (!existsSync(dir)) {
|
|
20
|
+
mkdirSync(dir, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function loadClaims() {
|
|
24
|
+
try {
|
|
25
|
+
const path = getClaimsPath();
|
|
26
|
+
if (existsSync(path)) {
|
|
27
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Return empty store on error
|
|
32
|
+
}
|
|
33
|
+
return { claims: {}, stealable: {}, contests: {} };
|
|
34
|
+
}
|
|
35
|
+
function saveClaims(store) {
|
|
36
|
+
ensureClaimsDir();
|
|
37
|
+
writeFileSync(getClaimsPath(), JSON.stringify(store, null, 2), 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
function formatClaimant(claimant) {
|
|
40
|
+
return claimant.type === 'human'
|
|
41
|
+
? `human:${claimant.userId}:${claimant.name}`
|
|
42
|
+
: `agent:${claimant.agentId}:${claimant.agentType}`;
|
|
43
|
+
}
|
|
44
|
+
function parseClaimant(str) {
|
|
45
|
+
const parts = str.split(':');
|
|
46
|
+
if (parts[0] === 'human' && parts.length >= 3) {
|
|
47
|
+
return { type: 'human', userId: parts[1], name: parts.slice(2).join(':') };
|
|
48
|
+
}
|
|
49
|
+
else if (parts[0] === 'agent' && parts.length >= 3) {
|
|
50
|
+
return { type: 'agent', agentId: parts[1], agentType: parts[2] };
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
export const claimsTools = [
|
|
55
|
+
{
|
|
56
|
+
name: 'claims/claim',
|
|
57
|
+
description: 'Claim an issue for work (human or agent)',
|
|
58
|
+
category: 'claims',
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: {
|
|
62
|
+
issueId: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
description: 'Issue ID or GitHub issue number',
|
|
65
|
+
},
|
|
66
|
+
claimant: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
description: 'Claimant identifier (e.g., "human:user-1:Alice" or "agent:coder-1:coder")',
|
|
69
|
+
},
|
|
70
|
+
context: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
description: 'Optional context about the work approach',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
required: ['issueId', 'claimant'],
|
|
76
|
+
},
|
|
77
|
+
handler: async (input) => {
|
|
78
|
+
const issueId = input.issueId;
|
|
79
|
+
const claimantStr = input.claimant;
|
|
80
|
+
const context = input.context;
|
|
81
|
+
const claimant = parseClaimant(claimantStr);
|
|
82
|
+
if (!claimant) {
|
|
83
|
+
return { success: false, error: 'Invalid claimant format. Use "human:userId:name" or "agent:agentId:agentType"' };
|
|
84
|
+
}
|
|
85
|
+
const store = loadClaims();
|
|
86
|
+
// Check if already claimed
|
|
87
|
+
if (store.claims[issueId]) {
|
|
88
|
+
const existing = store.claims[issueId];
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
error: `Issue already claimed by ${formatClaimant(existing.claimant)}`,
|
|
92
|
+
existingClaim: existing,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const now = new Date().toISOString();
|
|
96
|
+
const claim = {
|
|
97
|
+
issueId,
|
|
98
|
+
claimant,
|
|
99
|
+
claimedAt: now,
|
|
100
|
+
status: 'active',
|
|
101
|
+
statusChangedAt: now,
|
|
102
|
+
progress: 0,
|
|
103
|
+
context,
|
|
104
|
+
};
|
|
105
|
+
store.claims[issueId] = claim;
|
|
106
|
+
saveClaims(store);
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
claim,
|
|
110
|
+
message: `Issue ${issueId} claimed by ${formatClaimant(claimant)}`,
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'claims/release',
|
|
116
|
+
description: 'Release a claim on an issue',
|
|
117
|
+
category: 'claims',
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
properties: {
|
|
121
|
+
issueId: {
|
|
122
|
+
type: 'string',
|
|
123
|
+
description: 'Issue ID to release',
|
|
124
|
+
},
|
|
125
|
+
claimant: {
|
|
126
|
+
type: 'string',
|
|
127
|
+
description: 'Claimant identifier (must match current owner)',
|
|
128
|
+
},
|
|
129
|
+
reason: {
|
|
130
|
+
type: 'string',
|
|
131
|
+
description: 'Reason for releasing',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
required: ['issueId', 'claimant'],
|
|
135
|
+
},
|
|
136
|
+
handler: async (input) => {
|
|
137
|
+
const issueId = input.issueId;
|
|
138
|
+
const claimantStr = input.claimant;
|
|
139
|
+
const reason = input.reason;
|
|
140
|
+
const claimant = parseClaimant(claimantStr);
|
|
141
|
+
if (!claimant) {
|
|
142
|
+
return { success: false, error: 'Invalid claimant format' };
|
|
143
|
+
}
|
|
144
|
+
const store = loadClaims();
|
|
145
|
+
const claim = store.claims[issueId];
|
|
146
|
+
if (!claim) {
|
|
147
|
+
return { success: false, error: 'Issue is not claimed' };
|
|
148
|
+
}
|
|
149
|
+
// Verify ownership
|
|
150
|
+
if (formatClaimant(claim.claimant) !== formatClaimant(claimant)) {
|
|
151
|
+
return { success: false, error: 'Only the current claimant can release' };
|
|
152
|
+
}
|
|
153
|
+
delete store.claims[issueId];
|
|
154
|
+
delete store.stealable[issueId];
|
|
155
|
+
saveClaims(store);
|
|
156
|
+
return {
|
|
157
|
+
success: true,
|
|
158
|
+
message: `Issue ${issueId} released`,
|
|
159
|
+
reason,
|
|
160
|
+
previousClaim: claim,
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'claims/handoff',
|
|
166
|
+
description: 'Request handoff of an issue to another claimant',
|
|
167
|
+
category: 'claims',
|
|
168
|
+
inputSchema: {
|
|
169
|
+
type: 'object',
|
|
170
|
+
properties: {
|
|
171
|
+
issueId: {
|
|
172
|
+
type: 'string',
|
|
173
|
+
description: 'Issue ID to handoff',
|
|
174
|
+
},
|
|
175
|
+
from: {
|
|
176
|
+
type: 'string',
|
|
177
|
+
description: 'Current claimant identifier',
|
|
178
|
+
},
|
|
179
|
+
to: {
|
|
180
|
+
type: 'string',
|
|
181
|
+
description: 'Target claimant identifier',
|
|
182
|
+
},
|
|
183
|
+
reason: {
|
|
184
|
+
type: 'string',
|
|
185
|
+
description: 'Reason for handoff',
|
|
186
|
+
},
|
|
187
|
+
progress: {
|
|
188
|
+
type: 'number',
|
|
189
|
+
description: 'Current progress percentage (0-100)',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
required: ['issueId', 'from', 'to'],
|
|
193
|
+
},
|
|
194
|
+
handler: async (input) => {
|
|
195
|
+
const issueId = input.issueId;
|
|
196
|
+
const fromStr = input.from;
|
|
197
|
+
const toStr = input.to;
|
|
198
|
+
const reason = input.reason;
|
|
199
|
+
const progress = input.progress || 0;
|
|
200
|
+
const from = parseClaimant(fromStr);
|
|
201
|
+
const to = parseClaimant(toStr);
|
|
202
|
+
if (!from || !to) {
|
|
203
|
+
return { success: false, error: 'Invalid claimant format' };
|
|
204
|
+
}
|
|
205
|
+
const store = loadClaims();
|
|
206
|
+
const claim = store.claims[issueId];
|
|
207
|
+
if (!claim) {
|
|
208
|
+
return { success: false, error: 'Issue is not claimed' };
|
|
209
|
+
}
|
|
210
|
+
if (formatClaimant(claim.claimant) !== formatClaimant(from)) {
|
|
211
|
+
return { success: false, error: 'Only the current claimant can request handoff' };
|
|
212
|
+
}
|
|
213
|
+
const now = new Date().toISOString();
|
|
214
|
+
claim.status = 'handoff-pending';
|
|
215
|
+
claim.statusChangedAt = now;
|
|
216
|
+
claim.handoffTo = to;
|
|
217
|
+
claim.handoffReason = reason;
|
|
218
|
+
claim.progress = progress;
|
|
219
|
+
store.claims[issueId] = claim;
|
|
220
|
+
saveClaims(store);
|
|
221
|
+
return {
|
|
222
|
+
success: true,
|
|
223
|
+
claim,
|
|
224
|
+
message: `Handoff requested from ${formatClaimant(from)} to ${formatClaimant(to)}`,
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: 'claims/accept-handoff',
|
|
230
|
+
description: 'Accept a pending handoff',
|
|
231
|
+
category: 'claims',
|
|
232
|
+
inputSchema: {
|
|
233
|
+
type: 'object',
|
|
234
|
+
properties: {
|
|
235
|
+
issueId: {
|
|
236
|
+
type: 'string',
|
|
237
|
+
description: 'Issue ID with pending handoff',
|
|
238
|
+
},
|
|
239
|
+
claimant: {
|
|
240
|
+
type: 'string',
|
|
241
|
+
description: 'Claimant accepting the handoff',
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
required: ['issueId', 'claimant'],
|
|
245
|
+
},
|
|
246
|
+
handler: async (input) => {
|
|
247
|
+
const issueId = input.issueId;
|
|
248
|
+
const claimantStr = input.claimant;
|
|
249
|
+
const claimant = parseClaimant(claimantStr);
|
|
250
|
+
if (!claimant) {
|
|
251
|
+
return { success: false, error: 'Invalid claimant format' };
|
|
252
|
+
}
|
|
253
|
+
const store = loadClaims();
|
|
254
|
+
const claim = store.claims[issueId];
|
|
255
|
+
if (!claim) {
|
|
256
|
+
return { success: false, error: 'Issue is not claimed' };
|
|
257
|
+
}
|
|
258
|
+
if (claim.status !== 'handoff-pending') {
|
|
259
|
+
return { success: false, error: 'No pending handoff for this issue' };
|
|
260
|
+
}
|
|
261
|
+
if (!claim.handoffTo || formatClaimant(claim.handoffTo) !== formatClaimant(claimant)) {
|
|
262
|
+
return { success: false, error: 'You are not the target of this handoff' };
|
|
263
|
+
}
|
|
264
|
+
const previousOwner = claim.claimant;
|
|
265
|
+
const now = new Date().toISOString();
|
|
266
|
+
claim.claimant = claimant;
|
|
267
|
+
claim.status = 'active';
|
|
268
|
+
claim.statusChangedAt = now;
|
|
269
|
+
claim.handoffTo = undefined;
|
|
270
|
+
claim.handoffReason = undefined;
|
|
271
|
+
store.claims[issueId] = claim;
|
|
272
|
+
saveClaims(store);
|
|
273
|
+
return {
|
|
274
|
+
success: true,
|
|
275
|
+
claim,
|
|
276
|
+
previousOwner,
|
|
277
|
+
message: `Handoff accepted. ${formatClaimant(claimant)} now owns issue ${issueId}`,
|
|
278
|
+
};
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: 'claims/status',
|
|
283
|
+
description: 'Update claim status',
|
|
284
|
+
category: 'claims',
|
|
285
|
+
inputSchema: {
|
|
286
|
+
type: 'object',
|
|
287
|
+
properties: {
|
|
288
|
+
issueId: {
|
|
289
|
+
type: 'string',
|
|
290
|
+
description: 'Issue ID',
|
|
291
|
+
},
|
|
292
|
+
status: {
|
|
293
|
+
type: 'string',
|
|
294
|
+
description: 'New status',
|
|
295
|
+
enum: ['active', 'paused', 'blocked', 'review-requested', 'completed'],
|
|
296
|
+
},
|
|
297
|
+
note: {
|
|
298
|
+
type: 'string',
|
|
299
|
+
description: 'Status note or reason',
|
|
300
|
+
},
|
|
301
|
+
progress: {
|
|
302
|
+
type: 'number',
|
|
303
|
+
description: 'Current progress percentage',
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
required: ['issueId', 'status'],
|
|
307
|
+
},
|
|
308
|
+
handler: async (input) => {
|
|
309
|
+
const issueId = input.issueId;
|
|
310
|
+
const status = input.status;
|
|
311
|
+
const note = input.note;
|
|
312
|
+
const progress = input.progress;
|
|
313
|
+
const store = loadClaims();
|
|
314
|
+
const claim = store.claims[issueId];
|
|
315
|
+
if (!claim) {
|
|
316
|
+
return { success: false, error: 'Issue is not claimed' };
|
|
317
|
+
}
|
|
318
|
+
const now = new Date().toISOString();
|
|
319
|
+
claim.status = status;
|
|
320
|
+
claim.statusChangedAt = now;
|
|
321
|
+
if (status === 'blocked') {
|
|
322
|
+
claim.blockReason = note;
|
|
323
|
+
}
|
|
324
|
+
if (progress !== undefined) {
|
|
325
|
+
claim.progress = Math.min(100, Math.max(0, progress));
|
|
326
|
+
}
|
|
327
|
+
store.claims[issueId] = claim;
|
|
328
|
+
saveClaims(store);
|
|
329
|
+
return {
|
|
330
|
+
success: true,
|
|
331
|
+
claim,
|
|
332
|
+
message: `Issue ${issueId} status updated to ${status}`,
|
|
333
|
+
};
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: 'claims/list',
|
|
338
|
+
description: 'List all claims or filter by criteria',
|
|
339
|
+
category: 'claims',
|
|
340
|
+
inputSchema: {
|
|
341
|
+
type: 'object',
|
|
342
|
+
properties: {
|
|
343
|
+
status: {
|
|
344
|
+
type: 'string',
|
|
345
|
+
description: 'Filter by status',
|
|
346
|
+
enum: ['active', 'paused', 'blocked', 'stealable', 'completed', 'all'],
|
|
347
|
+
},
|
|
348
|
+
claimant: {
|
|
349
|
+
type: 'string',
|
|
350
|
+
description: 'Filter by claimant',
|
|
351
|
+
},
|
|
352
|
+
agentType: {
|
|
353
|
+
type: 'string',
|
|
354
|
+
description: 'Filter by agent type',
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
handler: async (input) => {
|
|
359
|
+
const status = input.status;
|
|
360
|
+
const claimantFilter = input.claimant;
|
|
361
|
+
const agentType = input.agentType;
|
|
362
|
+
const store = loadClaims();
|
|
363
|
+
let claims = Object.values(store.claims);
|
|
364
|
+
if (status && status !== 'all') {
|
|
365
|
+
claims = claims.filter(c => c.status === status);
|
|
366
|
+
}
|
|
367
|
+
if (claimantFilter) {
|
|
368
|
+
claims = claims.filter(c => formatClaimant(c.claimant).includes(claimantFilter));
|
|
369
|
+
}
|
|
370
|
+
if (agentType) {
|
|
371
|
+
claims = claims.filter(c => c.claimant.type === 'agent' && c.claimant.agentType === agentType);
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
success: true,
|
|
375
|
+
claims,
|
|
376
|
+
count: claims.length,
|
|
377
|
+
stealableCount: Object.keys(store.stealable).length,
|
|
378
|
+
};
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
name: 'claims/mark-stealable',
|
|
383
|
+
description: 'Mark an issue as stealable by other agents',
|
|
384
|
+
category: 'claims',
|
|
385
|
+
inputSchema: {
|
|
386
|
+
type: 'object',
|
|
387
|
+
properties: {
|
|
388
|
+
issueId: {
|
|
389
|
+
type: 'string',
|
|
390
|
+
description: 'Issue ID to mark stealable',
|
|
391
|
+
},
|
|
392
|
+
reason: {
|
|
393
|
+
type: 'string',
|
|
394
|
+
description: 'Reason for marking stealable',
|
|
395
|
+
enum: ['overloaded', 'stale', 'blocked-timeout', 'voluntary'],
|
|
396
|
+
},
|
|
397
|
+
preferredTypes: {
|
|
398
|
+
type: 'array',
|
|
399
|
+
description: 'Preferred agent types to steal',
|
|
400
|
+
items: { type: 'string' },
|
|
401
|
+
},
|
|
402
|
+
context: {
|
|
403
|
+
type: 'string',
|
|
404
|
+
description: 'Handoff context for the stealer',
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
required: ['issueId', 'reason'],
|
|
408
|
+
},
|
|
409
|
+
handler: async (input) => {
|
|
410
|
+
const issueId = input.issueId;
|
|
411
|
+
const reason = input.reason;
|
|
412
|
+
const preferredTypes = input.preferredTypes;
|
|
413
|
+
const context = input.context;
|
|
414
|
+
const store = loadClaims();
|
|
415
|
+
const claim = store.claims[issueId];
|
|
416
|
+
if (!claim) {
|
|
417
|
+
return { success: false, error: 'Issue is not claimed' };
|
|
418
|
+
}
|
|
419
|
+
const now = new Date().toISOString();
|
|
420
|
+
claim.status = 'stealable';
|
|
421
|
+
claim.statusChangedAt = now;
|
|
422
|
+
store.stealable[issueId] = {
|
|
423
|
+
reason,
|
|
424
|
+
stealableAt: now,
|
|
425
|
+
preferredTypes,
|
|
426
|
+
progress: claim.progress,
|
|
427
|
+
context,
|
|
428
|
+
};
|
|
429
|
+
store.claims[issueId] = claim;
|
|
430
|
+
saveClaims(store);
|
|
431
|
+
return {
|
|
432
|
+
success: true,
|
|
433
|
+
claim,
|
|
434
|
+
stealableInfo: store.stealable[issueId],
|
|
435
|
+
message: `Issue ${issueId} marked as stealable (${reason})`,
|
|
436
|
+
};
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: 'claims/steal',
|
|
441
|
+
description: 'Steal a stealable issue',
|
|
442
|
+
category: 'claims',
|
|
443
|
+
inputSchema: {
|
|
444
|
+
type: 'object',
|
|
445
|
+
properties: {
|
|
446
|
+
issueId: {
|
|
447
|
+
type: 'string',
|
|
448
|
+
description: 'Issue ID to steal',
|
|
449
|
+
},
|
|
450
|
+
stealer: {
|
|
451
|
+
type: 'string',
|
|
452
|
+
description: 'Claimant stealing the issue',
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
required: ['issueId', 'stealer'],
|
|
456
|
+
},
|
|
457
|
+
handler: async (input) => {
|
|
458
|
+
const issueId = input.issueId;
|
|
459
|
+
const stealerStr = input.stealer;
|
|
460
|
+
const stealer = parseClaimant(stealerStr);
|
|
461
|
+
if (!stealer) {
|
|
462
|
+
return { success: false, error: 'Invalid claimant format' };
|
|
463
|
+
}
|
|
464
|
+
const store = loadClaims();
|
|
465
|
+
const claim = store.claims[issueId];
|
|
466
|
+
const stealableInfo = store.stealable[issueId];
|
|
467
|
+
if (!claim) {
|
|
468
|
+
return { success: false, error: 'Issue is not claimed' };
|
|
469
|
+
}
|
|
470
|
+
if (!stealableInfo) {
|
|
471
|
+
return { success: false, error: 'Issue is not stealable' };
|
|
472
|
+
}
|
|
473
|
+
// Check preferred types
|
|
474
|
+
if (stealableInfo.preferredTypes && stealableInfo.preferredTypes.length > 0) {
|
|
475
|
+
if (stealer.type === 'agent' && !stealableInfo.preferredTypes.includes(stealer.agentType)) {
|
|
476
|
+
return {
|
|
477
|
+
success: false,
|
|
478
|
+
error: `Issue prefers agent types: ${stealableInfo.preferredTypes.join(', ')}`,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const previousOwner = claim.claimant;
|
|
483
|
+
const now = new Date().toISOString();
|
|
484
|
+
claim.claimant = stealer;
|
|
485
|
+
claim.status = 'active';
|
|
486
|
+
claim.statusChangedAt = now;
|
|
487
|
+
claim.context = stealableInfo.context;
|
|
488
|
+
delete store.stealable[issueId];
|
|
489
|
+
store.claims[issueId] = claim;
|
|
490
|
+
saveClaims(store);
|
|
491
|
+
return {
|
|
492
|
+
success: true,
|
|
493
|
+
claim,
|
|
494
|
+
previousOwner,
|
|
495
|
+
stealableInfo,
|
|
496
|
+
message: `Issue ${issueId} stolen by ${formatClaimant(stealer)}`,
|
|
497
|
+
};
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: 'claims/stealable',
|
|
502
|
+
description: 'List all stealable issues',
|
|
503
|
+
category: 'claims',
|
|
504
|
+
inputSchema: {
|
|
505
|
+
type: 'object',
|
|
506
|
+
properties: {
|
|
507
|
+
agentType: {
|
|
508
|
+
type: 'string',
|
|
509
|
+
description: 'Filter by preferred agent type',
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
handler: async (input) => {
|
|
514
|
+
const agentType = input.agentType;
|
|
515
|
+
const store = loadClaims();
|
|
516
|
+
let stealableIssues = Object.entries(store.stealable).map(([issueId, info]) => ({
|
|
517
|
+
issueId,
|
|
518
|
+
...info,
|
|
519
|
+
claim: store.claims[issueId],
|
|
520
|
+
}));
|
|
521
|
+
if (agentType) {
|
|
522
|
+
stealableIssues = stealableIssues.filter(s => !s.preferredTypes || s.preferredTypes.length === 0 || s.preferredTypes.includes(agentType));
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
success: true,
|
|
526
|
+
stealable: stealableIssues,
|
|
527
|
+
count: stealableIssues.length,
|
|
528
|
+
};
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
name: 'claims/load',
|
|
533
|
+
description: 'Get agent load information',
|
|
534
|
+
category: 'claims',
|
|
535
|
+
inputSchema: {
|
|
536
|
+
type: 'object',
|
|
537
|
+
properties: {
|
|
538
|
+
agentId: {
|
|
539
|
+
type: 'string',
|
|
540
|
+
description: 'Specific agent ID (optional)',
|
|
541
|
+
},
|
|
542
|
+
agentType: {
|
|
543
|
+
type: 'string',
|
|
544
|
+
description: 'Filter by agent type',
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
handler: async (input) => {
|
|
549
|
+
const agentId = input.agentId;
|
|
550
|
+
const agentType = input.agentType;
|
|
551
|
+
const store = loadClaims();
|
|
552
|
+
const claims = Object.values(store.claims);
|
|
553
|
+
// Group claims by agent
|
|
554
|
+
const agentLoads = new Map();
|
|
555
|
+
for (const claim of claims) {
|
|
556
|
+
if (claim.claimant.type !== 'agent')
|
|
557
|
+
continue;
|
|
558
|
+
const key = claim.claimant.agentId;
|
|
559
|
+
if (!agentLoads.has(key)) {
|
|
560
|
+
agentLoads.set(key, {
|
|
561
|
+
agentId: key,
|
|
562
|
+
agentType: claim.claimant.agentType,
|
|
563
|
+
claims: [],
|
|
564
|
+
blockedCount: 0,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
const load = agentLoads.get(key);
|
|
568
|
+
load.claims.push(claim);
|
|
569
|
+
if (claim.status === 'blocked') {
|
|
570
|
+
load.blockedCount++;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
let loads = Array.from(agentLoads.values());
|
|
574
|
+
if (agentId) {
|
|
575
|
+
loads = loads.filter(l => l.agentId === agentId);
|
|
576
|
+
}
|
|
577
|
+
if (agentType) {
|
|
578
|
+
loads = loads.filter(l => l.agentType === agentType);
|
|
579
|
+
}
|
|
580
|
+
const result = loads.map(l => ({
|
|
581
|
+
agentId: l.agentId,
|
|
582
|
+
agentType: l.agentType,
|
|
583
|
+
claimCount: l.claims.length,
|
|
584
|
+
maxClaims: 5, // Default max
|
|
585
|
+
utilization: l.claims.length / 5,
|
|
586
|
+
blockedCount: l.blockedCount,
|
|
587
|
+
claims: l.claims.map(c => ({
|
|
588
|
+
issueId: c.issueId,
|
|
589
|
+
status: c.status,
|
|
590
|
+
progress: c.progress,
|
|
591
|
+
})),
|
|
592
|
+
}));
|
|
593
|
+
return {
|
|
594
|
+
success: true,
|
|
595
|
+
loads: result,
|
|
596
|
+
totalAgents: result.length,
|
|
597
|
+
totalClaims: claims.filter(c => c.claimant.type === 'agent').length,
|
|
598
|
+
avgUtilization: result.length > 0
|
|
599
|
+
? result.reduce((sum, l) => sum + l.utilization, 0) / result.length
|
|
600
|
+
: 0,
|
|
601
|
+
};
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
name: 'claims/board',
|
|
606
|
+
description: 'Get a visual board view of all claims',
|
|
607
|
+
category: 'claims',
|
|
608
|
+
inputSchema: {
|
|
609
|
+
type: 'object',
|
|
610
|
+
properties: {},
|
|
611
|
+
},
|
|
612
|
+
handler: async () => {
|
|
613
|
+
const store = loadClaims();
|
|
614
|
+
const claims = Object.values(store.claims);
|
|
615
|
+
const byStatus = {
|
|
616
|
+
active: [],
|
|
617
|
+
paused: [],
|
|
618
|
+
blocked: [],
|
|
619
|
+
'handoff-pending': [],
|
|
620
|
+
'review-requested': [],
|
|
621
|
+
stealable: [],
|
|
622
|
+
completed: [],
|
|
623
|
+
};
|
|
624
|
+
for (const claim of claims) {
|
|
625
|
+
if (byStatus[claim.status]) {
|
|
626
|
+
byStatus[claim.status].push(claim);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
const humanClaims = claims.filter(c => c.claimant.type === 'human');
|
|
630
|
+
const agentClaims = claims.filter(c => c.claimant.type === 'agent');
|
|
631
|
+
return {
|
|
632
|
+
success: true,
|
|
633
|
+
board: {
|
|
634
|
+
active: byStatus.active.map(c => ({ issueId: c.issueId, claimant: formatClaimant(c.claimant), progress: c.progress })),
|
|
635
|
+
paused: byStatus.paused.map(c => ({ issueId: c.issueId, claimant: formatClaimant(c.claimant) })),
|
|
636
|
+
blocked: byStatus.blocked.map(c => ({ issueId: c.issueId, claimant: formatClaimant(c.claimant), reason: c.blockReason })),
|
|
637
|
+
'handoff-pending': byStatus['handoff-pending'].map(c => ({ issueId: c.issueId, from: formatClaimant(c.claimant), to: c.handoffTo ? formatClaimant(c.handoffTo) : null })),
|
|
638
|
+
'review-requested': byStatus['review-requested'].map(c => ({ issueId: c.issueId, claimant: formatClaimant(c.claimant) })),
|
|
639
|
+
stealable: byStatus.stealable.map(c => ({ issueId: c.issueId, claimant: formatClaimant(c.claimant) })),
|
|
640
|
+
completed: byStatus.completed.map(c => ({ issueId: c.issueId, claimant: formatClaimant(c.claimant) })),
|
|
641
|
+
},
|
|
642
|
+
summary: {
|
|
643
|
+
total: claims.length,
|
|
644
|
+
active: byStatus.active.length,
|
|
645
|
+
blocked: byStatus.blocked.length,
|
|
646
|
+
stealable: byStatus.stealable.length,
|
|
647
|
+
humanClaims: humanClaims.length,
|
|
648
|
+
agentClaims: agentClaims.length,
|
|
649
|
+
},
|
|
650
|
+
};
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
name: 'claims/rebalance',
|
|
655
|
+
description: 'Suggest or apply load rebalancing across agents',
|
|
656
|
+
category: 'claims',
|
|
657
|
+
inputSchema: {
|
|
658
|
+
type: 'object',
|
|
659
|
+
properties: {
|
|
660
|
+
dryRun: {
|
|
661
|
+
type: 'boolean',
|
|
662
|
+
description: 'Preview rebalancing without applying',
|
|
663
|
+
default: true,
|
|
664
|
+
},
|
|
665
|
+
targetUtilization: {
|
|
666
|
+
type: 'number',
|
|
667
|
+
description: 'Target utilization (0-1)',
|
|
668
|
+
default: 0.7,
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
handler: async (input) => {
|
|
673
|
+
const dryRun = input.dryRun !== false;
|
|
674
|
+
const targetUtilization = input.targetUtilization || 0.7;
|
|
675
|
+
const store = loadClaims();
|
|
676
|
+
const claims = Object.values(store.claims);
|
|
677
|
+
// Group by agent
|
|
678
|
+
const agentLoads = new Map();
|
|
679
|
+
for (const claim of claims) {
|
|
680
|
+
if (claim.claimant.type !== 'agent')
|
|
681
|
+
continue;
|
|
682
|
+
const key = claim.claimant.agentId;
|
|
683
|
+
if (!agentLoads.has(key)) {
|
|
684
|
+
agentLoads.set(key, { agentId: key, agentType: claim.claimant.agentType, claims: [] });
|
|
685
|
+
}
|
|
686
|
+
agentLoads.get(key).claims.push(claim);
|
|
687
|
+
}
|
|
688
|
+
const loads = Array.from(agentLoads.values());
|
|
689
|
+
const maxClaims = 5;
|
|
690
|
+
const avgLoad = loads.length > 0
|
|
691
|
+
? loads.reduce((sum, l) => sum + l.claims.length, 0) / loads.length
|
|
692
|
+
: 0;
|
|
693
|
+
const overloaded = loads.filter(l => l.claims.length > maxClaims * targetUtilization * 1.5);
|
|
694
|
+
const underloaded = loads.filter(l => l.claims.length < maxClaims * targetUtilization * 0.5);
|
|
695
|
+
const suggestions = [];
|
|
696
|
+
for (const over of overloaded) {
|
|
697
|
+
// Find low-progress claims to redistribute
|
|
698
|
+
const movable = over.claims
|
|
699
|
+
.filter(c => c.progress < 25 && c.status === 'active')
|
|
700
|
+
.slice(0, over.claims.length - Math.ceil(maxClaims * targetUtilization));
|
|
701
|
+
for (const claim of movable) {
|
|
702
|
+
const target = underloaded.find(u => u.agentType === over.agentType && u.claims.length < maxClaims);
|
|
703
|
+
if (target) {
|
|
704
|
+
suggestions.push({
|
|
705
|
+
issueId: claim.issueId,
|
|
706
|
+
from: `agent:${over.agentId}:${over.agentType}`,
|
|
707
|
+
to: `agent:${target.agentId}:${target.agentType}`,
|
|
708
|
+
reason: 'Load balancing',
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return {
|
|
714
|
+
success: true,
|
|
715
|
+
dryRun,
|
|
716
|
+
suggestions,
|
|
717
|
+
metrics: {
|
|
718
|
+
totalAgents: loads.length,
|
|
719
|
+
avgLoad,
|
|
720
|
+
overloadedCount: overloaded.length,
|
|
721
|
+
underloadedCount: underloaded.length,
|
|
722
|
+
targetUtilization,
|
|
723
|
+
},
|
|
724
|
+
message: dryRun
|
|
725
|
+
? `Found ${suggestions.length} rebalancing opportunities (dry run)`
|
|
726
|
+
: `Applied ${suggestions.length} rebalancing moves`,
|
|
727
|
+
};
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
];
|
|
731
|
+
export default claimsTools;
|
|
732
|
+
//# sourceMappingURL=claims-tools.js.map
|