@kkelly-offical/kkcode 0.1.6 → 0.2.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/LICENSE +674 -674
- package/README.md +452 -387
- package/package.json +50 -46
- package/src/agent/agent.mjs +19 -2
- package/src/agent/custom-agent-loader.mjs +6 -3
- package/src/agent/generator.mjs +2 -2
- package/src/agent/prompt/assistant.txt +12 -0
- package/src/agent/prompt/bug-hunter.txt +90 -0
- package/src/agent/prompt/frontend-designer.txt +58 -58
- package/src/agent/prompt/guide.txt +1 -1
- package/src/agent/prompt/longagent-blueprint-agent.txt +83 -83
- package/src/agent/prompt/longagent-coding-agent.txt +37 -37
- package/src/agent/prompt/longagent-debugging-agent.txt +46 -46
- package/src/agent/prompt/longagent-preview-agent.txt +63 -63
- package/src/command/custom-commands.mjs +2 -2
- package/src/commands/agent.mjs +1 -1
- package/src/commands/background.mjs +145 -4
- package/src/commands/chat.mjs +117 -76
- package/src/commands/config.mjs +148 -1
- package/src/commands/doctor.mjs +30 -6
- package/src/commands/init.mjs +32 -6
- package/src/commands/longagent.mjs +117 -0
- package/src/commands/mcp.mjs +275 -43
- package/src/commands/permission.mjs +1 -1
- package/src/commands/session.mjs +195 -140
- package/src/commands/skill.mjs +63 -0
- package/src/commands/theme.mjs +1 -1
- package/src/config/defaults.mjs +280 -260
- package/src/config/import-config.mjs +1 -1
- package/src/config/load-config.mjs +61 -4
- package/src/config/schema.mjs +591 -574
- package/src/context.mjs +4 -1
- package/src/core/constants.mjs +97 -91
- package/src/core/types.mjs +1 -1
- package/src/github/api.mjs +78 -78
- package/src/github/auth.mjs +294 -286
- package/src/github/flow.mjs +298 -298
- package/src/github/workspace.mjs +225 -212
- package/src/index.mjs +84 -82
- package/src/knowledge/frontend-aesthetics.txt +38 -38
- package/src/mcp/client-http.mjs +139 -141
- package/src/mcp/client-sse.mjs +297 -288
- package/src/mcp/client-stdio.mjs +534 -533
- package/src/mcp/constants.mjs +2 -2
- package/src/mcp/registry.mjs +498 -479
- package/src/mcp/stdio-framing.mjs +135 -133
- package/src/mcp/tool-result.mjs +24 -24
- package/src/observability/edit-diagnostics.mjs +449 -0
- package/src/observability/index.mjs +42 -42
- package/src/observability/metrics.mjs +165 -137
- package/src/observability/tracer.mjs +137 -137
- package/src/onboarding.mjs +209 -0
- package/src/orchestration/background-manager.mjs +567 -372
- package/src/orchestration/background-worker.mjs +419 -305
- package/src/orchestration/interruption-reason.mjs +21 -0
- package/src/orchestration/longagent-manager.mjs +197 -171
- package/src/orchestration/stage-scheduler.mjs +733 -728
- package/src/orchestration/subagent-router.mjs +7 -1
- package/src/orchestration/task-scheduler.mjs +219 -7
- package/src/permission/engine.mjs +1 -1
- package/src/permission/exec-policy.mjs +370 -370
- package/src/permission/file-edit-policy.mjs +108 -0
- package/src/permission/prompt.mjs +1 -1
- package/src/permission/rules.mjs +116 -7
- package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
- package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
- package/src/plugin/hook-bus.mjs +19 -5
- package/src/plugin/manifest-loader.mjs +222 -0
- package/src/provider/anthropic.mjs +396 -390
- package/src/provider/ollama.mjs +7 -1
- package/src/provider/openai.mjs +382 -340
- package/src/provider/retry-policy.mjs +74 -68
- package/src/provider/router.mjs +242 -241
- package/src/provider/sse.mjs +104 -104
- package/src/provider/wizard.mjs +556 -0
- package/src/repl/capability-facade.mjs +30 -0
- package/src/repl/command-surface.mjs +23 -0
- package/src/repl/controller-entry.mjs +40 -0
- package/src/repl/core-shell.mjs +208 -0
- package/src/repl/dialog-router.mjs +87 -0
- package/src/repl/input-engine.mjs +76 -0
- package/src/repl/keymap.mjs +7 -0
- package/src/repl/operator-surface.mjs +15 -0
- package/src/repl/permission-flow.mjs +49 -0
- package/src/repl/runtime-facade.mjs +36 -0
- package/src/repl/slash-router.mjs +62 -0
- package/src/repl/state-store.mjs +29 -0
- package/src/repl/turn-controller.mjs +58 -0
- package/src/repl/verification.mjs +23 -0
- package/src/repl.mjs +3368 -2929
- package/src/rules/load-rules.mjs +3 -3
- package/src/runtime.mjs +1 -1
- package/src/session/agent-transaction.mjs +86 -0
- package/src/session/checkpoint.mjs +302 -302
- package/src/session/compaction.mjs +36 -14
- package/src/session/engine.mjs +417 -227
- package/src/session/longagent-4stage.mjs +467 -460
- package/src/session/longagent-hybrid.mjs +1344 -1081
- package/src/session/longagent-plan.mjs +376 -365
- package/src/session/longagent-project-memory.mjs +53 -53
- package/src/session/longagent-scaffold.mjs +291 -291
- package/src/session/longagent-task-bus.mjs +138 -54
- package/src/session/longagent-utils.mjs +828 -472
- package/src/session/longagent.mjs +911 -884
- package/src/session/loop.mjs +1005 -905
- package/src/session/prompt/agent.txt +25 -0
- package/src/session/prompt/anthropic.txt +150 -150
- package/src/session/prompt/beast.txt +1 -1
- package/src/session/prompt/plan.txt +28 -6
- package/src/session/prompt/qwen.txt +46 -46
- package/src/session/recovery.mjs +21 -0
- package/src/session/rollback.mjs +197 -0
- package/src/session/routing-observability.mjs +72 -0
- package/src/session/runtime-state.mjs +47 -0
- package/src/session/store.mjs +523 -510
- package/src/session/system-prompt.mjs +56 -8
- package/src/session/task-validator.mjs +267 -267
- package/src/session/usability-gates.mjs +2 -2
- package/src/skill/builtin/commit.mjs +64 -64
- package/src/skill/builtin/design.mjs +76 -76
- package/src/skill/generator.mjs +18 -2
- package/src/skill/registry.mjs +642 -390
- package/src/storage/audit-store.mjs +18 -11
- package/src/storage/event-log.mjs +7 -1
- package/src/storage/ghost-commit-store.mjs +243 -245
- package/src/storage/paths.mjs +13 -0
- package/src/theme/default-theme.mjs +1 -1
- package/src/theme/markdown.mjs +4 -0
- package/src/theme/schema.mjs +1 -1
- package/src/theme/status-bar.mjs +162 -158
- package/src/tool/audit-wrapper.mjs +18 -2
- package/src/tool/edit-transaction.mjs +23 -0
- package/src/tool/executor.mjs +26 -1
- package/src/tool/file-read-state.mjs +65 -0
- package/src/tool/git-auto.mjs +526 -526
- package/src/tool/git-full-auto.mjs +487 -478
- package/src/tool/mutation-guard.mjs +54 -0
- package/src/tool/prompt/edit.txt +3 -3
- package/src/tool/prompt/multiedit.txt +1 -0
- package/src/tool/prompt/notebookedit.txt +2 -1
- package/src/tool/prompt/patch.txt +25 -24
- package/src/tool/prompt/read.txt +3 -3
- package/src/tool/prompt/sysinfo.txt +29 -0
- package/src/tool/prompt/task.txt +66 -4
- package/src/tool/prompt/write.txt +2 -2
- package/src/tool/question-prompt.mjs +17 -4
- package/src/tool/registry.mjs +1701 -1343
- package/src/tool/task-tool.mjs +14 -6
- package/src/ui/activity-renderer.mjs +667 -664
- package/src/ui/repl-background-panel.mjs +7 -0
- package/src/ui/repl-capability-panel.mjs +9 -0
- package/src/ui/repl-dashboard.mjs +54 -4
- package/src/ui/repl-help.mjs +110 -0
- package/src/ui/repl-operator-panel.mjs +12 -0
- package/src/ui/repl-route-feedback.mjs +35 -0
- package/src/ui/repl-status-view.mjs +76 -0
- package/src/ui/repl-task-panel.mjs +5 -0
- package/src/ui/repl-transcript-panel.mjs +56 -0
- package/src/ui/repl-turn-summary.mjs +135 -0
- package/src/usage/pricing.mjs +122 -121
- package/src/usage/usage-meter.mjs +1 -0
- package/src/util/git.mjs +562 -519
- package/src/util/template.mjs +6 -1
package/src/mcp/registry.mjs
CHANGED
|
@@ -1,479 +1,498 @@
|
|
|
1
|
-
import { createHttpMcpClient } from "./client-http.mjs"
|
|
2
|
-
import { createStdioMcpClient } from "./client-stdio.mjs"
|
|
3
|
-
import { createSseMcpClient } from "./client-sse.mjs"
|
|
4
|
-
import { McpError } from "../core/errors.mjs"
|
|
5
|
-
import { EventBus } from "../core/events.mjs"
|
|
6
|
-
import { EVENT_TYPES } from "../core/constants.mjs"
|
|
7
|
-
import { readFile } from "node:fs/promises"
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
await
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
const
|
|
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
|
-
state.
|
|
473
|
-
|
|
474
|
-
state.
|
|
475
|
-
state.configured.
|
|
476
|
-
state.
|
|
477
|
-
state.
|
|
478
|
-
|
|
479
|
-
}
|
|
1
|
+
import { createHttpMcpClient } from "./client-http.mjs"
|
|
2
|
+
import { createStdioMcpClient } from "./client-stdio.mjs"
|
|
3
|
+
import { createSseMcpClient } from "./client-sse.mjs"
|
|
4
|
+
import { McpError } from "../core/errors.mjs"
|
|
5
|
+
import { EventBus } from "../core/events.mjs"
|
|
6
|
+
import { EVENT_TYPES } from "../core/constants.mjs"
|
|
7
|
+
import { readFile } from "node:fs/promises"
|
|
8
|
+
import { join } from "node:path"
|
|
9
|
+
import { userRootDir } from "../storage/paths.mjs"
|
|
10
|
+
|
|
11
|
+
const state = {
|
|
12
|
+
loaded: false,
|
|
13
|
+
servers: new Map(),
|
|
14
|
+
tools: new Map(),
|
|
15
|
+
prompts: new Map(),
|
|
16
|
+
health: new Map(),
|
|
17
|
+
configured: new Map(),
|
|
18
|
+
loadedAt: 0,
|
|
19
|
+
lastSignature: "",
|
|
20
|
+
initPromise: null,
|
|
21
|
+
shuttingDown: false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeTool(serverName, tool) {
|
|
25
|
+
const id = `mcp_${serverName}_${tool.name}`
|
|
26
|
+
return {
|
|
27
|
+
id,
|
|
28
|
+
server: serverName,
|
|
29
|
+
name: tool.name,
|
|
30
|
+
description: tool.description || `${serverName}:${tool.name}`,
|
|
31
|
+
inputSchema: tool.inputSchema || tool.input_schema || { type: "object", properties: {}, required: [] }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizePrompt(serverName, prompt) {
|
|
36
|
+
const id = `mcp_${serverName}_${prompt.name}`
|
|
37
|
+
return {
|
|
38
|
+
id,
|
|
39
|
+
server: serverName,
|
|
40
|
+
name: prompt.name,
|
|
41
|
+
description: prompt.description || `${serverName}:${prompt.name}`,
|
|
42
|
+
arguments: prompt.arguments || []
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveTransport(server = {}) {
|
|
47
|
+
const transport = String(server.transport || server.type || "stdio").toLowerCase()
|
|
48
|
+
if (transport === "http") return "http"
|
|
49
|
+
if (transport === "sse" || transport === "streamable-http") return "sse"
|
|
50
|
+
return "stdio"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createClient(name, server) {
|
|
54
|
+
const transport = resolveTransport(server)
|
|
55
|
+
if (transport === "sse") return createSseMcpClient(name, server)
|
|
56
|
+
if (transport === "http") return createHttpMcpClient(name, server)
|
|
57
|
+
return createStdioMcpClient(name, server)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function setHealth(name, serverConfig = {}, patch = {}) {
|
|
61
|
+
const prev = state.health.get(name) || {
|
|
62
|
+
name,
|
|
63
|
+
transport: resolveTransport(serverConfig),
|
|
64
|
+
ok: false,
|
|
65
|
+
reason: "not_checked",
|
|
66
|
+
error: null,
|
|
67
|
+
lastCheckedAt: 0
|
|
68
|
+
}
|
|
69
|
+
const next = {
|
|
70
|
+
...prev,
|
|
71
|
+
...patch,
|
|
72
|
+
name,
|
|
73
|
+
transport: patch.transport || prev.transport || resolveTransport(serverConfig),
|
|
74
|
+
lastCheckedAt: Date.now()
|
|
75
|
+
}
|
|
76
|
+
state.health.set(name, next)
|
|
77
|
+
return next
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Dynamic discovery: load MCP server configs from well-known project files.
|
|
82
|
+
* Checks (in order, merged):
|
|
83
|
+
* .mcp.json — Claude Code / VS Code convention
|
|
84
|
+
* .mcp/config.json — directory-based convention
|
|
85
|
+
* .kkcode/mcp.json — kkcode-specific
|
|
86
|
+
* <KKCODE_HOME>/mcp.json — global user-level
|
|
87
|
+
*/
|
|
88
|
+
async function discoverProjectServers(cwd) {
|
|
89
|
+
const candidates = [
|
|
90
|
+
join(cwd, ".mcp.json"),
|
|
91
|
+
join(cwd, ".mcp", "config.json"),
|
|
92
|
+
join(cwd, ".kkcode", "mcp.json"),
|
|
93
|
+
join(userRootDir(), "mcp.json")
|
|
94
|
+
]
|
|
95
|
+
const merged = {}
|
|
96
|
+
for (const filePath of candidates) {
|
|
97
|
+
try {
|
|
98
|
+
const raw = await readFile(filePath, "utf-8")
|
|
99
|
+
const parsed = JSON.parse(raw)
|
|
100
|
+
const servers = parsed?.servers || parsed?.mcpServers || {}
|
|
101
|
+
for (const [name, cfg] of Object.entries(servers)) {
|
|
102
|
+
if (!merged[name]) merged[name] = cfg
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// ignore missing/invalid files
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return merged
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function connectServer(name, server) {
|
|
112
|
+
const transport = resolveTransport(server)
|
|
113
|
+
let client
|
|
114
|
+
try {
|
|
115
|
+
client = createClient(name, server)
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const health = setHealth(name, server, {
|
|
118
|
+
ok: false,
|
|
119
|
+
reason: error.reason || "unknown",
|
|
120
|
+
error: error.message,
|
|
121
|
+
transport
|
|
122
|
+
})
|
|
123
|
+
await EventBus.emit({
|
|
124
|
+
type: EVENT_TYPES.MCP_HEALTH,
|
|
125
|
+
payload: { server: name, ...health }
|
|
126
|
+
})
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let health
|
|
131
|
+
try {
|
|
132
|
+
health = await client.health()
|
|
133
|
+
} catch (error) {
|
|
134
|
+
health = { ok: false, reason: error.reason || "unknown", error: error.message || String(error) }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const normalizedHealth = setHealth(name, server, {
|
|
138
|
+
ok: Boolean(health?.ok),
|
|
139
|
+
reason: health?.reason || (health?.ok ? "ok" : "unknown"),
|
|
140
|
+
error: health?.error || null,
|
|
141
|
+
phase: health?.phase || null,
|
|
142
|
+
transport
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
await EventBus.emit({
|
|
146
|
+
type: EVENT_TYPES.MCP_HEALTH,
|
|
147
|
+
payload: { server: name, ...normalizedHealth }
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
if (!normalizedHealth.ok) return null
|
|
151
|
+
|
|
152
|
+
state.servers.set(name, client)
|
|
153
|
+
|
|
154
|
+
// Discover tools
|
|
155
|
+
try {
|
|
156
|
+
const tools = await client.listTools()
|
|
157
|
+
for (const tool of tools) {
|
|
158
|
+
const normalized = normalizeTool(name, tool)
|
|
159
|
+
state.tools.set(normalized.id, normalized)
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
setHealth(name, server, {
|
|
163
|
+
ok: false,
|
|
164
|
+
reason: error.reason || "unknown",
|
|
165
|
+
error: `listTools failed: ${error.message}`
|
|
166
|
+
})
|
|
167
|
+
state.servers.delete(name)
|
|
168
|
+
await EventBus.emit({
|
|
169
|
+
type: EVENT_TYPES.MCP_HEALTH,
|
|
170
|
+
payload: { server: name, ...state.health.get(name) }
|
|
171
|
+
})
|
|
172
|
+
return null
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Discover prompts (optional)
|
|
176
|
+
if (typeof client.listPrompts === "function") {
|
|
177
|
+
try {
|
|
178
|
+
const prompts = await client.listPrompts()
|
|
179
|
+
for (const prompt of prompts) {
|
|
180
|
+
const normalized = normalizePrompt(name, prompt)
|
|
181
|
+
state.prompts.set(normalized.id, normalized)
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
// optional capability
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return client
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function reinitialize(config, { force = false, cwd = null } = {}) {
|
|
192
|
+
state.shuttingDown = false
|
|
193
|
+
const ttlMs = Math.max(0, Number(config?.runtime?.mcp_refresh_ttl_ms || 60000))
|
|
194
|
+
const effectiveCwd = cwd || process.cwd()
|
|
195
|
+
const sig = JSON.stringify({
|
|
196
|
+
mcp: config?.mcp || {},
|
|
197
|
+
runtime: config?.runtime || {},
|
|
198
|
+
cwd: effectiveCwd
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const cacheValid = state.loaded && !force && state.lastSignature === sig && Date.now() - state.loadedAt <= ttlMs
|
|
202
|
+
if (cacheValid) return
|
|
203
|
+
|
|
204
|
+
for (const [, client] of state.servers) {
|
|
205
|
+
if (typeof client.shutdown === "function") {
|
|
206
|
+
try { await Promise.resolve(client.shutdown()) } catch { /* best-effort */ }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
state.loaded = false
|
|
210
|
+
state.servers.clear()
|
|
211
|
+
state.tools.clear()
|
|
212
|
+
state.prompts.clear()
|
|
213
|
+
state.health.clear()
|
|
214
|
+
state.configured.clear()
|
|
215
|
+
|
|
216
|
+
// Built-in MCP servers (user config can override or disable with enabled: false)
|
|
217
|
+
const builtinServers = {
|
|
218
|
+
context7: {
|
|
219
|
+
command: "npx",
|
|
220
|
+
args: ["--yes", "@upstash/context7-mcp"],
|
|
221
|
+
timeout_ms: 30000,
|
|
222
|
+
startup_timeout_ms: 60000,
|
|
223
|
+
framing: "newline"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const configServers = config?.mcp?.servers || {}
|
|
227
|
+
const discoveredServers = config?.mcp?.auto_discover !== false
|
|
228
|
+
? await discoverProjectServers(effectiveCwd)
|
|
229
|
+
: {}
|
|
230
|
+
const allServers = { ...builtinServers, ...discoveredServers, ...configServers }
|
|
231
|
+
|
|
232
|
+
// Merge global mcp.* defaults into each server config (server-level overrides global)
|
|
233
|
+
const mcpGlobalDefaults = {}
|
|
234
|
+
for (const gk of ["timeout_ms", "shutdown_timeout_ms", "max_sse_buffer_bytes", "max_reconnect_attempts", "circuit_reset_ms", "max_buffer_bytes"]) {
|
|
235
|
+
if (config?.mcp?.[gk] !== undefined) mcpGlobalDefaults[gk] = config.mcp[gk]
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for (const [name, serverConfig] of Object.entries(allServers)) {
|
|
239
|
+
const effective = { ...mcpGlobalDefaults, ...serverConfig }
|
|
240
|
+
allServers[name] = effective
|
|
241
|
+
state.configured.set(name, effective)
|
|
242
|
+
if (serverConfig?.enabled === false) {
|
|
243
|
+
setHealth(name, serverConfig, {
|
|
244
|
+
ok: false,
|
|
245
|
+
reason: "disabled",
|
|
246
|
+
error: null
|
|
247
|
+
})
|
|
248
|
+
} else {
|
|
249
|
+
setHealth(name, serverConfig, {
|
|
250
|
+
ok: false,
|
|
251
|
+
reason: "not_checked",
|
|
252
|
+
error: null
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const entries = Object.entries(allServers).filter(([, serverConfig]) => serverConfig?.enabled !== false)
|
|
258
|
+
|
|
259
|
+
// 内置服务器(如 context7)必须在启动前加载,失败时重试一次
|
|
260
|
+
const builtinEntries = entries.filter(([name]) => name in builtinServers)
|
|
261
|
+
const otherEntries = entries.filter(([name]) => !(name in builtinServers))
|
|
262
|
+
|
|
263
|
+
// 优先加载内置 MCP 服务器(串行 + 重试)
|
|
264
|
+
for (const [name, serverConfig] of builtinEntries) {
|
|
265
|
+
let result = await connectServer(name, serverConfig)
|
|
266
|
+
if (!result) {
|
|
267
|
+
// 重试一次
|
|
268
|
+
const health = state.health.get(name)
|
|
269
|
+
const errMsg = health?.error || "unknown error"
|
|
270
|
+
process.stderr.write(`[kkcode] MCP builtin "${name}" 首次连接失败 (${errMsg}),正在重试...\n`)
|
|
271
|
+
result = await connectServer(name, serverConfig)
|
|
272
|
+
if (!result) {
|
|
273
|
+
process.stderr.write(`[kkcode] MCP builtin "${name}" 重试仍失败,跳过。可使用 /status 查看详情。\n`)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 并行加载其他 MCP 服务器
|
|
279
|
+
await Promise.allSettled(otherEntries.map(([name, serverConfig]) => connectServer(name, serverConfig)))
|
|
280
|
+
|
|
281
|
+
state.loaded = true
|
|
282
|
+
state.loadedAt = Date.now()
|
|
283
|
+
state.lastSignature = sig
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export const McpRegistry = {
|
|
287
|
+
async initialize(config, { force = false, cwd = null } = {}) {
|
|
288
|
+
if (state.initPromise) {
|
|
289
|
+
await state.initPromise
|
|
290
|
+
if (!force) return
|
|
291
|
+
}
|
|
292
|
+
state.initPromise = reinitialize(config, { force, cwd })
|
|
293
|
+
try {
|
|
294
|
+
await state.initPromise
|
|
295
|
+
} finally {
|
|
296
|
+
state.initPromise = null
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
isReady() {
|
|
301
|
+
return state.loaded
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
listServers() {
|
|
305
|
+
return [...state.servers.keys()]
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
serverInfo(name) {
|
|
309
|
+
const health = state.health.get(name)
|
|
310
|
+
if (!health) return null
|
|
311
|
+
return {
|
|
312
|
+
name,
|
|
313
|
+
transport: health.transport,
|
|
314
|
+
lastHealth: health.ok ? "ok" : "fail",
|
|
315
|
+
reason: health.reason || "unknown",
|
|
316
|
+
lastError: health.error || null
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
healthSnapshot() {
|
|
321
|
+
return [...state.health.entries()]
|
|
322
|
+
.map(([name, health]) => ({
|
|
323
|
+
name,
|
|
324
|
+
transport: health.transport || "stdio",
|
|
325
|
+
ok: Boolean(health.ok),
|
|
326
|
+
reason: health.reason || "unknown",
|
|
327
|
+
error: health.error || null,
|
|
328
|
+
phase: health.phase || null,
|
|
329
|
+
configured: state.configured.has(name),
|
|
330
|
+
enabled: state.configured.get(name)?.enabled !== false,
|
|
331
|
+
lastCheckedAt: health.lastCheckedAt || 0
|
|
332
|
+
}))
|
|
333
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
listTools() {
|
|
337
|
+
return [...state.tools.values()]
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
listPrompts() {
|
|
341
|
+
return [...state.prompts.values()]
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
async getPrompt(promptId, args = {}) {
|
|
345
|
+
const prompt = state.prompts.get(promptId)
|
|
346
|
+
if (!prompt) throw new McpError(`mcp prompt not found: ${promptId}`, { reason: "not_found", prompt: promptId })
|
|
347
|
+
const client = state.servers.get(prompt.server)
|
|
348
|
+
if (!client || typeof client.getPrompt !== "function") {
|
|
349
|
+
throw new McpError(`mcp server "${prompt.server}" does not support prompts/get`, { reason: "not_supported", server: prompt.server })
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
return await client.getPrompt(prompt.name, args)
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (error instanceof McpError) throw error
|
|
355
|
+
throw new McpError(`mcp prompt "${promptId}" failed: ${error?.message || error}`, {
|
|
356
|
+
reason: "bad_response", server: prompt.server, prompt: promptId
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
async listResources(serverName) {
|
|
362
|
+
const client = state.servers.get(serverName)
|
|
363
|
+
if (!client) return []
|
|
364
|
+
return client.listResources()
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
async listTemplates(serverName) {
|
|
368
|
+
const client = state.servers.get(serverName)
|
|
369
|
+
if (!client) return []
|
|
370
|
+
return client.listTemplates()
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
async callTool(toolId, args = {}, signal = null) {
|
|
374
|
+
if (state.shuttingDown) {
|
|
375
|
+
throw new McpError("MCP registry is shutting down", { reason: "shutting_down" })
|
|
376
|
+
}
|
|
377
|
+
const tool = state.tools.get(toolId)
|
|
378
|
+
if (!tool) throw new McpError(`mcp tool not found: ${toolId}`, { reason: "not_found", tool: toolId })
|
|
379
|
+
let client = state.servers.get(tool.server)
|
|
380
|
+
if (!client) throw new McpError(`mcp server not found: ${tool.server}`, { reason: "not_found", server: tool.server })
|
|
381
|
+
const serverConfig = state.configured.get(tool.server)
|
|
382
|
+
const serverTimeout = serverConfig?.timeout_ms
|
|
383
|
+
let effectiveSignal = signal
|
|
384
|
+
if (serverTimeout && !signal) {
|
|
385
|
+
effectiveSignal = AbortSignal.timeout(serverTimeout)
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
return await client.callTool(tool.name, args, effectiveSignal)
|
|
389
|
+
} catch (error) {
|
|
390
|
+
if (error?.reason === "spawn_failed" || error?.reason === "server_crash") {
|
|
391
|
+
setHealth(tool.server, serverConfig, {
|
|
392
|
+
ok: false, reason: error.reason, error: error.message
|
|
393
|
+
})
|
|
394
|
+
try {
|
|
395
|
+
await this.refreshServer(tool.server)
|
|
396
|
+
client = state.servers.get(tool.server)
|
|
397
|
+
if (client) {
|
|
398
|
+
const retrySignal = serverTimeout ? AbortSignal.timeout(serverTimeout) : null
|
|
399
|
+
return client.callTool(tool.name, args, retrySignal)
|
|
400
|
+
}
|
|
401
|
+
} catch {}
|
|
402
|
+
}
|
|
403
|
+
throw error
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
async refreshServer(name) {
|
|
408
|
+
const serverConfig = state.configured.get(name)
|
|
409
|
+
if (!serverConfig) throw new Error(`mcp server not configured: ${name}`)
|
|
410
|
+
const existing = state.servers.get(name)
|
|
411
|
+
if (existing && typeof existing.shutdown === "function") existing.shutdown()
|
|
412
|
+
state.servers.delete(name)
|
|
413
|
+
for (const [id, t] of state.tools) {
|
|
414
|
+
if (t.server === name) state.tools.delete(id)
|
|
415
|
+
}
|
|
416
|
+
for (const [id, p] of state.prompts) {
|
|
417
|
+
if (p.server === name) state.prompts.delete(id)
|
|
418
|
+
}
|
|
419
|
+
return connectServer(name, serverConfig)
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
async addServer(name, serverConfig) {
|
|
423
|
+
if (state.servers.has(name)) {
|
|
424
|
+
const existing = state.servers.get(name)
|
|
425
|
+
if (typeof existing.shutdown === "function") existing.shutdown()
|
|
426
|
+
state.servers.delete(name)
|
|
427
|
+
for (const [id, t] of state.tools) {
|
|
428
|
+
if (t.server === name) state.tools.delete(id)
|
|
429
|
+
}
|
|
430
|
+
for (const [id, p] of state.prompts) {
|
|
431
|
+
if (p.server === name) state.prompts.delete(id)
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
state.configured.set(name, serverConfig)
|
|
435
|
+
return connectServer(name, serverConfig)
|
|
436
|
+
},
|
|
437
|
+
|
|
438
|
+
async healthCheck(serverName) {
|
|
439
|
+
const client = state.servers.get(serverName)
|
|
440
|
+
const serverConfig = state.configured.get(serverName)
|
|
441
|
+
if (!client || !serverConfig) return { ok: false, reason: "not_found" }
|
|
442
|
+
try {
|
|
443
|
+
const result = await client.health()
|
|
444
|
+
const patch = {
|
|
445
|
+
ok: Boolean(result?.ok),
|
|
446
|
+
reason: result?.reason || (result?.ok ? "ok" : "unknown"),
|
|
447
|
+
error: result?.error || null
|
|
448
|
+
}
|
|
449
|
+
setHealth(serverName, serverConfig, patch)
|
|
450
|
+
await EventBus.emit({ type: EVENT_TYPES.MCP_HEALTH, payload: { server: serverName, ...patch } })
|
|
451
|
+
if (!result?.ok) {
|
|
452
|
+
try { await this.refreshServer(serverName) } catch {}
|
|
453
|
+
}
|
|
454
|
+
return patch
|
|
455
|
+
} catch (error) {
|
|
456
|
+
const patch = { ok: false, reason: error.reason || "unknown", error: error.message }
|
|
457
|
+
setHealth(serverName, serverConfig, patch)
|
|
458
|
+
return patch
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
|
|
462
|
+
async healthCheckAll() {
|
|
463
|
+
const results = {}
|
|
464
|
+
for (const name of state.configured.keys()) {
|
|
465
|
+
if (state.configured.get(name)?.enabled === false) continue
|
|
466
|
+
results[name] = await this.healthCheck(name)
|
|
467
|
+
}
|
|
468
|
+
return results
|
|
469
|
+
},
|
|
470
|
+
|
|
471
|
+
removeServer(name) {
|
|
472
|
+
const client = state.servers.get(name)
|
|
473
|
+
if (client && typeof client.shutdown === "function") client.shutdown()
|
|
474
|
+
state.servers.delete(name)
|
|
475
|
+
state.configured.delete(name)
|
|
476
|
+
state.health.delete(name)
|
|
477
|
+
for (const [id, t] of state.tools) {
|
|
478
|
+
if (t.server === name) state.tools.delete(id)
|
|
479
|
+
}
|
|
480
|
+
for (const [id, p] of state.prompts) {
|
|
481
|
+
if (p.server === name) state.prompts.delete(id)
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
shutdown() {
|
|
486
|
+
state.shuttingDown = true
|
|
487
|
+
for (const [, client] of state.servers) {
|
|
488
|
+
if (typeof client.shutdown === "function") client.shutdown()
|
|
489
|
+
}
|
|
490
|
+
state.servers.clear()
|
|
491
|
+
state.tools.clear()
|
|
492
|
+
state.prompts.clear()
|
|
493
|
+
state.health.clear()
|
|
494
|
+
state.configured.clear()
|
|
495
|
+
state.loaded = false
|
|
496
|
+
state.lastSignature = ""
|
|
497
|
+
}
|
|
498
|
+
}
|