@vibescope/mcp-server 0.2.9 → 0.3.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/CHANGELOG.md +84 -84
- package/README.md +194 -194
- package/dist/api-client.d.ts +36 -0
- package/dist/api-client.js +34 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +30 -38
- package/dist/handlers/discovery.js +2 -0
- package/dist/handlers/session.d.ts +11 -0
- package/dist/handlers/session.js +101 -0
- package/dist/handlers/tasks.d.ts +8 -0
- package/dist/handlers/tasks.js +163 -3
- package/dist/handlers/tool-docs.js +840 -828
- package/dist/handlers/validation.js +49 -2
- package/dist/index.js +73 -73
- package/dist/setup.js +6 -6
- package/dist/templates/agent-guidelines.js +185 -185
- package/dist/templates/help-content.js +1622 -1544
- package/dist/tools.js +130 -74
- package/dist/utils.d.ts +15 -11
- package/dist/utils.js +53 -28
- package/docs/TOOLS.md +2407 -2053
- package/package.json +51 -51
- package/scripts/generate-docs.ts +212 -212
- package/scripts/version-bump.ts +203 -203
- package/src/api-client.test.ts +723 -723
- package/src/api-client.ts +2561 -2499
- package/src/cli.test.ts +24 -8
- package/src/cli.ts +204 -212
- package/src/handlers/__test-setup__.ts +236 -236
- package/src/handlers/__test-utils__.ts +87 -87
- package/src/handlers/blockers.test.ts +468 -468
- package/src/handlers/blockers.ts +163 -163
- package/src/handlers/bodies-of-work.test.ts +704 -704
- package/src/handlers/bodies-of-work.ts +526 -526
- package/src/handlers/connectors.test.ts +834 -834
- package/src/handlers/connectors.ts +229 -229
- package/src/handlers/cost.test.ts +462 -462
- package/src/handlers/cost.ts +285 -285
- package/src/handlers/decisions.test.ts +382 -382
- package/src/handlers/decisions.ts +153 -153
- package/src/handlers/deployment.test.ts +551 -551
- package/src/handlers/deployment.ts +541 -541
- package/src/handlers/discovery.test.ts +206 -206
- package/src/handlers/discovery.ts +392 -390
- package/src/handlers/fallback.test.ts +537 -537
- package/src/handlers/fallback.ts +194 -194
- package/src/handlers/file-checkouts.test.ts +750 -750
- package/src/handlers/file-checkouts.ts +185 -185
- package/src/handlers/findings.test.ts +633 -633
- package/src/handlers/findings.ts +239 -239
- package/src/handlers/git-issues.test.ts +631 -631
- package/src/handlers/git-issues.ts +136 -136
- package/src/handlers/ideas.test.ts +644 -644
- package/src/handlers/ideas.ts +207 -207
- package/src/handlers/index.ts +84 -84
- package/src/handlers/milestones.test.ts +475 -475
- package/src/handlers/milestones.ts +180 -180
- package/src/handlers/organizations.test.ts +826 -826
- package/src/handlers/organizations.ts +315 -315
- package/src/handlers/progress.test.ts +269 -269
- package/src/handlers/progress.ts +77 -77
- package/src/handlers/project.test.ts +546 -546
- package/src/handlers/project.ts +239 -239
- package/src/handlers/requests.test.ts +303 -303
- package/src/handlers/requests.ts +99 -99
- package/src/handlers/roles.test.ts +305 -305
- package/src/handlers/roles.ts +219 -219
- package/src/handlers/session.test.ts +998 -875
- package/src/handlers/session.ts +839 -730
- package/src/handlers/sprints.test.ts +732 -732
- package/src/handlers/sprints.ts +537 -537
- package/src/handlers/tasks.test.ts +931 -907
- package/src/handlers/tasks.ts +1121 -945
- package/src/handlers/tool-categories.test.ts +66 -66
- package/src/handlers/tool-docs.ts +1109 -1096
- package/src/handlers/types.test.ts +259 -259
- package/src/handlers/types.ts +175 -175
- package/src/handlers/validation.test.ts +582 -582
- package/src/handlers/validation.ts +164 -113
- package/src/index.test.ts +674 -0
- package/src/index.ts +792 -792
- package/src/setup.test.ts +233 -233
- package/src/setup.ts +404 -403
- package/src/templates/agent-guidelines.ts +210 -210
- package/src/templates/help-content.ts +1751 -1673
- package/src/token-tracking.test.ts +463 -463
- package/src/token-tracking.ts +166 -166
- package/src/tools.test.ts +416 -0
- package/src/tools.ts +3611 -3555
- package/src/utils.test.ts +785 -683
- package/src/utils.ts +469 -436
- package/src/validators.test.ts +223 -223
- package/src/validators.ts +249 -249
- package/tsconfig.json +16 -16
- package/vitest.config.ts +14 -14
package/src/handlers/session.ts
CHANGED
|
@@ -1,730 +1,839 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session Handlers
|
|
3
|
-
*
|
|
4
|
-
* Handles agent session lifecycle:
|
|
5
|
-
* - start_work_session
|
|
6
|
-
* - heartbeat
|
|
7
|
-
* - end_work_session
|
|
8
|
-
* - get_help
|
|
9
|
-
* - get_token_usage
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import os from 'os';
|
|
13
|
-
import type { Handler, HandlerRegistry, TokenUsage } from './types.js';
|
|
14
|
-
import { parseArgs, createEnumValidator } from '../validators.js';
|
|
15
|
-
import { getApiClient } from '../api-client.js';
|
|
16
|
-
import { getAgentGuidelinesTemplate, getAgentGuidelinesSummary } from '../templates/agent-guidelines.js';
|
|
17
|
-
import { getFallbackHelpContent, getAvailableHelpTopics } from '../templates/help-content.js';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
type
|
|
27
|
-
type
|
|
28
|
-
type
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
result.
|
|
176
|
-
result.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
//
|
|
185
|
-
if (
|
|
186
|
-
result.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (data.
|
|
200
|
-
result.
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Add
|
|
204
|
-
if (data.
|
|
205
|
-
result.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
result.
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
result.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
};
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
.
|
|
494
|
-
.
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
//
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
};
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Session Handlers
|
|
3
|
+
*
|
|
4
|
+
* Handles agent session lifecycle:
|
|
5
|
+
* - start_work_session
|
|
6
|
+
* - heartbeat
|
|
7
|
+
* - end_work_session
|
|
8
|
+
* - get_help
|
|
9
|
+
* - get_token_usage
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import os from 'os';
|
|
13
|
+
import type { Handler, HandlerRegistry, TokenUsage } from './types.js';
|
|
14
|
+
import { parseArgs, createEnumValidator } from '../validators.js';
|
|
15
|
+
import { getApiClient } from '../api-client.js';
|
|
16
|
+
import { getAgentGuidelinesTemplate, getAgentGuidelinesSummary } from '../templates/agent-guidelines.js';
|
|
17
|
+
import { getFallbackHelpContent, getAvailableHelpTopics } from '../templates/help-content.js';
|
|
18
|
+
import { normalizeGitUrl } from '../utils.js';
|
|
19
|
+
|
|
20
|
+
// Auto-detect machine hostname for worktree tracking
|
|
21
|
+
const MACHINE_HOSTNAME = os.hostname();
|
|
22
|
+
|
|
23
|
+
const VALID_MODES = ['lite', 'full'] as const;
|
|
24
|
+
// Model, role, and agent_type are now open-ended - any string is accepted
|
|
25
|
+
|
|
26
|
+
type SessionMode = typeof VALID_MODES[number];
|
|
27
|
+
type SessionModel = string; // Open-ended - any model name accepted
|
|
28
|
+
type SessionRole = string; // Open-ended - any role name accepted
|
|
29
|
+
type AgentType = string; // Open-ended - any agent type accepted
|
|
30
|
+
|
|
31
|
+
// Argument schemas for type-safe parsing
|
|
32
|
+
const startWorkSessionSchema = {
|
|
33
|
+
project_id: { type: 'string' as const },
|
|
34
|
+
git_url: { type: 'string' as const },
|
|
35
|
+
mode: { type: 'string' as const, default: 'lite', validate: createEnumValidator(VALID_MODES) },
|
|
36
|
+
model: { type: 'string' as const }, // Open-ended - any model name accepted
|
|
37
|
+
role: { type: 'string' as const, default: 'developer' }, // Open-ended - any role name accepted
|
|
38
|
+
hostname: { type: 'string' as const }, // Machine hostname for worktree tracking
|
|
39
|
+
agent_type: { type: 'string' as const }, // Open-ended - any agent type accepted
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const heartbeatSchema = {
|
|
43
|
+
session_id: { type: 'string' as const },
|
|
44
|
+
current_worktree_path: { type: 'string' as const },
|
|
45
|
+
hostname: { type: 'string' as const }, // Machine hostname for worktree tracking
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const endWorkSessionSchema = {
|
|
49
|
+
session_id: { type: 'string' as const },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const getHelpSchema = {
|
|
53
|
+
topic: { type: 'string' as const, required: true as const },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const startWorkSession: Handler = async (args, ctx) => {
|
|
57
|
+
const { project_id, git_url, mode, model, role, hostname: providedHostname, agent_type } = parseArgs(args, startWorkSessionSchema);
|
|
58
|
+
|
|
59
|
+
// Use auto-detected hostname if not provided - enables machine-aware worktree filtering
|
|
60
|
+
const hostname = providedHostname || MACHINE_HOSTNAME;
|
|
61
|
+
|
|
62
|
+
// Normalize git_url and track if it was changed - helps agents understand URL matching
|
|
63
|
+
const normalizedGitUrl = git_url ? normalizeGitUrl(git_url) : null;
|
|
64
|
+
const gitUrlWasNormalized = git_url && normalizedGitUrl && git_url !== normalizedGitUrl;
|
|
65
|
+
|
|
66
|
+
const { session, updateSession } = ctx;
|
|
67
|
+
|
|
68
|
+
// Reset token tracking for new session with model info
|
|
69
|
+
// Model is now open-ended - use as-is (normalize Claude model names for consistency)
|
|
70
|
+
const normalizedModel = model ? model.toLowerCase().replace(/^claude[- ]*/i, '') : null;
|
|
71
|
+
|
|
72
|
+
updateSession({
|
|
73
|
+
tokenUsage: {
|
|
74
|
+
callCount: 0,
|
|
75
|
+
totalTokens: 0,
|
|
76
|
+
byTool: {},
|
|
77
|
+
byModel: {},
|
|
78
|
+
currentModel: normalizedModel,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Require project_id or git_url
|
|
83
|
+
if (!project_id && !git_url) {
|
|
84
|
+
return {
|
|
85
|
+
result: {
|
|
86
|
+
error: 'Please provide project_id or git_url to start a session',
|
|
87
|
+
session_termination_required: true,
|
|
88
|
+
reason: 'Cannot start work without identifying a project',
|
|
89
|
+
action: 'END_SESSION_NOW - Do not proceed with any work until MCP is properly configured.',
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const apiClient = getApiClient();
|
|
95
|
+
const response = await apiClient.startSession({
|
|
96
|
+
project_id,
|
|
97
|
+
git_url,
|
|
98
|
+
mode: mode as SessionMode,
|
|
99
|
+
model: model as SessionModel | undefined,
|
|
100
|
+
role: role as SessionRole,
|
|
101
|
+
hostname, // Machine hostname for worktree tracking
|
|
102
|
+
agent_type: agent_type as AgentType | undefined, // Agent type for onboarding
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
// Include additional error details if available
|
|
107
|
+
const errorData = response.data as { detail?: string; code?: string } | undefined;
|
|
108
|
+
return {
|
|
109
|
+
result: {
|
|
110
|
+
error: response.error || 'Failed to start session',
|
|
111
|
+
...(errorData?.detail && { detail: errorData.detail }),
|
|
112
|
+
...(errorData?.code && { code: errorData.code }),
|
|
113
|
+
session_termination_required: true,
|
|
114
|
+
reason: 'MCP server connection failed - cannot track work',
|
|
115
|
+
action: 'END_SESSION_NOW - Do not proceed with any work.',
|
|
116
|
+
troubleshooting: [
|
|
117
|
+
'1. Check if MCP server is configured: claude mcp list',
|
|
118
|
+
'2. Verify VIBESCOPE_API_KEY is set correctly',
|
|
119
|
+
'3. Check network connectivity to vibescope.dev',
|
|
120
|
+
'4. Restart Claude Code after fixing configuration',
|
|
121
|
+
],
|
|
122
|
+
user_message: 'MCP connection to Vibescope failed. I cannot proceed without task tracking. Please fix the configuration and restart.',
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const data = response.data;
|
|
128
|
+
|
|
129
|
+
// Handle project not found - include agent guidelines for new project setup
|
|
130
|
+
if (!data?.session_started) {
|
|
131
|
+
// If project_not_found, include agent guidelines template for CLAUDE.md setup
|
|
132
|
+
if (data?.project_not_found) {
|
|
133
|
+
return {
|
|
134
|
+
result: {
|
|
135
|
+
...data,
|
|
136
|
+
agent_guidelines: {
|
|
137
|
+
message: 'IMPORTANT: After creating the project, add these guidelines to your .claude/CLAUDE.md file.',
|
|
138
|
+
summary: getAgentGuidelinesSummary(),
|
|
139
|
+
full_template: getAgentGuidelinesTemplate(),
|
|
140
|
+
setup_instructions: [
|
|
141
|
+
'1. Create the project using create_project()',
|
|
142
|
+
'2. Create .claude/CLAUDE.md in your project root',
|
|
143
|
+
'3. Copy the full_template content into CLAUDE.md',
|
|
144
|
+
'4. Call start_work_session again to begin work',
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return { result: data };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Store session ID and persona in local state
|
|
154
|
+
if (data.session_id) {
|
|
155
|
+
updateSession({
|
|
156
|
+
currentSessionId: data.session_id,
|
|
157
|
+
currentPersona: data.persona || null,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check for urgent questions - these MUST be handled first
|
|
162
|
+
const hasUrgentQuestions = data.URGENT_QUESTIONS || (data.pending_requests && data.pending_requests.length > 0);
|
|
163
|
+
|
|
164
|
+
// Build result - URGENT_QUESTIONS at absolute top for maximum visibility
|
|
165
|
+
const result: Record<string, unknown> = {
|
|
166
|
+
session_started: true,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// URGENT_QUESTIONS must be the FIRST thing the agent sees
|
|
170
|
+
if (data.URGENT_QUESTIONS) {
|
|
171
|
+
result.URGENT_QUESTIONS = data.URGENT_QUESTIONS;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Directive comes right after urgent questions
|
|
175
|
+
result.directive = data.directive || 'ACTION_REQUIRED: Start working immediately.';
|
|
176
|
+
result.auto_continue = true;
|
|
177
|
+
|
|
178
|
+
// Session info
|
|
179
|
+
result.session_id = data.session_id;
|
|
180
|
+
result.persona = data.persona;
|
|
181
|
+
result.role = data.role;
|
|
182
|
+
result.project = data.project;
|
|
183
|
+
|
|
184
|
+
// Inform agent if git_url was normalized (helps explain URL matching behavior)
|
|
185
|
+
if (gitUrlWasNormalized) {
|
|
186
|
+
result.git_url_normalized = {
|
|
187
|
+
message: 'Your git URL was normalized for project lookup. All URL formats for the same repository resolve to the same project.',
|
|
188
|
+
original: git_url,
|
|
189
|
+
normalized: normalizedGitUrl,
|
|
190
|
+
examples: [
|
|
191
|
+
'git@github.com:owner/repo.git → https://github.com/owner/repo',
|
|
192
|
+
'https://GITHUB.COM/Owner/Repo/ → https://github.com/owner/repo',
|
|
193
|
+
'http://github.com/owner/repo.git → https://github.com/owner/repo',
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Add task data
|
|
199
|
+
if (data.next_task) {
|
|
200
|
+
result.next_task = data.next_task;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Add pending requests (questions from user) - these take priority
|
|
204
|
+
if (data.pending_requests && data.pending_requests.length > 0) {
|
|
205
|
+
result.pending_requests = data.pending_requests;
|
|
206
|
+
result.pending_requests_count = data.pending_requests.length;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Add active tasks for full mode
|
|
210
|
+
if (data.active_tasks) {
|
|
211
|
+
result.active_tasks = data.active_tasks;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Add blockers
|
|
215
|
+
if (data.blockers) {
|
|
216
|
+
result.open_blockers = data.blockers;
|
|
217
|
+
}
|
|
218
|
+
if (data.blockers_count !== undefined && data.blockers_count > 0) {
|
|
219
|
+
result.blockers_count = data.blockers_count;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Add validation tasks when present - agents should validate before starting new work
|
|
223
|
+
if (data.validation_count !== undefined && data.validation_count > 0) {
|
|
224
|
+
result.validation_count = data.validation_count;
|
|
225
|
+
}
|
|
226
|
+
if (data.awaiting_validation && data.awaiting_validation.length > 0) {
|
|
227
|
+
result.awaiting_validation = data.awaiting_validation;
|
|
228
|
+
result.validation_priority = data.validation_priority;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Add stale worktrees warning if any exist
|
|
232
|
+
if (data.stale_worktrees && data.stale_worktrees.length > 0) {
|
|
233
|
+
result.stale_worktrees = data.stale_worktrees;
|
|
234
|
+
result.stale_worktrees_count = data.stale_worktrees_count;
|
|
235
|
+
result.cleanup_action = data.cleanup_action;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Add git workflow info if available in project
|
|
239
|
+
if (data.project?.git_workflow && data.project.git_workflow !== 'none') {
|
|
240
|
+
// Branching workflows (git-flow, github-flow) require worktrees
|
|
241
|
+
// Trunk-based development commits directly to main, no worktree needed
|
|
242
|
+
const isBranchingWorkflow = data.project.git_workflow === 'git-flow' || data.project.git_workflow === 'github-flow';
|
|
243
|
+
const baseBranch = data.project.git_workflow === 'git-flow'
|
|
244
|
+
? (data.project.git_develop_branch || 'develop')
|
|
245
|
+
: (data.project.git_main_branch || 'main');
|
|
246
|
+
|
|
247
|
+
result.git_workflow = {
|
|
248
|
+
workflow: data.project.git_workflow,
|
|
249
|
+
auto_branch: data.project.git_auto_branch ?? false,
|
|
250
|
+
main_branch: data.project.git_main_branch || 'main',
|
|
251
|
+
...(data.project.git_workflow === 'git-flow' && data.project.git_develop_branch
|
|
252
|
+
? { develop_branch: data.project.git_develop_branch }
|
|
253
|
+
: {}),
|
|
254
|
+
worktree_required: isBranchingWorkflow,
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Only show worktree reminder for branching workflows (git-flow, github-flow)
|
|
258
|
+
if (isBranchingWorkflow) {
|
|
259
|
+
result.WORKTREE_REMINDER = {
|
|
260
|
+
message: 'CRITICAL: Create worktree BEFORE making ANY file edits',
|
|
261
|
+
wrong_order: 'DO NOT: Edit files → stash → create worktree → pop stash',
|
|
262
|
+
right_order: 'DO: Create worktree → cd into it → THEN edit files',
|
|
263
|
+
command: `git worktree add ../<project>-<persona>-<task> -b feature/<task-id> ${baseBranch}`,
|
|
264
|
+
help: 'Run get_help("git") for full instructions',
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Add FIRST_TIME_CONNECTION guidance for git-flow
|
|
268
|
+
if (data.project.git_workflow === 'git-flow') {
|
|
269
|
+
result.FIRST_TIME_CONNECTION = {
|
|
270
|
+
workflow: 'git-flow',
|
|
271
|
+
steps: [
|
|
272
|
+
`1. git checkout ${data.project.git_develop_branch || 'develop'}`,
|
|
273
|
+
`2. git pull origin ${data.project.git_develop_branch || 'develop'}`,
|
|
274
|
+
'3. All feature branches must be created from develop',
|
|
275
|
+
],
|
|
276
|
+
warning: 'Working from main or stale branches causes merge conflicts.',
|
|
277
|
+
base_branch: data.project.git_develop_branch || 'develop',
|
|
278
|
+
};
|
|
279
|
+
} else if (data.project.git_workflow === 'github-flow') {
|
|
280
|
+
result.FIRST_TIME_CONNECTION = {
|
|
281
|
+
workflow: 'github-flow',
|
|
282
|
+
steps: [
|
|
283
|
+
`1. git checkout ${data.project.git_main_branch || 'main'}`,
|
|
284
|
+
`2. git pull origin ${data.project.git_main_branch || 'main'}`,
|
|
285
|
+
'3. All feature branches must be created from main',
|
|
286
|
+
],
|
|
287
|
+
warning: 'Working from stale branches causes merge conflicts.',
|
|
288
|
+
base_branch: data.project.git_main_branch || 'main',
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Add agent setup instructions if this is a new agent type for the project
|
|
295
|
+
if (data.agent_setup) {
|
|
296
|
+
result.agent_setup = data.agent_setup;
|
|
297
|
+
// If setup is required, update directive to prioritize setup
|
|
298
|
+
if (data.agent_setup.setup_required) {
|
|
299
|
+
result.directive = `SETUP REQUIRED: This is your first time connecting as a ${data.agent_setup.agent_type} agent. Follow the agent_setup instructions before starting work.`;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Add next action at end - pending requests take priority over validation, then regular tasks
|
|
304
|
+
if (hasUrgentQuestions) {
|
|
305
|
+
const firstQuestion = data.URGENT_QUESTIONS?.requests?.[0] || data.pending_requests?.[0];
|
|
306
|
+
result.next_action = firstQuestion
|
|
307
|
+
? `answer_question(request_id: "${firstQuestion.id}", answer: "...")`
|
|
308
|
+
: 'Check pending_requests and respond using answer_question(request_id, answer)';
|
|
309
|
+
} else if (data.awaiting_validation && data.awaiting_validation.length > 0) {
|
|
310
|
+
// Validation tasks take priority over new work - use next_action from API if available
|
|
311
|
+
result.next_action = data.next_action || `claim_validation(task_id: "${data.awaiting_validation[0].id}")`;
|
|
312
|
+
} else if (data.next_task) {
|
|
313
|
+
result.next_action = `update_task(task_id: "${data.next_task.id}", status: "in_progress")`;
|
|
314
|
+
} else if (data.project) {
|
|
315
|
+
result.next_action = `start_fallback_activity(project_id: "${data.project.id}", activity: "code_review")`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return { result };
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
export const heartbeat: Handler = async (args, ctx) => {
|
|
322
|
+
const { session_id, current_worktree_path, hostname: providedHostname } = parseArgs(args, heartbeatSchema);
|
|
323
|
+
const { session } = ctx;
|
|
324
|
+
const targetSession = session_id || session.currentSessionId;
|
|
325
|
+
|
|
326
|
+
// Use auto-detected hostname if not provided
|
|
327
|
+
const hostname = providedHostname || MACHINE_HOSTNAME;
|
|
328
|
+
|
|
329
|
+
if (!targetSession) {
|
|
330
|
+
return {
|
|
331
|
+
result: {
|
|
332
|
+
error: 'No active session. Call start_work_session first.',
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const apiClient = getApiClient();
|
|
338
|
+
|
|
339
|
+
// Send heartbeat with optional worktree path and hostname
|
|
340
|
+
const heartbeatResponse = await apiClient.heartbeat(targetSession, {
|
|
341
|
+
current_worktree_path,
|
|
342
|
+
hostname,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
if (!heartbeatResponse.ok) {
|
|
346
|
+
return {
|
|
347
|
+
result: {
|
|
348
|
+
error: heartbeatResponse.error || 'Failed to send heartbeat',
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Sync token usage to session
|
|
354
|
+
await apiClient.syncSession(targetSession, {
|
|
355
|
+
total_tokens: session.tokenUsage.totalTokens,
|
|
356
|
+
token_breakdown: session.tokenUsage.byTool,
|
|
357
|
+
model_usage: session.tokenUsage.byModel,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
result: {
|
|
362
|
+
success: true,
|
|
363
|
+
session_id: targetSession,
|
|
364
|
+
timestamp: heartbeatResponse.data?.timestamp || new Date().toISOString(),
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
export const endWorkSession: Handler = async (args, ctx) => {
|
|
370
|
+
const { session_id } = parseArgs(args, endWorkSessionSchema);
|
|
371
|
+
const { session, updateSession } = ctx;
|
|
372
|
+
const targetSession = session_id || session.currentSessionId;
|
|
373
|
+
|
|
374
|
+
if (!targetSession) {
|
|
375
|
+
return {
|
|
376
|
+
result: {
|
|
377
|
+
success: true,
|
|
378
|
+
message: 'No active session to end',
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const apiClient = getApiClient();
|
|
384
|
+
|
|
385
|
+
// Sync final token usage before ending
|
|
386
|
+
await apiClient.syncSession(targetSession, {
|
|
387
|
+
total_tokens: session.tokenUsage.totalTokens,
|
|
388
|
+
token_breakdown: session.tokenUsage.byTool,
|
|
389
|
+
model_usage: session.tokenUsage.byModel,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// End the session
|
|
393
|
+
const response = await apiClient.endSession(targetSession);
|
|
394
|
+
|
|
395
|
+
if (!response.ok) {
|
|
396
|
+
return {
|
|
397
|
+
result: {
|
|
398
|
+
error: response.error || 'Failed to end session',
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const endedSessionId = targetSession;
|
|
404
|
+
|
|
405
|
+
// Clear local session state if this was the current session
|
|
406
|
+
if (session.currentSessionId === targetSession) {
|
|
407
|
+
updateSession({ currentSessionId: null });
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const data = response.data;
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
result: {
|
|
414
|
+
success: true,
|
|
415
|
+
ended_session_id: endedSessionId,
|
|
416
|
+
session_summary: {
|
|
417
|
+
agent_name: data?.session_summary?.agent_name || 'Agent',
|
|
418
|
+
tasks_completed_this_session: data?.session_summary?.tasks_completed_this_session || 0,
|
|
419
|
+
tasks_awaiting_validation: data?.session_summary?.tasks_awaiting_validation || 0,
|
|
420
|
+
tasks_released: data?.session_summary?.tasks_released || 0,
|
|
421
|
+
token_usage: {
|
|
422
|
+
total_calls: session.tokenUsage.callCount,
|
|
423
|
+
total_tokens: session.tokenUsage.totalTokens,
|
|
424
|
+
avg_per_call: session.tokenUsage.callCount > 0
|
|
425
|
+
? Math.round(session.tokenUsage.totalTokens / session.tokenUsage.callCount)
|
|
426
|
+
: 0,
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
reminders: data?.reminders || ['Session ended cleanly. Good work!'],
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
export const getHelp: Handler = async (args, _ctx) => {
|
|
435
|
+
const { topic } = parseArgs(args, getHelpSchema);
|
|
436
|
+
|
|
437
|
+
const apiClient = getApiClient();
|
|
438
|
+
const response = await apiClient.getHelpTopic(topic);
|
|
439
|
+
|
|
440
|
+
// Try database content first
|
|
441
|
+
if (response.ok && response.data?.content) {
|
|
442
|
+
return { result: { topic, content: response.data.content } };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Fall back to local content if database is empty or unavailable
|
|
446
|
+
const fallback = getFallbackHelpContent(topic);
|
|
447
|
+
if (fallback) {
|
|
448
|
+
return { result: { topic, content: fallback.content } };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Topic not found in either source - show available topics
|
|
452
|
+
const available = getAvailableHelpTopics();
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
result: {
|
|
456
|
+
error: `Unknown topic: ${topic}`,
|
|
457
|
+
available,
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
// Model pricing rates (USD per 1M tokens) by pricing tier
|
|
463
|
+
// 'standard' = regular API rates (included in Max plans)
|
|
464
|
+
// 'extra_usage' = overage rates when exceeding plan limits (currently same as standard)
|
|
465
|
+
export type PricingTier = 'standard' | 'extra_usage';
|
|
466
|
+
|
|
467
|
+
interface ModelPricing {
|
|
468
|
+
input: number;
|
|
469
|
+
output: number;
|
|
470
|
+
description?: string;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const MODEL_PRICING: Record<PricingTier, Record<string, ModelPricing>> = {
|
|
474
|
+
standard: {
|
|
475
|
+
// Claude models
|
|
476
|
+
opus: { input: 15.0, output: 75.0, description: 'Claude Opus 4.5' },
|
|
477
|
+
sonnet: { input: 3.0, output: 15.0, description: 'Claude Sonnet 4' },
|
|
478
|
+
haiku: { input: 0.25, output: 1.25, description: 'Claude Haiku 3.5' },
|
|
479
|
+
// Gemini models (as of Jan 2025)
|
|
480
|
+
gemini: { input: 0.10, output: 0.40, description: 'Gemini 2.0 Flash' },
|
|
481
|
+
'gemini-2.0-flash': { input: 0.10, output: 0.40, description: 'Gemini 2.0 Flash' },
|
|
482
|
+
'gemini-1.5-pro': { input: 1.25, output: 5.00, description: 'Gemini 1.5 Pro' },
|
|
483
|
+
'gemini-1.5-flash': { input: 0.075, output: 0.30, description: 'Gemini 1.5 Flash' },
|
|
484
|
+
},
|
|
485
|
+
extra_usage: {
|
|
486
|
+
// Claude models - extra usage/overage rates (same as standard for now)
|
|
487
|
+
opus: { input: 15.0, output: 75.0, description: 'Claude Opus 4.5 - Extra usage' },
|
|
488
|
+
sonnet: { input: 3.0, output: 15.0, description: 'Claude Sonnet 4 - Extra usage' },
|
|
489
|
+
haiku: { input: 0.25, output: 1.25, description: 'Claude Haiku 3.5 - Extra usage' },
|
|
490
|
+
// Gemini models - extra usage rates (same as standard for now)
|
|
491
|
+
gemini: { input: 0.10, output: 0.40, description: 'Gemini 2.0 Flash - Extra usage' },
|
|
492
|
+
'gemini-2.0-flash': { input: 0.10, output: 0.40, description: 'Gemini 2.0 Flash - Extra usage' },
|
|
493
|
+
'gemini-1.5-pro': { input: 1.25, output: 5.00, description: 'Gemini 1.5 Pro - Extra usage' },
|
|
494
|
+
'gemini-1.5-flash': { input: 0.075, output: 0.30, description: 'Gemini 1.5 Flash - Extra usage' },
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
// Legacy accessor for backward compatibility
|
|
499
|
+
const getModelPricing = (tier: PricingTier = 'standard') => MODEL_PRICING[tier];
|
|
500
|
+
|
|
501
|
+
function calculateCost(
|
|
502
|
+
byModel: Record<string, { input: number; output: number }>,
|
|
503
|
+
tier: PricingTier = 'standard'
|
|
504
|
+
): {
|
|
505
|
+
breakdown: Record<string, { input_cost: number; output_cost: number; total: number; description?: string }>;
|
|
506
|
+
total: number;
|
|
507
|
+
pricing_tier: PricingTier;
|
|
508
|
+
} {
|
|
509
|
+
const breakdown: Record<string, { input_cost: number; output_cost: number; total: number; description?: string }> = {};
|
|
510
|
+
let total = 0;
|
|
511
|
+
const pricingTable = getModelPricing(tier);
|
|
512
|
+
|
|
513
|
+
for (const [model, tokens] of Object.entries(byModel)) {
|
|
514
|
+
const pricing = pricingTable[model];
|
|
515
|
+
if (pricing) {
|
|
516
|
+
const inputCost = (tokens.input / 1_000_000) * pricing.input;
|
|
517
|
+
const outputCost = (tokens.output / 1_000_000) * pricing.output;
|
|
518
|
+
const modelTotal = inputCost + outputCost;
|
|
519
|
+
breakdown[model] = {
|
|
520
|
+
input_cost: Math.round(inputCost * 10000) / 10000,
|
|
521
|
+
output_cost: Math.round(outputCost * 10000) / 10000,
|
|
522
|
+
total: Math.round(modelTotal * 10000) / 10000,
|
|
523
|
+
description: pricing.description,
|
|
524
|
+
};
|
|
525
|
+
total += modelTotal;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return { breakdown, total: Math.round(total * 10000) / 10000, pricing_tier: tier };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
export const getTokenUsage: Handler = async (_args, ctx) => {
|
|
533
|
+
const { session } = ctx;
|
|
534
|
+
const sessionTokenUsage = session.tokenUsage;
|
|
535
|
+
|
|
536
|
+
const topTools = Object.entries(sessionTokenUsage.byTool)
|
|
537
|
+
.sort(([, a], [, b]) => b.tokens - a.tokens)
|
|
538
|
+
.slice(0, 5)
|
|
539
|
+
.map(([tool, stats]) => ({
|
|
540
|
+
tool,
|
|
541
|
+
calls: stats.calls,
|
|
542
|
+
tokens: stats.tokens,
|
|
543
|
+
avg: Math.round(stats.tokens / stats.calls),
|
|
544
|
+
}));
|
|
545
|
+
|
|
546
|
+
// Calculate model breakdown and costs for both pricing tiers
|
|
547
|
+
const modelBreakdown = Object.entries(sessionTokenUsage.byModel || {}).map(([model, tokens]) => ({
|
|
548
|
+
model,
|
|
549
|
+
input_tokens: tokens.input,
|
|
550
|
+
output_tokens: tokens.output,
|
|
551
|
+
total_tokens: tokens.input + tokens.output,
|
|
552
|
+
}));
|
|
553
|
+
|
|
554
|
+
const standardCost = calculateCost(sessionTokenUsage.byModel || {}, 'standard');
|
|
555
|
+
const extraUsageCost = calculateCost(sessionTokenUsage.byModel || {}, 'extra_usage');
|
|
556
|
+
|
|
557
|
+
// If no model tracking, estimate cost assuming sonnet (middle tier)
|
|
558
|
+
const hasModelData = Object.keys(sessionTokenUsage.byModel || {}).length > 0;
|
|
559
|
+
const estimatedCostNoModel = !hasModelData
|
|
560
|
+
? Math.round((sessionTokenUsage.totalTokens / 1_000_000) * getModelPricing('standard').sonnet.output * 10000) / 10000
|
|
561
|
+
: null;
|
|
562
|
+
|
|
563
|
+
// Add context clearing directive when usage is high
|
|
564
|
+
const shouldClearContext = sessionTokenUsage.callCount > 50 || sessionTokenUsage.totalTokens > 100000;
|
|
565
|
+
|
|
566
|
+
return {
|
|
567
|
+
result: {
|
|
568
|
+
session: {
|
|
569
|
+
calls: sessionTokenUsage.callCount,
|
|
570
|
+
tokens: sessionTokenUsage.totalTokens,
|
|
571
|
+
avg_per_call: sessionTokenUsage.callCount > 0
|
|
572
|
+
? Math.round(sessionTokenUsage.totalTokens / sessionTokenUsage.callCount)
|
|
573
|
+
: 0,
|
|
574
|
+
current_model: sessionTokenUsage.currentModel,
|
|
575
|
+
},
|
|
576
|
+
top_tools: topTools,
|
|
577
|
+
model_breakdown: modelBreakdown.length > 0 ? modelBreakdown : undefined,
|
|
578
|
+
cost: {
|
|
579
|
+
// Standard tier (optimistic - included in Max plan)
|
|
580
|
+
standard: {
|
|
581
|
+
by_model: Object.keys(standardCost.breakdown).length > 0 ? standardCost.breakdown : undefined,
|
|
582
|
+
total_usd: standardCost.total > 0 ? standardCost.total : estimatedCostNoModel,
|
|
583
|
+
pricing_tier: 'standard',
|
|
584
|
+
},
|
|
585
|
+
// Extra usage tier (pessimistic - overage rates)
|
|
586
|
+
extra_usage: {
|
|
587
|
+
by_model: Object.keys(extraUsageCost.breakdown).length > 0 ? extraUsageCost.breakdown : undefined,
|
|
588
|
+
total_usd: extraUsageCost.total > 0 ? extraUsageCost.total : estimatedCostNoModel,
|
|
589
|
+
pricing_tier: 'extra_usage',
|
|
590
|
+
},
|
|
591
|
+
// Legacy field for backward compatibility
|
|
592
|
+
total_usd: standardCost.total > 0 ? standardCost.total : estimatedCostNoModel,
|
|
593
|
+
estimated: estimatedCostNoModel !== null,
|
|
594
|
+
},
|
|
595
|
+
supported_models: {
|
|
596
|
+
claude: ['opus', 'sonnet', 'haiku'],
|
|
597
|
+
gemini: ['gemini', 'gemini-2.0-flash', 'gemini-1.5-pro', 'gemini-1.5-flash'],
|
|
598
|
+
},
|
|
599
|
+
note: 'IMPORTANT: These estimates only track MCP tool I/O (~1-5% of actual API usage). Actual costs may be 20-100x higher due to conversation context, system prompts, and reasoning tokens. Use report_token_usage() with actual API response values for accurate tracking.',
|
|
600
|
+
estimation_coverage: 'mcp_tool_io_only',
|
|
601
|
+
...(shouldClearContext && {
|
|
602
|
+
directive: 'High token usage detected. Run /clear then start_work_session to reset context. Do not ask permission.',
|
|
603
|
+
}),
|
|
604
|
+
},
|
|
605
|
+
};
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
const reportTokenUsageSchema = {
|
|
609
|
+
input_tokens: { type: 'number' as const, required: true as const },
|
|
610
|
+
output_tokens: { type: 'number' as const, required: true as const },
|
|
611
|
+
model: { type: 'string' as const }, // Open-ended - any model name accepted
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
const confirmAgentSetupSchema = {
|
|
615
|
+
project_id: { type: 'string' as const, required: true as const },
|
|
616
|
+
agent_type: { type: 'string' as const, required: true as const }, // Open-ended - any agent type accepted
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Report actual Claude API token usage for accurate cost tracking.
|
|
621
|
+
* This allows agents to report their actual API usage instead of relying on MCP estimates.
|
|
622
|
+
* The backend will attribute costs to the current task if one is active.
|
|
623
|
+
*/
|
|
624
|
+
export const reportTokenUsage: Handler = async (args, ctx) => {
|
|
625
|
+
const { input_tokens, output_tokens, model } = parseArgs(args, reportTokenUsageSchema);
|
|
626
|
+
const { session, updateSession } = ctx;
|
|
627
|
+
|
|
628
|
+
// Validate token counts
|
|
629
|
+
if (input_tokens! < 0 || output_tokens! < 0) {
|
|
630
|
+
return {
|
|
631
|
+
result: {
|
|
632
|
+
error: 'Token counts must be non-negative',
|
|
633
|
+
},
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Determine which model to attribute to
|
|
638
|
+
const targetModel = model || session.tokenUsage.currentModel || 'sonnet';
|
|
639
|
+
|
|
640
|
+
// Update the session's local token usage
|
|
641
|
+
const updatedByModel = { ...session.tokenUsage.byModel };
|
|
642
|
+
if (!updatedByModel[targetModel]) {
|
|
643
|
+
updatedByModel[targetModel] = { input: 0, output: 0 };
|
|
644
|
+
}
|
|
645
|
+
updatedByModel[targetModel].input += input_tokens!;
|
|
646
|
+
updatedByModel[targetModel].output += output_tokens!;
|
|
647
|
+
|
|
648
|
+
const totalTokens = input_tokens! + output_tokens!;
|
|
649
|
+
|
|
650
|
+
updateSession({
|
|
651
|
+
tokenUsage: {
|
|
652
|
+
...session.tokenUsage,
|
|
653
|
+
callCount: session.tokenUsage.callCount + 1,
|
|
654
|
+
totalTokens: session.tokenUsage.totalTokens + totalTokens,
|
|
655
|
+
byModel: updatedByModel,
|
|
656
|
+
},
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// Report to backend - this handles both session update and task cost attribution
|
|
660
|
+
const apiClient = getApiClient();
|
|
661
|
+
const currentSessionId = session.currentSessionId;
|
|
662
|
+
|
|
663
|
+
if (!currentSessionId) {
|
|
664
|
+
// Calculate cost locally if no session (use standard tier)
|
|
665
|
+
const pricing = getModelPricing('standard')[targetModel];
|
|
666
|
+
const inputCost = pricing ? (input_tokens! / 1_000_000) * pricing.input : 0;
|
|
667
|
+
const outputCost = pricing ? (output_tokens! / 1_000_000) * pricing.output : 0;
|
|
668
|
+
|
|
669
|
+
return {
|
|
670
|
+
result: {
|
|
671
|
+
success: true,
|
|
672
|
+
reported: {
|
|
673
|
+
model: targetModel,
|
|
674
|
+
input_tokens: input_tokens!,
|
|
675
|
+
output_tokens: output_tokens!,
|
|
676
|
+
total_tokens: totalTokens,
|
|
677
|
+
estimated_cost_usd: Math.round((inputCost + outputCost) * 10000) / 10000,
|
|
678
|
+
},
|
|
679
|
+
note: 'Token usage recorded locally. Start a session to attribute costs to your project.',
|
|
680
|
+
},
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Call the backend to report and attribute costs
|
|
685
|
+
const response = await apiClient.reportTokenUsage(currentSessionId, {
|
|
686
|
+
input_tokens: input_tokens!,
|
|
687
|
+
output_tokens: output_tokens!,
|
|
688
|
+
model: targetModel as 'opus' | 'sonnet' | 'haiku',
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
if (!response.ok) {
|
|
692
|
+
// Fall back to local calculation on error (use standard tier)
|
|
693
|
+
const pricing = getModelPricing('standard')[targetModel];
|
|
694
|
+
const inputCost = pricing ? (input_tokens! / 1_000_000) * pricing.input : 0;
|
|
695
|
+
const outputCost = pricing ? (output_tokens! / 1_000_000) * pricing.output : 0;
|
|
696
|
+
|
|
697
|
+
return {
|
|
698
|
+
result: {
|
|
699
|
+
success: true,
|
|
700
|
+
reported: {
|
|
701
|
+
model: targetModel,
|
|
702
|
+
input_tokens: input_tokens!,
|
|
703
|
+
output_tokens: output_tokens!,
|
|
704
|
+
total_tokens: totalTokens,
|
|
705
|
+
estimated_cost_usd: Math.round((inputCost + outputCost) * 10000) / 10000,
|
|
706
|
+
},
|
|
707
|
+
warning: 'Backend sync failed. Token usage recorded locally only.',
|
|
708
|
+
},
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const data = response.data!;
|
|
713
|
+
|
|
714
|
+
return {
|
|
715
|
+
result: {
|
|
716
|
+
success: true,
|
|
717
|
+
reported: data.reported,
|
|
718
|
+
task_attributed: data.task_attributed,
|
|
719
|
+
...(data.task_id && { task_id: data.task_id }),
|
|
720
|
+
note: data.task_attributed
|
|
721
|
+
? 'Token usage recorded and attributed to current task for per-task cost tracking.'
|
|
722
|
+
: 'Token usage recorded to session. No active task to attribute costs to.',
|
|
723
|
+
},
|
|
724
|
+
};
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Confirm that agent setup is complete for a project.
|
|
729
|
+
* This marks the agent type as onboarded, so future sessions won't receive setup instructions.
|
|
730
|
+
*/
|
|
731
|
+
export const confirmAgentSetup: Handler = async (args, _ctx) => {
|
|
732
|
+
const { project_id, agent_type } = parseArgs(args, confirmAgentSetupSchema);
|
|
733
|
+
|
|
734
|
+
if (!project_id || !agent_type) {
|
|
735
|
+
return {
|
|
736
|
+
result: {
|
|
737
|
+
error: 'project_id and agent_type are required',
|
|
738
|
+
},
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const apiClient = getApiClient();
|
|
743
|
+
const response = await apiClient.confirmAgentSetup(project_id, agent_type);
|
|
744
|
+
|
|
745
|
+
if (!response.ok) {
|
|
746
|
+
return {
|
|
747
|
+
result: {
|
|
748
|
+
error: response.error || 'Failed to confirm agent setup',
|
|
749
|
+
},
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return {
|
|
754
|
+
result: {
|
|
755
|
+
success: true,
|
|
756
|
+
project_id,
|
|
757
|
+
agent_type,
|
|
758
|
+
message: `Setup confirmed for ${agent_type} agent. You will no longer receive setup instructions for this project.`,
|
|
759
|
+
},
|
|
760
|
+
};
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
const signalIdleSchema = {
|
|
764
|
+
session_id: { type: 'string' as const },
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Signal that the agent is idle (no more tasks to work on).
|
|
769
|
+
* This immediately updates the session status to 'idle', providing real-time
|
|
770
|
+
* visibility on the dashboard instead of waiting for heartbeat timeout.
|
|
771
|
+
*
|
|
772
|
+
* Call this when:
|
|
773
|
+
* - complete_task returns no next_task
|
|
774
|
+
* - get_next_task returns no tasks
|
|
775
|
+
* - There's genuinely no work to do
|
|
776
|
+
*/
|
|
777
|
+
export const signalIdle: Handler = async (args, ctx) => {
|
|
778
|
+
const { session_id } = parseArgs(args, signalIdleSchema);
|
|
779
|
+
const { session } = ctx;
|
|
780
|
+
const targetSession = session_id || session.currentSessionId;
|
|
781
|
+
|
|
782
|
+
if (!targetSession) {
|
|
783
|
+
return {
|
|
784
|
+
result: {
|
|
785
|
+
error: 'No active session. Call start_work_session first.',
|
|
786
|
+
},
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const apiClient = getApiClient();
|
|
791
|
+
const response = await apiClient.signalIdle(targetSession);
|
|
792
|
+
|
|
793
|
+
if (!response.ok) {
|
|
794
|
+
return {
|
|
795
|
+
result: {
|
|
796
|
+
error: response.error || 'Failed to signal idle',
|
|
797
|
+
},
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
return {
|
|
802
|
+
result: {
|
|
803
|
+
success: true,
|
|
804
|
+
session_id: targetSession,
|
|
805
|
+
status: 'idle',
|
|
806
|
+
message: 'Session marked as idle. Dashboard will show you as inactive until you start a new task.',
|
|
807
|
+
next_steps: [
|
|
808
|
+
'Wait for new tasks to be added by the user',
|
|
809
|
+
'Or call start_fallback_activity() to do background work',
|
|
810
|
+
'Or call end_work_session() if you are done for the session',
|
|
811
|
+
],
|
|
812
|
+
AUTONOMY_RULES: {
|
|
813
|
+
message: 'Agents must be autonomous. Follow these rules:',
|
|
814
|
+
rules: [
|
|
815
|
+
'Never ask "should I continue?" → Just continue',
|
|
816
|
+
'Never ask "should I clear context?" → Just clear it',
|
|
817
|
+
'Never say "let me know what to do" → Use get_next_task or fallback activities',
|
|
818
|
+
'When context grows large: /clear → start_work_session (don\'t ask, just do it)',
|
|
819
|
+
],
|
|
820
|
+
preferred_activities: ['code_review', 'security_review', 'test_coverage', 'documentation_review', 'dependency_audit'],
|
|
821
|
+
directive: 'BE PROACTIVE: Start a fallback activity now rather than waiting for user input.',
|
|
822
|
+
},
|
|
823
|
+
},
|
|
824
|
+
};
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Session handlers registry
|
|
829
|
+
*/
|
|
830
|
+
export const sessionHandlers: HandlerRegistry = {
|
|
831
|
+
start_work_session: startWorkSession,
|
|
832
|
+
heartbeat: heartbeat,
|
|
833
|
+
end_work_session: endWorkSession,
|
|
834
|
+
signal_idle: signalIdle,
|
|
835
|
+
get_help: getHelp,
|
|
836
|
+
get_token_usage: getTokenUsage,
|
|
837
|
+
report_token_usage: reportTokenUsage,
|
|
838
|
+
confirm_agent_setup: confirmAgentSetup,
|
|
839
|
+
};
|