SaylorTwift HF Staff commited on
Commit
4e23b01
·
verified ·
1 Parent(s): d522ffa

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. packages/agent-core-v2/docs/config-manifest.toml +490 -0
  2. packages/agent-core-v2/docs/state-manifest.d.ts +1270 -0
  3. packages/agent-core-v2/docs/wire-manifest.d.ts +946 -0
  4. packages/agent-core-v2/scripts/check-import-boundaries.d.mts +10 -0
  5. packages/agent-core-v2/scripts/check-import-boundaries.mjs +228 -0
  6. packages/agent-core-v2/scripts/debarrel.mjs +474 -0
  7. packages/agent-core-v2/scripts/gen-config-manifest.mts +314 -0
  8. packages/agent-core-v2/scripts/gen-contract-types.mjs +171 -0
  9. packages/agent-core-v2/scripts/gen-state-manifest.mts +882 -0
  10. packages/agent-core-v2/scripts/gen-wire-manifest.mts +1039 -0
  11. packages/agent-core-v2/scripts/generate-webp-dec-wasm.mjs +24 -0
  12. packages/agent-core-v2/src/_base/asyncEventQueue.ts +58 -0
  13. packages/agent-core-v2/src/_base/event.ts +281 -0
  14. packages/agent-core-v2/src/_base/version.ts +3 -0
  15. packages/agent-core-v2/src/app/scopes.ts +15 -0
  16. packages/agent-core-v2/src/debug/debugCascade.ts +63 -0
  17. packages/agent-core-v2/src/debug/debugCascadeService.ts +183 -0
  18. packages/agent-core-v2/src/debug/debugGraph.ts +30 -0
  19. packages/agent-core-v2/src/debug/debugGraphService.ts +76 -0
  20. packages/agent-core-v2/src/debug/debugLedger.ts +29 -0
  21. packages/agent-core-v2/src/debug/debugLedgerService.ts +56 -0
  22. packages/agent-core-v2/src/debug/errors.ts +10 -0
  23. packages/agent-core-v2/src/debug/index.ts +7 -0
  24. packages/agent-core-v2/src/debug/scopeTree.ts +38 -0
  25. packages/agent-core-v2/src/env.d.ts +4 -0
  26. packages/agent-core-v2/src/errors.ts +257 -0
  27. packages/agent-core-v2/src/events.ts +87 -0
  28. packages/agent-core-v2/src/features/feature.ts +117 -0
  29. packages/agent-core-v2/src/features/featureAssembly.ts +8 -0
  30. packages/agent-core-v2/src/features/featureAssemblyService.ts +26 -0
  31. packages/agent-core-v2/src/features/featureRegistry.ts +15 -0
  32. packages/agent-core-v2/src/hooks.ts +112 -0
  33. packages/agent-core-v2/src/human/index.ts +97 -0
  34. packages/agent-core-v2/src/human/package.json +6 -0
  35. packages/agent-core-v2/src/human/plugin.ts +54 -0
  36. packages/agent-core-v2/src/human/xstate2.ts +49 -0
  37. packages/agent-core-v2/src/human/xstateInspection.ts +72 -0
  38. packages/agent-core-v2/src/index.ts +758 -0
  39. packages/agent-core-v2/src/llm-adapter/record-diff.ts +49 -0
  40. packages/agent-core-v2/src/mcpCore/client-http.ts +169 -0
  41. packages/agent-core-v2/src/mcpCore/client-remote.ts +29 -0
  42. packages/agent-core-v2/src/mcpCore/client-shared.ts +109 -0
  43. packages/agent-core-v2/src/mcpCore/client-sse.ts +161 -0
  44. packages/agent-core-v2/src/mcpCore/client-stdio.ts +304 -0
  45. packages/agent-core-v2/src/mcpCore/config-schema.ts +68 -0
  46. packages/agent-core-v2/src/mcpCore/configView.ts +18 -0
  47. packages/agent-core-v2/src/mcpCore/connection-manager.ts +603 -0
  48. packages/agent-core-v2/src/mcpCore/errors.ts +13 -0
  49. packages/agent-core-v2/src/mcpCore/tool-naming.ts +26 -0
  50. packages/agent-core-v2/src/mcpCore/types.ts +55 -0
packages/agent-core-v2/docs/config-manifest.toml ADDED
@@ -0,0 +1,490 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Config Section Manifest
2
+ #
3
+ # Generated by scripts/gen-config-manifest.mts — do not edit by hand.
4
+ # Regenerate with: pnpm --filter @moonshot-ai/agent-core-v2 gen:config-manifest
5
+ #
6
+ # One [table] per registered config section, in the on-disk config.toml shape
7
+ # (snake_case keys). Un-commented assignments are registered defaults;
8
+ # commented "# field: type" lines describe the remaining schema fields.
9
+ # Values resolve as: default -> config.toml -> env overlay -> memory.
10
+
11
+ # Index (28 sections · 2 overlay(s))
12
+ # background src/agent/task/configSection.ts
13
+ # builtinProductSkills src/features/skill/catalog/configSection.ts
14
+ # cron src/features/cron/configSection.ts
15
+ # database src/persistence/configSection.ts
16
+ # defaultPermissionMode src/agent/permissionMode/configSection.ts
17
+ # defaultPlanMode src/features/plan/configSection.ts
18
+ # experimental src/app/flag/flag.ts
19
+ # extraAgentDirs src/workspace/workspaceAgentProfileLoader/configSection.ts
20
+ # extraSkillDirs src/features/skill/catalog/configSection.ts
21
+ # hooks src/features/externalHooks/configSection.ts
22
+ # identity src/app/agentIdentity/configSection.ts
23
+ # image src/agent/media/configSection.ts
24
+ # loopControl src/agent/loop/configSection.ts
25
+ # mcp src/app/mcpConfig/configSection.ts
26
+ # mergeAllAvailableSkills src/features/skill/catalog/configSection.ts
27
+ # modelCatalog src/app/kosongConfig/configSection.ts
28
+ # models src/app/kosongConfig/configSection.ts
29
+ # permission src/agent/permissionRules/configSection.ts
30
+ # providers src/app/kosongConfig/configSection.ts
31
+ # read src/agent/tools/os/read/configSection.ts
32
+ # secondaryModel src/session/subagent/configSection.ts
33
+ # services src/app/auth/configSection.ts
34
+ # subagent src/session/subagent/configSection.ts
35
+ # swarm src/features/swarm/configSection.ts
36
+ # task src/agent/task/configSection.ts
37
+ # thinking src/app/kosongConfig/configSection.ts
38
+ # tokenCounting src/agent/tokenCounting/configSection.ts
39
+ # tools src/agent/toolPolicy/configSection.ts
40
+ # (overlay) servicesCredentialEnvOverlay src/app/auth/configSection.ts
41
+ # (overlay) kimiModelEnvOverlay src/app/kosongConfig/envOverlay.ts
42
+
43
+ # ##########################################################################
44
+ # background
45
+ # owner: src/agent/task/configSection.ts
46
+ # scope: core
47
+ # hooks: stripEnv
48
+ # env:
49
+ # keep_alive_on_exit <- KIMI_CODE_BACKGROUND_KEEP_ALIVE_ON_EXIT (custom parse)
50
+ # max_running_tasks <- KIMI_CODE_BACKGROUND_MAX_RUNNING_TASKS (custom parse)
51
+ # bash_task_timeout_s <- KIMI_CODE_BACKGROUND_BASH_TASK_TIMEOUT_S (custom parse)
52
+ # print_wait_ceiling_s <- KIMI_CODE_BACKGROUND_PRINT_WAIT_CEILING_S (custom parse)
53
+ # print_background_mode <- KIMI_CODE_BACKGROUND_PRINT_BACKGROUND_MODE (custom parse)
54
+ # print_max_turns <- KIMI_CODE_BACKGROUND_PRINT_MAX_TURNS (custom parse)
55
+ # ##########################################################################
56
+
57
+ [background]
58
+ # max_running_tasks: integer
59
+ # keep_alive_on_exit: boolean
60
+ # bash_auto_background_on_timeout: boolean
61
+ # bash_task_timeout_s: integer
62
+ # kill_grace_period_ms: integer
63
+ # print_wait_ceiling_s: integer
64
+ # print_background_mode: "exit" | "drain" | "steer"
65
+ # print_max_turns: integer
66
+
67
+ # ##########################################################################
68
+ # builtinProductSkills (config.toml: builtin_product_skills)
69
+ # owner: src/features/skill/catalog/configSection.ts
70
+ # scope: core
71
+ # hooks: stripEnv
72
+ # env:
73
+ # <- KIMI_CODE_BUILTIN_PRODUCT_SKILLS (custom parse)
74
+ # ##########################################################################
75
+
76
+ builtin_product_skills = true
77
+
78
+ # ##########################################################################
79
+ # cron
80
+ # owner: src/features/cron/configSection.ts
81
+ # scope: core
82
+ # hooks: stripEnv
83
+ # env:
84
+ # debug <- KIMI_CRON_DEBUG (custom parse)
85
+ # no_jitter <- KIMI_CRON_NO_JITTER (custom parse)
86
+ # no_stale <- KIMI_CRON_NO_STALE (custom parse)
87
+ # disabled <- KIMI_DISABLE_CRON (custom parse)
88
+ # manual_tick <- KIMI_CRON_MANUAL_TICK (custom parse)
89
+ # clock <- KIMI_CRON_CLOCK
90
+ # poll_interval_ms <- KIMI_CRON_POLL_INTERVAL_MS (custom parse)
91
+ # ##########################################################################
92
+
93
+ [cron]
94
+ # (schema uses transforms; fields below come from the registered default)
95
+ debug = false
96
+ no_jitter = false
97
+ no_stale = false
98
+ disabled = false
99
+ manual_tick = false
100
+
101
+ # ##########################################################################
102
+ # database
103
+ # owner: src/persistence/configSection.ts
104
+ # scope: core
105
+ # hooks: stripEnv
106
+ # env:
107
+ # base <- KIMI_CODE_PERSISTENCE_MINIDB_READMODEL (custom parse)
108
+ # search <- KIMI_CODE_SEARCH_WORKER (custom parse)
109
+ # ##########################################################################
110
+
111
+ [database]
112
+ # base: boolean
113
+ # search: boolean
114
+
115
+ # ##########################################################################
116
+ # defaultPermissionMode (config.toml: default_permission_mode)
117
+ # owner: src/agent/permissionMode/configSection.ts
118
+ # scope: core
119
+ # ##########################################################################
120
+
121
+ # default_permission_mode: "manual" | "auto" | "yolo"
122
+
123
+ # ##########################################################################
124
+ # defaultPlanMode (config.toml: default_plan_mode)
125
+ # owner: src/features/plan/configSection.ts
126
+ # scope: core
127
+ # ##########################################################################
128
+
129
+ default_plan_mode = false
130
+
131
+ # ##########################################################################
132
+ # experimental
133
+ # owner: src/app/flag/flag.ts
134
+ # scope: core
135
+ # hooks: custom fromToml · custom toToml
136
+ # ##########################################################################
137
+
138
+ [experimental]
139
+ # <name>: boolean
140
+
141
+ # ##########################################################################
142
+ # extraAgentDirs (config.toml: extra_agent_dirs)
143
+ # owner: src/workspace/workspaceAgentProfileLoader/configSection.ts
144
+ # scope: core
145
+ # ##########################################################################
146
+
147
+ extra_agent_dirs = []
148
+
149
+ # ##########################################################################
150
+ # extraSkillDirs (config.toml: extra_skill_dirs)
151
+ # owner: src/features/skill/catalog/configSection.ts
152
+ # scope: core
153
+ # ##########################################################################
154
+
155
+ extra_skill_dirs = []
156
+
157
+ # ##########################################################################
158
+ # hooks
159
+ # owner: src/features/externalHooks/configSection.ts
160
+ # scope: core
161
+ # hooks: custom fromToml · custom toToml
162
+ # ##########################################################################
163
+
164
+ # one [[hooks]] table per entry:
165
+ # [[hooks]]
166
+ # event: "PreToolUse" | "PostToolUse" | "PostToolUseFailure" | "PermissionRequest" | "PermissionResult" | "UserPromptSubmit" | "UserPromptQueued" | "TurnStarted" | "Stop" | "StopFailure" | "Interrupt" | "SessionStart" | "SessionEnd" | "SessionHeartbeat" | "SubagentStart" | "SubagentStop" | "TaskStarted" | "PreCompact" | "PostCompact" | "Notification"
167
+ # matcher: string
168
+ # command: string
169
+ # timeout: integer
170
+
171
+ # ##########################################################################
172
+ # identity
173
+ # owner: src/app/agentIdentity/configSection.ts
174
+ # scope: core
175
+ # hooks: stripEnv
176
+ # env:
177
+ # name <- KIMI_CODE_IDENTITY_NAME (custom parse)
178
+ # slug <- KIMI_CODE_IDENTITY_SLUG (custom parse)
179
+ # ##########################################################################
180
+
181
+ [identity]
182
+ # name: string
183
+ # slug: string
184
+
185
+ # ##########################################################################
186
+ # image
187
+ # owner: src/agent/media/configSection.ts
188
+ # scope: core
189
+ # hooks: stripEnv
190
+ # env:
191
+ # max_edge_px <- KIMI_IMAGE_MAX_EDGE_PX (custom parse)
192
+ # read_byte_budget <- KIMI_IMAGE_READ_BYTE_BUDGET (custom parse)
193
+ # ##########################################################################
194
+
195
+ [image]
196
+ # max_edge_px: integer
197
+ # read_byte_budget: integer
198
+
199
+ # ##########################################################################
200
+ # loopControl (config.toml: loop_control)
201
+ # owner: src/agent/loop/configSection.ts
202
+ # scope: core
203
+ # hooks: custom toToml · stripEnv
204
+ # deprecations (old key is ignored + warns; rename manually):
205
+ # max_retries_per_step -> max_attempts_per_step
206
+ # max_steps_per_run -> max_steps_per_turn
207
+ # env:
208
+ # max_steps_per_turn <- KIMI_LOOP_MAX_STEPS_PER_TURN (custom parse)
209
+ # max_attempts_per_step <- KIMI_LOOP_MAX_ATTEMPTS_PER_STEP (custom parse; deprecated fallback KIMI_LOOP_MAX_RETRIES_PER_STEP)
210
+ # ##########################################################################
211
+
212
+ [loop_control]
213
+ # max_steps_per_turn: integer
214
+ # max_attempts_per_step: integer
215
+ # max_ralph_iterations: integer
216
+ # reserved_context_size: integer
217
+ # compaction_trigger_ratio: number
218
+ # compaction_max_attempts: integer
219
+
220
+ # ##########################################################################
221
+ # mcp
222
+ # owner: src/app/mcpConfig/configSection.ts
223
+ # scope: core
224
+ # hooks: stripEnv
225
+ # env:
226
+ # startup_timeout_ms <- KIMI_MCP_STARTUP_TIMEOUT_MS (custom parse)
227
+ # tool_timeout_ms <- KIMI_MCP_TOOL_TIMEOUT_MS (custom parse)
228
+ # ##########################################################################
229
+
230
+ [mcp]
231
+ # startup_timeout_ms: integer
232
+ # tool_timeout_ms: integer
233
+
234
+ # ##########################################################################
235
+ # mergeAllAvailableSkills (config.toml: merge_all_available_skills)
236
+ # owner: src/features/skill/catalog/configSection.ts
237
+ # scope: core
238
+ # ##########################################################################
239
+
240
+ merge_all_available_skills = true
241
+
242
+ # ##########################################################################
243
+ # modelCatalog (config.toml: model_catalog)
244
+ # owner: src/app/kosongConfig/configSection.ts
245
+ # scope: core
246
+ # ##########################################################################
247
+
248
+ [model_catalog]
249
+ # refresh_interval_ms: integer
250
+ # refresh_on_start: boolean
251
+
252
+ # ##########################################################################
253
+ # models
254
+ # owner: src/app/kosongConfig/configSection.ts
255
+ # scope: core
256
+ # hooks: custom fromToml · custom toToml
257
+ # ##########################################################################
258
+
259
+ [models]
260
+
261
+ # one [models."<name>"] table per entry:
262
+ # [models."<name>"]
263
+ # provider_id: string
264
+ # base_url: string
265
+ # api_key: string
266
+ # oauth: object
267
+ # storage: "file" | "keyring"
268
+ # key: string
269
+ # oauth_host: string
270
+ # protocol: "anthropic" | "openai" | "openai_responses" | "google-genai"
271
+ # name: string
272
+ # aliases: string[]
273
+ # provider: string
274
+ # model: string
275
+ # max_context_size: integer
276
+ # max_input_size: integer
277
+ # max_output_size: integer
278
+ # capabilities: string[]
279
+ # display_name: string
280
+ # reasoning_key: string
281
+ # adaptive_thinking: boolean
282
+ # beta_api: boolean
283
+ # support_efforts: string[]
284
+ # default_effort: string
285
+ # off_effort: string
286
+ # overrides: object
287
+ # max_context_size: integer
288
+ # max_input_size: integer
289
+ # max_output_size: integer
290
+ # capabilities: string[]
291
+ # display_name: string
292
+ # reasoning_key: string
293
+ # adaptive_thinking: boolean
294
+ # support_efforts: string[]
295
+ # default_effort: string
296
+ # off_effort: string
297
+
298
+ # ##########################################################################
299
+ # permission
300
+ # owner: src/agent/permissionRules/configSection.ts
301
+ # scope: core
302
+ # hooks: custom fromToml · custom toToml · stripEnv
303
+ # env:
304
+ # dangerous_command_guard <- KIMI_CODE_DANGEROUS_COMMAND_GUARD (custom parse)
305
+ # ##########################################################################
306
+
307
+ [permission]
308
+ # rules: object[] — one entry per item:
309
+ # decision: "allow" | "deny" | "ask"
310
+ # scope: "turn-override" | "session-runtime" | "project" | "user" (default: "user")
311
+ # pattern: string
312
+ # reason: string
313
+ # dangerous_command_guard: boolean
314
+
315
+ # ##########################################################################
316
+ # providers
317
+ # owner: src/app/kosongConfig/configSection.ts
318
+ # scope: core
319
+ # hooks: custom fromToml · custom toToml · stripEnv
320
+ # env:
321
+ # __kimi_env__.api_key <- KIMI_MODEL_API_KEY
322
+ # __kimi_env__.type <- KIMI_MODEL_PROVIDER_TYPE
323
+ # __kimi_env__.base_url <- KIMI_MODEL_BASE_URL
324
+ # ##########################################################################
325
+
326
+ [providers]
327
+
328
+ # one [providers."<name>"] table per entry:
329
+ # [providers."<name>"]
330
+ # model_source: "static" | "discover" | "oauth-catalog"
331
+ # base_url: string
332
+ # custom_headers: record<string, string>
333
+ # default_model: string
334
+ # type: string
335
+ # api_key: string
336
+ # oauth: object
337
+ # storage: "file" | "keyring"
338
+ # key: string
339
+ # oauth_host: string
340
+ # env: record<string, string>
341
+ # source: record<string, any>
342
+
343
+ # ##########################################################################
344
+ # read
345
+ # owner: src/agent/tools/os/read/configSection.ts
346
+ # scope: core
347
+ # ##########################################################################
348
+
349
+ [read]
350
+ # default_max_chars: integer
351
+ # max_chars: integer
352
+
353
+ # ##########################################################################
354
+ # secondaryModel (config.toml: secondary_model)
355
+ # owner: src/session/subagent/configSection.ts
356
+ # scope: core
357
+ # ##########################################################################
358
+
359
+ [secondary_model]
360
+ # default_model: string
361
+ # models: record<string, string>
362
+ # force: boolean
363
+ # model: string
364
+ # max_context_size: integer
365
+ # max_input_size: integer
366
+ # max_output_size: integer
367
+ # capabilities: string[]
368
+ # display_name: string
369
+ # reasoning_key: string
370
+ # adaptive_thinking: boolean
371
+ # support_efforts: string[]
372
+ # default_effort: string
373
+ # off_effort: string
374
+
375
+ # ##########################################################################
376
+ # services
377
+ # owner: src/app/auth/configSection.ts
378
+ # scope: core
379
+ # hooks: custom fromToml · custom toToml · stripEnv
380
+ # env:
381
+ # moonshot_search.base_url <- KIMI_WEB_SEARCH_BASE_URL (custom parse)
382
+ # moonshot_search.api_key <- KIMI_WEB_SEARCH_API_KEY (custom parse)
383
+ # moonshot_fetch.base_url <- KIMI_WEB_FETCH_BASE_URL (custom parse)
384
+ # moonshot_fetch.api_key <- KIMI_WEB_FETCH_API_KEY (custom parse)
385
+ # ##########################################################################
386
+
387
+ [services]
388
+
389
+ # [services.moonshot_search]
390
+ # base_url: string
391
+ # api_key: string
392
+ # oauth: object
393
+ # storage: "file" | "keyring"
394
+ # key: string
395
+ # oauth_host: string
396
+ # custom_headers: record<string, string>
397
+
398
+ # [services.moonshot_fetch]
399
+ # base_url: string
400
+ # api_key: string
401
+ # oauth: object
402
+ # storage: "file" | "keyring"
403
+ # key: string
404
+ # oauth_host: string
405
+ # custom_headers: record<string, string>
406
+
407
+ # ##########################################################################
408
+ # subagent
409
+ # owner: src/session/subagent/configSection.ts
410
+ # scope: core
411
+ # hooks: stripEnv
412
+ # env:
413
+ # timeout_ms <- KIMI_SUBAGENT_TIMEOUT_MS (custom parse)
414
+ # ##########################################################################
415
+
416
+ [subagent]
417
+ timeout_ms = 7200000
418
+
419
+ # ##########################################################################
420
+ # swarm
421
+ # owner: src/features/swarm/configSection.ts
422
+ # scope: core
423
+ # hooks: stripEnv
424
+ # env:
425
+ # timeout_ms <- KIMI_CODE_SWARM_TIMEOUT_MS (custom parse)
426
+ # ##########################################################################
427
+
428
+ [swarm]
429
+ timeout_ms = 7200000
430
+
431
+ # ##########################################################################
432
+ # task
433
+ # owner: src/agent/task/configSection.ts
434
+ # scope: core
435
+ # hooks: stripEnv
436
+ # env:
437
+ # keep_alive_on_exit <- KIMI_CODE_BACKGROUND_KEEP_ALIVE_ON_EXIT (custom parse)
438
+ # max_running_tasks <- KIMI_CODE_BACKGROUND_MAX_RUNNING_TASKS (custom parse)
439
+ # bash_task_timeout_s <- KIMI_CODE_BACKGROUND_BASH_TASK_TIMEOUT_S (custom parse)
440
+ # print_wait_ceiling_s <- KIMI_CODE_BACKGROUND_PRINT_WAIT_CEILING_S (custom parse)
441
+ # print_background_mode <- KIMI_CODE_BACKGROUND_PRINT_BACKGROUND_MODE (custom parse)
442
+ # print_max_turns <- KIMI_CODE_BACKGROUND_PRINT_MAX_TURNS (custom parse)
443
+ # ##########################################################################
444
+
445
+ [task]
446
+ # max_running_tasks: integer
447
+ # keep_alive_on_exit: boolean
448
+ # bash_auto_background_on_timeout: boolean
449
+ # bash_task_timeout_s: integer
450
+ # kill_grace_period_ms: integer
451
+ # print_wait_ceiling_s: integer
452
+ # print_background_mode: "exit" | "drain" | "steer"
453
+ # print_max_turns: integer
454
+
455
+ # ##########################################################################
456
+ # thinking
457
+ # owner: src/app/kosongConfig/configSection.ts
458
+ # scope: core
459
+ # hooks: stripEnv
460
+ # env:
461
+ # forced_effort <- KIMI_MODEL_THINKING_EFFORT
462
+ # ##########################################################################
463
+
464
+ [thinking]
465
+ # enabled: boolean
466
+ # effort: string
467
+ # forced_effort: string
468
+ # keep: string
469
+
470
+ # ##########################################################################
471
+ # tokenCounting (config.toml: token_counting)
472
+ # owner: src/agent/tokenCounting/configSection.ts
473
+ # scope: core
474
+ # hooks: stripEnv
475
+ # env:
476
+ # strategy <- KIMI_TOKEN_COUNTING_STRATEGY (custom parse)
477
+ # ##########################################################################
478
+
479
+ [token_counting]
480
+ strategy = "measured+estimated"
481
+
482
+ # ##########################################################################
483
+ # tools
484
+ # owner: src/agent/toolPolicy/configSection.ts
485
+ # scope: core
486
+ # ##########################################################################
487
+
488
+ [tools]
489
+ # enabled: string[]
490
+ # disabled: string[]
packages/agent-core-v2/docs/state-manifest.d.ts ADDED
@@ -0,0 +1,1270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // App, Workspace, Session & Agent State Manifest
2
+ //
3
+ // Generated by scripts/gen-state-manifest.mts — do not edit by hand.
4
+ // Regenerate with: pnpm --filter @moonshot-ai/agent-core-v2 gen:state-manifest
5
+ //
6
+ // Every state key registered into the App-scope IAppStateService, the
7
+ // Workspace-scope IWorkspaceStateService, the Session-scope
8
+ // ISessionStateService, or the Agent-scope IAgentStateService (see
9
+ // src/_base/state/stateRegistry.ts), collected statically from the
10
+ // `states.contributeState(...)` call sites and the replayable key chains — a
11
+ // `defineState(...).replayable(...)` key is contributed into the Agent-scope
12
+ // service by its owner service at construction, and
13
+ // carries a `// replayable · durable|transient · undoable? — folds: ...` line.
14
+ // Replayable values are excluded from snapshot()/inspect(). A key defined via
15
+ // defineState but never registered nor replayable does not appear here. Each entry shows the
16
+ // compile-time StateKey<T> value type fully expanded inline, so the manifest is
17
+ // self-contained (no imports, no helper declarations). A named type is marked
18
+ // at its expansion site with a `/* TypeName — source/file.ts */` comment; a
19
+ // `/* TypeName — recursive (...) */ unknown` marker stops a recursive expansion.
20
+ // Lib globals (Map/Set/Record/…) are referenced as-is. Generic instantiations
21
+ // expand structurally; classes render as their public instance shape. The
22
+ // defining source file heads each group.
23
+ //
24
+ // snapshot() returns JSON-safe deep copies of these values: Maps become plain
25
+ // objects (or [key, value] entry arrays when a key is not string/number), Sets
26
+ // become arrays, bigints become strings, functions are dropped, circular
27
+ // references become '(circular)', and class instances collapse to a '(ClassName)'
28
+ // marker — the wire shape of an entry is the JSON projection of the type here.
29
+ //
30
+ // Index (App: 0 keys · Workspace: 6 keys · Session: 9 keys · Agent: 73 keys)
31
+ // App
32
+ // Workspace
33
+ // workspaceDirs.ephemeralDirs src/workspace/workspaceDirs/workspaceDirsService.ts
34
+ // workspaceDirs.fileDirs src/workspace/workspaceDirs/workspaceDirsService.ts
35
+ // workspaceInstructions.current src/workspace/workspaceInstructions/workspaceInstructionsService.ts
36
+ // workspaceSkillCatalog.contributions src/features/skill/workspace/workspaceSkillCatalogService.ts
37
+ // workspaceSkillCatalog.merged src/features/skill/workspace/workspaceSkillCatalogService.ts
38
+ // workspaceTrust.trusted src/workspace/workspaceTrust/workspaceTrustService.ts
39
+ // Session
40
+ // sessionActivity.current src/session/sessionActivity/sessionActivityService.ts
41
+ // sessionActivity.folds src/session/sessionActivity/sessionActivityService.ts
42
+ // sessionLog.rootLevel src/session/sessionLog/sessionLogService.ts
43
+ // sessionMetadata.data src/session/sessionMetadata/sessionMetadataService.ts
44
+ // sessionSkillCatalog.contributions src/features/skill/session/skillCatalogService.ts
45
+ // sessionSkillCatalog.merged src/features/skill/session/skillCatalogService.ts
46
+ // sessionToolPolicy.state src/session/sessionToolPolicy/sessionToolPolicyService.ts
47
+ // workspaceContext.additionalDirs src/session/workspaceContext/workspaceContextService.ts
48
+ // workspaceContext.workDir src/session/workspaceContext/workspaceContextService.ts
49
+ // Agent
50
+ // agentPlugin.sessionStartRefreshPending src/agent/plugin/agentPluginService.ts
51
+ // agentsMdReminder.cwd src/agent/agentsMdReminder/agentsMdReminderService.ts
52
+ // agentsMdReminder.known src/agent/agentsMdReminder/agentsMdReminderService.ts
53
+ // agentsMdReminder.seeded src/agent/agentsMdReminder/agentsMdReminderService.ts
54
+ // contextMemory src/agent/contextMemory/contextOps.ts
55
+ // contextProjector.lastRepairSignature src/agent/contextProjector/contextProjectorService.ts
56
+ // externalHooks.stopHookContinuationUsed src/features/externalHooks/agent/agentExternalHooksService.ts
57
+ // fileHistory src/features/fileHistory/fileHistoryOps.ts
58
+ // fullCompaction src/agent/fullCompaction/compactionOps.ts
59
+ // fullCompaction.activeTurnId src/agent/fullCompaction/fullCompactionService.ts
60
+ // fullCompaction.compactionCountInTurn src/agent/fullCompaction/fullCompactionService.ts
61
+ // fullCompaction.consecutiveOverflowCompactions src/agent/fullCompaction/fullCompactionService.ts
62
+ // fullCompaction.lastCompactedTokenCount src/agent/fullCompaction/fullCompactionService.ts
63
+ // fullCompaction.observedMaxContextTokensByModel src/agent/fullCompaction/fullCompactionService.ts
64
+ // fullCompaction.wireRanges src/agent/fullCompaction/compactionOps.ts
65
+ // interruptionReminder src/agent/interruptionReminder/interruptionReminderOps.ts
66
+ // llm.requestTrace src/agent/llmRequester/llmRequestOps.ts
67
+ // llmRequester.emittedThinkingEffortWarnings src/agent/llmRequester/llmRequesterService.ts
68
+ // llmRequester.lastConfigLogSignature src/agent/llmRequester/llmRequesterService.ts
69
+ // llmRequester.mediaDegradedTurns src/agent/llmRequester/llmRequesterService.ts
70
+ // llmRequester.mediaStrippedTurns src/agent/llmRequester/llmRequesterService.ts
71
+ // llmRequester.turnConfigs src/agent/llmRequester/llmRequesterService.ts
72
+ // loop.disposing src/agent/loop/loopService.ts
73
+ // loop.lastRequestTraceId src/agent/loop/loopService.ts
74
+ // mcp.discovery src/agent/mcp/mcpDiscoveryOps.ts
75
+ // mcp.discoveryWritesReady src/agent/mcp/mcpService.ts
76
+ // mcp.mcpToolsByServer src/agent/mcp/mcpService.ts
77
+ // media.budgetDropped src/agent/media/mediaResolverService.ts
78
+ // media.registeredKey src/agent/media/mediaToolsRegistrar.ts
79
+ // media.resolved src/agent/media/mediaResolverService.ts
80
+ // permissionMode src/agent/permissionMode/permissionModeOps.ts
81
+ // permissionMode.configured src/agent/permissionMode/permissionModeOps.ts
82
+ // permissionMode.lastMode src/agent/permissionMode/injection/permissionModeInjection.ts
83
+ // permissionRules src/agent/permissionRules/permissionRulesOps.ts
84
+ // plan src/features/plan/planOps.ts
85
+ // plan.wasActive src/features/plan/injection/planModeInjection.ts
86
+ // pluginSessionStartSnapshot src/agent/plugin/agentPluginOps.ts
87
+ // profile src/agent/profile/profileOps.ts
88
+ // profile.activeToolNamesOverlay src/agent/profile/profileService.ts
89
+ // profile.activeTools src/agent/profile/profileOps.ts
90
+ // profile.agentsMdWarning src/agent/profile/profileService.ts
91
+ // profile.emittedPluginBudgetWarnings src/agent/profile/profileService.ts
92
+ // profile.emittedThinkingEffortWarnings src/agent/profile/profileService.ts
93
+ // profile.emittedToolPatternWarnings src/agent/profile/profileService.ts
94
+ // runtime.binding src/agent/runtimeBinding/runtimeBindingService.ts
95
+ // runtimeBinding src/agent/runtimeBinding/runtimeBindingOps.ts
96
+ // shellCommand.tasks src/agent/shellCommand/shellCommandService.ts
97
+ // swarm src/features/swarm/swarmOps.ts
98
+ // task src/agent/task/taskOps.ts
99
+ // task.activeTaskReminderPending src/agent/task/taskService.ts
100
+ // task.deliveredNotificationKeys src/agent/task/taskService.ts
101
+ // task.ghosts src/agent/task/taskService.ts
102
+ // task.notificationDelivery src/agent/task/taskService.ts
103
+ // task.scheduledNotificationKeys src/agent/task/taskService.ts
104
+ // toolDedupe.activeStep src/agent/toolDedupe/toolDedupeService.ts
105
+ // toolDedupe.activeTurnId src/agent/toolDedupe/toolDedupeService.ts
106
+ // toolDedupe.callKeyByCallId src/agent/toolDedupe/toolDedupeService.ts
107
+ // toolDedupe.consecutiveCount src/agent/toolDedupe/toolDedupeService.ts
108
+ // toolDedupe.consecutiveKey src/agent/toolDedupe/toolDedupeService.ts
109
+ // toolDedupe.handoffPhase src/agent/toolDedupe/toolDedupeService.ts
110
+ // toolDedupe.originalCallIndex src/agent/toolDedupe/toolDedupeService.ts
111
+ // toolDedupe.stepCalls src/agent/toolDedupe/toolDedupeService.ts
112
+ // toolDedupe.syntheticCallIds src/agent/toolDedupe/toolDedupeService.ts
113
+ // toolDedupe.turnCallRecords src/agent/toolDedupe/toolDedupeService.ts
114
+ // toolDedupe.turnRepeatCount src/agent/toolDedupe/toolDedupeService.ts
115
+ // toolExecutor.dupTypeTurnId src/agent/toolExecutor/toolExecutorService.ts
116
+ // toolExecutor.toolCallDupTypes src/agent/toolExecutor/toolExecutorService.ts
117
+ // toolSelect.pendingLoaded src/agent/toolSelect/toolSelectService.ts
118
+ // tower src/features/tower/towerOps.ts
119
+ // tower.base src/features/tower/towerOps.ts
120
+ // tower.owner src/features/tower/towerOps.ts
121
+ // turn src/agent/loop/turnOps.ts
122
+ // userTool src/agent/userTool/userToolOps.ts
123
+
124
+ /** App-scope keys registered into IAppStateService. */
125
+ export interface AppStateSnapshot {
126
+ }
127
+
128
+ export type AppStateKey = keyof AppStateSnapshot;
129
+
130
+ /** Workspace-scope keys registered into IWorkspaceStateService. */
131
+ export interface WorkspaceStateSnapshot {
132
+ // src/features/skill/workspace/workspaceSkillCatalogService.ts
133
+ 'workspaceSkillCatalog.contributions': Map<string, {
134
+ readonly c: /* SkillContribution — packages/agent-core-v2/src/features/skill/catalog/skillSource.ts */ {
135
+ readonly skills: readonly /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
136
+ readonly name: string;
137
+ readonly description: string;
138
+ readonly path: string;
139
+ readonly dir: string;
140
+ readonly content: string;
141
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
142
+ readonly name?: string;
143
+ readonly description?: string;
144
+ readonly type?: string;
145
+ readonly whenToUse?: string;
146
+ readonly disableModelInvocation?: boolean;
147
+ readonly isSubSkill?: boolean;
148
+ readonly safe?: boolean;
149
+ readonly arguments?: string | readonly unknown[];
150
+ [key: string]: unknown;
151
+ };
152
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
153
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
154
+ readonly id: string;
155
+ readonly instructions?: string;
156
+ };
157
+ readonly mermaid?: string;
158
+ readonly d2?: string;
159
+ readonly productSpecific?: boolean;
160
+ readonly experimentalFlag?: string;
161
+ }[];
162
+ readonly skipped?: readonly /* SkippedSkill — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
163
+ readonly path: string;
164
+ readonly type: string;
165
+ readonly reason: string;
166
+ }[];
167
+ readonly scannedRoots?: readonly string[];
168
+ };
169
+ readonly priority: number;
170
+ }>;
171
+ 'workspaceSkillCatalog.merged': /* InMemorySkillCatalog — packages/agent-core-v2/src/features/skill/catalog/registry.ts */ {
172
+ registerBuiltinSkill: (skill: /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
173
+ readonly name: string;
174
+ readonly description: string;
175
+ readonly path: string;
176
+ readonly dir: string;
177
+ readonly content: string;
178
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
179
+ readonly name?: string;
180
+ readonly description?: string;
181
+ readonly type?: string;
182
+ readonly whenToUse?: string;
183
+ readonly disableModelInvocation?: boolean;
184
+ readonly isSubSkill?: boolean;
185
+ readonly safe?: boolean;
186
+ readonly arguments?: string | readonly unknown[];
187
+ [key: string]: unknown;
188
+ };
189
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
190
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
191
+ readonly id: string;
192
+ readonly instructions?: string;
193
+ };
194
+ readonly mermaid?: string;
195
+ readonly d2?: string;
196
+ readonly productSpecific?: boolean;
197
+ readonly experimentalFlag?: string;
198
+ }) => void;
199
+ register: (skill: /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
200
+ readonly name: string;
201
+ readonly description: string;
202
+ readonly path: string;
203
+ readonly dir: string;
204
+ readonly content: string;
205
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
206
+ readonly name?: string;
207
+ readonly description?: string;
208
+ readonly type?: string;
209
+ readonly whenToUse?: string;
210
+ readonly disableModelInvocation?: boolean;
211
+ readonly isSubSkill?: boolean;
212
+ readonly safe?: boolean;
213
+ readonly arguments?: string | readonly unknown[];
214
+ [key: string]: unknown;
215
+ };
216
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
217
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
218
+ readonly id: string;
219
+ readonly instructions?: string;
220
+ };
221
+ readonly mermaid?: string;
222
+ readonly d2?: string;
223
+ readonly productSpecific?: boolean;
224
+ readonly experimentalFlag?: string;
225
+ }, options?: {
226
+ readonly replace?: boolean;
227
+ }) => void;
228
+ recordSkipped: (skills: readonly /* SkippedSkill — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
229
+ readonly path: string;
230
+ readonly type: string;
231
+ readonly reason: string;
232
+ }[]) => void;
233
+ addRoots: (roots: readonly string[]) => void;
234
+ getSkill: (name: string) => /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
235
+ readonly name: string;
236
+ readonly description: string;
237
+ readonly path: string;
238
+ readonly dir: string;
239
+ readonly content: string;
240
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
241
+ readonly name?: string;
242
+ readonly description?: string;
243
+ readonly type?: string;
244
+ readonly whenToUse?: string;
245
+ readonly disableModelInvocation?: boolean;
246
+ readonly isSubSkill?: boolean;
247
+ readonly safe?: boolean;
248
+ readonly arguments?: string | readonly unknown[];
249
+ [key: string]: unknown;
250
+ };
251
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
252
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
253
+ readonly id: string;
254
+ readonly instructions?: string;
255
+ };
256
+ readonly mermaid?: string;
257
+ readonly d2?: string;
258
+ readonly productSpecific?: boolean;
259
+ readonly experimentalFlag?: string;
260
+ } | undefined;
261
+ getPluginSkill: (pluginId: string, name: string) => /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
262
+ readonly name: string;
263
+ readonly description: string;
264
+ readonly path: string;
265
+ readonly dir: string;
266
+ readonly content: string;
267
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
268
+ readonly name?: string;
269
+ readonly description?: string;
270
+ readonly type?: string;
271
+ readonly whenToUse?: string;
272
+ readonly disableModelInvocation?: boolean;
273
+ readonly isSubSkill?: boolean;
274
+ readonly safe?: boolean;
275
+ readonly arguments?: string | readonly unknown[];
276
+ [key: string]: unknown;
277
+ };
278
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
279
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
280
+ readonly id: string;
281
+ readonly instructions?: string;
282
+ };
283
+ readonly mermaid?: string;
284
+ readonly d2?: string;
285
+ readonly productSpecific?: boolean;
286
+ readonly experimentalFlag?: string;
287
+ } | undefined;
288
+ renderSkillPrompt: (skill: /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
289
+ readonly name: string;
290
+ readonly description: string;
291
+ readonly path: string;
292
+ readonly dir: string;
293
+ readonly content: string;
294
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
295
+ readonly name?: string;
296
+ readonly description?: string;
297
+ readonly type?: string;
298
+ readonly whenToUse?: string;
299
+ readonly disableModelInvocation?: boolean;
300
+ readonly isSubSkill?: boolean;
301
+ readonly safe?: boolean;
302
+ readonly arguments?: string | readonly unknown[];
303
+ [key: string]: unknown;
304
+ };
305
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
306
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
307
+ readonly id: string;
308
+ readonly instructions?: string;
309
+ };
310
+ readonly mermaid?: string;
311
+ readonly d2?: string;
312
+ readonly productSpecific?: boolean;
313
+ readonly experimentalFlag?: string;
314
+ }, rawArgs: string, context?: {
315
+ readonly sessionId?: string;
316
+ }) => string;
317
+ listSkills: () => readonly /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
318
+ readonly name: string;
319
+ readonly description: string;
320
+ readonly path: string;
321
+ readonly dir: string;
322
+ readonly content: string;
323
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
324
+ readonly name?: string;
325
+ readonly description?: string;
326
+ readonly type?: string;
327
+ readonly whenToUse?: string;
328
+ readonly disableModelInvocation?: boolean;
329
+ readonly isSubSkill?: boolean;
330
+ readonly safe?: boolean;
331
+ readonly arguments?: string | readonly unknown[];
332
+ [key: string]: unknown;
333
+ };
334
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
335
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
336
+ readonly id: string;
337
+ readonly instructions?: string;
338
+ };
339
+ readonly mermaid?: string;
340
+ readonly d2?: string;
341
+ readonly productSpecific?: boolean;
342
+ readonly experimentalFlag?: string;
343
+ }[];
344
+ listInvocableSkills: () => readonly /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
345
+ readonly name: string;
346
+ readonly description: string;
347
+ readonly path: string;
348
+ readonly dir: string;
349
+ readonly content: string;
350
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
351
+ readonly name?: string;
352
+ readonly description?: string;
353
+ readonly type?: string;
354
+ readonly whenToUse?: string;
355
+ readonly disableModelInvocation?: boolean;
356
+ readonly isSubSkill?: boolean;
357
+ readonly safe?: boolean;
358
+ readonly arguments?: string | readonly unknown[];
359
+ [key: string]: unknown;
360
+ };
361
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
362
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
363
+ readonly id: string;
364
+ readonly instructions?: string;
365
+ };
366
+ readonly mermaid?: string;
367
+ readonly d2?: string;
368
+ readonly productSpecific?: boolean;
369
+ readonly experimentalFlag?: string;
370
+ }[];
371
+ getSkillRoots: () => readonly string[];
372
+ getSkippedByPolicy: () => readonly /* SkippedSkill — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
373
+ readonly path: string;
374
+ readonly type: string;
375
+ readonly reason: string;
376
+ }[];
377
+ getKimiSkillsDescription: () => string;
378
+ getModelSkillListing: () => string;
379
+ };
380
+ // src/workspace/workspaceDirs/workspaceDirsService.ts
381
+ 'workspaceDirs.ephemeralDirs': readonly string[];
382
+ 'workspaceDirs.fileDirs': readonly string[];
383
+ // src/workspace/workspaceInstructions/workspaceInstructionsService.ts
384
+ 'workspaceInstructions.current': /* WorkspaceInstructionsSnapshot — packages/agent-core-v2/src/workspace/workspaceInstructions/workspaceInstructions.ts */ {
385
+ readonly agentsMd: string | undefined;
386
+ readonly agentsMdWarning: string | undefined;
387
+ readonly agentsMdPaths: readonly string[] | undefined;
388
+ };
389
+ // src/workspace/workspaceTrust/workspaceTrustService.ts
390
+ 'workspaceTrust.trusted': boolean;
391
+ }
392
+
393
+ export type WorkspaceStateKey = keyof WorkspaceStateSnapshot;
394
+
395
+ /** Session-scope keys registered into ISessionStateService. */
396
+ export interface SessionStateSnapshot {
397
+ // src/features/skill/session/skillCatalogService.ts
398
+ 'sessionSkillCatalog.contributions': Map<string, {
399
+ readonly c: /* SkillContribution — packages/agent-core-v2/src/features/skill/catalog/skillSource.ts */ {
400
+ readonly skills: readonly /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
401
+ readonly name: string;
402
+ readonly description: string;
403
+ readonly path: string;
404
+ readonly dir: string;
405
+ readonly content: string;
406
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
407
+ readonly name?: string;
408
+ readonly description?: string;
409
+ readonly type?: string;
410
+ readonly whenToUse?: string;
411
+ readonly disableModelInvocation?: boolean;
412
+ readonly isSubSkill?: boolean;
413
+ readonly safe?: boolean;
414
+ readonly arguments?: string | readonly unknown[];
415
+ [key: string]: unknown;
416
+ };
417
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
418
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
419
+ readonly id: string;
420
+ readonly instructions?: string;
421
+ };
422
+ readonly mermaid?: string;
423
+ readonly d2?: string;
424
+ readonly productSpecific?: boolean;
425
+ readonly experimentalFlag?: string;
426
+ }[];
427
+ readonly skipped?: readonly /* SkippedSkill — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
428
+ readonly path: string;
429
+ readonly type: string;
430
+ readonly reason: string;
431
+ }[];
432
+ readonly scannedRoots?: readonly string[];
433
+ };
434
+ readonly priority: number;
435
+ }>;
436
+ 'sessionSkillCatalog.merged': /* InMemorySkillCatalog — packages/agent-core-v2/src/features/skill/catalog/registry.ts */ {
437
+ registerBuiltinSkill: (skill: /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
438
+ readonly name: string;
439
+ readonly description: string;
440
+ readonly path: string;
441
+ readonly dir: string;
442
+ readonly content: string;
443
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
444
+ readonly name?: string;
445
+ readonly description?: string;
446
+ readonly type?: string;
447
+ readonly whenToUse?: string;
448
+ readonly disableModelInvocation?: boolean;
449
+ readonly isSubSkill?: boolean;
450
+ readonly safe?: boolean;
451
+ readonly arguments?: string | readonly unknown[];
452
+ [key: string]: unknown;
453
+ };
454
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
455
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
456
+ readonly id: string;
457
+ readonly instructions?: string;
458
+ };
459
+ readonly mermaid?: string;
460
+ readonly d2?: string;
461
+ readonly productSpecific?: boolean;
462
+ readonly experimentalFlag?: string;
463
+ }) => void;
464
+ register: (skill: /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
465
+ readonly name: string;
466
+ readonly description: string;
467
+ readonly path: string;
468
+ readonly dir: string;
469
+ readonly content: string;
470
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
471
+ readonly name?: string;
472
+ readonly description?: string;
473
+ readonly type?: string;
474
+ readonly whenToUse?: string;
475
+ readonly disableModelInvocation?: boolean;
476
+ readonly isSubSkill?: boolean;
477
+ readonly safe?: boolean;
478
+ readonly arguments?: string | readonly unknown[];
479
+ [key: string]: unknown;
480
+ };
481
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
482
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
483
+ readonly id: string;
484
+ readonly instructions?: string;
485
+ };
486
+ readonly mermaid?: string;
487
+ readonly d2?: string;
488
+ readonly productSpecific?: boolean;
489
+ readonly experimentalFlag?: string;
490
+ }, options?: {
491
+ readonly replace?: boolean;
492
+ }) => void;
493
+ recordSkipped: (skills: readonly /* SkippedSkill — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
494
+ readonly path: string;
495
+ readonly type: string;
496
+ readonly reason: string;
497
+ }[]) => void;
498
+ addRoots: (roots: readonly string[]) => void;
499
+ getSkill: (name: string) => /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
500
+ readonly name: string;
501
+ readonly description: string;
502
+ readonly path: string;
503
+ readonly dir: string;
504
+ readonly content: string;
505
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
506
+ readonly name?: string;
507
+ readonly description?: string;
508
+ readonly type?: string;
509
+ readonly whenToUse?: string;
510
+ readonly disableModelInvocation?: boolean;
511
+ readonly isSubSkill?: boolean;
512
+ readonly safe?: boolean;
513
+ readonly arguments?: string | readonly unknown[];
514
+ [key: string]: unknown;
515
+ };
516
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
517
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
518
+ readonly id: string;
519
+ readonly instructions?: string;
520
+ };
521
+ readonly mermaid?: string;
522
+ readonly d2?: string;
523
+ readonly productSpecific?: boolean;
524
+ readonly experimentalFlag?: string;
525
+ } | undefined;
526
+ getPluginSkill: (pluginId: string, name: string) => /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
527
+ readonly name: string;
528
+ readonly description: string;
529
+ readonly path: string;
530
+ readonly dir: string;
531
+ readonly content: string;
532
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
533
+ readonly name?: string;
534
+ readonly description?: string;
535
+ readonly type?: string;
536
+ readonly whenToUse?: string;
537
+ readonly disableModelInvocation?: boolean;
538
+ readonly isSubSkill?: boolean;
539
+ readonly safe?: boolean;
540
+ readonly arguments?: string | readonly unknown[];
541
+ [key: string]: unknown;
542
+ };
543
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
544
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
545
+ readonly id: string;
546
+ readonly instructions?: string;
547
+ };
548
+ readonly mermaid?: string;
549
+ readonly d2?: string;
550
+ readonly productSpecific?: boolean;
551
+ readonly experimentalFlag?: string;
552
+ } | undefined;
553
+ renderSkillPrompt: (skill: /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
554
+ readonly name: string;
555
+ readonly description: string;
556
+ readonly path: string;
557
+ readonly dir: string;
558
+ readonly content: string;
559
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
560
+ readonly name?: string;
561
+ readonly description?: string;
562
+ readonly type?: string;
563
+ readonly whenToUse?: string;
564
+ readonly disableModelInvocation?: boolean;
565
+ readonly isSubSkill?: boolean;
566
+ readonly safe?: boolean;
567
+ readonly arguments?: string | readonly unknown[];
568
+ [key: string]: unknown;
569
+ };
570
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
571
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
572
+ readonly id: string;
573
+ readonly instructions?: string;
574
+ };
575
+ readonly mermaid?: string;
576
+ readonly d2?: string;
577
+ readonly productSpecific?: boolean;
578
+ readonly experimentalFlag?: string;
579
+ }, rawArgs: string, context?: {
580
+ readonly sessionId?: string;
581
+ }) => string;
582
+ listSkills: () => readonly /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
583
+ readonly name: string;
584
+ readonly description: string;
585
+ readonly path: string;
586
+ readonly dir: string;
587
+ readonly content: string;
588
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
589
+ readonly name?: string;
590
+ readonly description?: string;
591
+ readonly type?: string;
592
+ readonly whenToUse?: string;
593
+ readonly disableModelInvocation?: boolean;
594
+ readonly isSubSkill?: boolean;
595
+ readonly safe?: boolean;
596
+ readonly arguments?: string | readonly unknown[];
597
+ [key: string]: unknown;
598
+ };
599
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
600
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
601
+ readonly id: string;
602
+ readonly instructions?: string;
603
+ };
604
+ readonly mermaid?: string;
605
+ readonly d2?: string;
606
+ readonly productSpecific?: boolean;
607
+ readonly experimentalFlag?: string;
608
+ }[];
609
+ listInvocableSkills: () => readonly /* SkillDefinition — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
610
+ readonly name: string;
611
+ readonly description: string;
612
+ readonly path: string;
613
+ readonly dir: string;
614
+ readonly content: string;
615
+ readonly metadata: /* SkillMetadata — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
616
+ readonly name?: string;
617
+ readonly description?: string;
618
+ readonly type?: string;
619
+ readonly whenToUse?: string;
620
+ readonly disableModelInvocation?: boolean;
621
+ readonly isSubSkill?: boolean;
622
+ readonly safe?: boolean;
623
+ readonly arguments?: string | readonly unknown[];
624
+ [key: string]: unknown;
625
+ };
626
+ readonly source: /* SkillSource — packages/agent-core-v2/src/features/skill/catalog/types.ts */ 'project' | 'user' | 'extra' | 'builtin';
627
+ readonly plugin?: /* SkillPluginContext — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
628
+ readonly id: string;
629
+ readonly instructions?: string;
630
+ };
631
+ readonly mermaid?: string;
632
+ readonly d2?: string;
633
+ readonly productSpecific?: boolean;
634
+ readonly experimentalFlag?: string;
635
+ }[];
636
+ getSkillRoots: () => readonly string[];
637
+ getSkippedByPolicy: () => readonly /* SkippedSkill — packages/agent-core-v2/src/features/skill/catalog/types.ts */ {
638
+ readonly path: string;
639
+ readonly type: string;
640
+ readonly reason: string;
641
+ }[];
642
+ getKimiSkillsDescription: () => string;
643
+ getModelSkillListing: () => string;
644
+ };
645
+ // src/session/sessionActivity/sessionActivityService.ts
646
+ 'sessionActivity.current': /* SessionActivityState — packages/agent-core-v2/src/session/sessionActivity/sessionActivity.ts */ {
647
+ readonly busy: boolean;
648
+ readonly mainTurnActive: boolean;
649
+ readonly pendingInteraction: /* SessionPendingInteraction — packages/agent-core-v2/src/session/sessionActivity/sessionActivity.ts */ 'none' | 'approval' | 'question';
650
+ readonly lastTurnReason?: 'completed' | 'cancelled' | 'failed';
651
+ };
652
+ 'sessionActivity.folds': Map<string, /* AgentWorkFold — packages/agent-core-v2/src/session/sessionActivity/sessionActivityService.ts */ {
653
+ turnActive: boolean;
654
+ background: ReadonlySet<string>;
655
+ compacting: boolean;
656
+ lastTurnReason?: 'completed' | 'cancelled' | 'failed';
657
+ }>;
658
+ // src/session/sessionLog/sessionLogService.ts
659
+ 'sessionLog.rootLevel': /* LogLevelState — packages/agent-core-v2/src/_base/log/logService.ts */ {
660
+ level: /* LogLevel — packages/agent-core-v2/src/_base/log/log.ts */ 'info' | 'off' | 'error' | 'warn' | 'debug';
661
+ };
662
+ // src/session/sessionMetadata/sessionMetadataService.ts
663
+ 'sessionMetadata.data': /* SessionMeta — packages/agent-core-v2/src/session/sessionMetadata/sessionMetadata.ts */ {
664
+ readonly id: string;
665
+ readonly version?: number;
666
+ readonly title?: string;
667
+ readonly titleKind?: 'replaceable' | 'generated' | 'custom';
668
+ readonly lastPrompt?: string;
669
+ readonly createdAt: number;
670
+ readonly updatedAt: number;
671
+ readonly archived: boolean;
672
+ readonly archivedAt?: number;
673
+ readonly cwd?: string;
674
+ readonly forkedFrom?: string;
675
+ readonly agents?: Readonly<Record<string, /* AgentMeta — packages/agent-core-v2/src/session/sessionMetadata/sessionMetadata.ts */ {
676
+ readonly homedir?: string;
677
+ readonly type?: 'main' | 'sub' | 'independent';
678
+ readonly parentAgentId?: string | null;
679
+ readonly forkedFrom?: string;
680
+ readonly labels?: Readonly<Record<string, string>>;
681
+ readonly swarmItem?: string;
682
+ }>>;
683
+ readonly custom?: Record<string, unknown>;
684
+ readonly lastTurnReason?: 'completed' | 'cancelled' | 'failed';
685
+ } | undefined;
686
+ // src/session/sessionToolPolicy/sessionToolPolicyService.ts
687
+ 'sessionToolPolicy.state': /* SessionToolPolicyState — packages/agent-core-v2/src/session/sessionToolPolicy/sessionToolPolicyService.ts */ {
688
+ readonly disabledTools: readonly string[];
689
+ };
690
+ // src/session/workspaceContext/workspaceContextService.ts
691
+ 'workspaceContext.additionalDirs': string[];
692
+ 'workspaceContext.workDir': string;
693
+ }
694
+
695
+ export type SessionStateKey = keyof SessionStateSnapshot;
696
+
697
+ /** Agent-scope keys registered into IAgentStateService. */
698
+ export interface AgentStateSnapshot {
699
+ // src/agent/agentsMdReminder/agentsMdReminderService.ts
700
+ 'agentsMdReminder.cwd': string | undefined;
701
+ 'agentsMdReminder.known': Set<string>;
702
+ 'agentsMdReminder.seeded': boolean;
703
+ // src/agent/contextMemory/contextOps.ts
704
+ // replayable · durable · undoable — folds: ContextAppendMessage, ContextAppendLoopEvent, ContextClear, ContextApplyCompaction
705
+ 'contextMemory': (/* ContextMessage — packages/agent-core-v2/src/agent/contextMemory/types.ts */ /* Message — packages/agent-core-v2/src/llm-adapter/contract/message.ts */ {
706
+ readonly role: /* Role — packages/agent-core-v2/src/human/llm/message.ts */ 'user' | 'system' | 'assistant' | 'tool';
707
+ readonly name?: string;
708
+ readonly content: (/* ContentPart — packages/agent-core-v2/src/human/llm/message.ts */ /* TextPart — packages/agent-core-v2/src/human/llm/message.ts */ {
709
+ type: 'text';
710
+ text: string;
711
+ } | /* ThinkPart — packages/agent-core-v2/src/human/llm/message.ts */ {
712
+ type: 'think';
713
+ think: string;
714
+ encrypted?: string;
715
+ detailsIndex?: number;
716
+ hidden?: boolean;
717
+ } | /* ImageURLPart — packages/agent-core-v2/src/human/llm/message.ts */ {
718
+ type: 'image_url';
719
+ imageUrl: {
720
+ url: string;
721
+ id?: string;
722
+ name?: string;
723
+ };
724
+ } | /* AudioURLPart — packages/agent-core-v2/src/human/llm/message.ts */ {
725
+ type: 'audio_url';
726
+ audioUrl: {
727
+ url: string;
728
+ id?: string;
729
+ };
730
+ } | /* VideoURLPart — packages/agent-core-v2/src/human/llm/message.ts */ {
731
+ type: 'video_url';
732
+ videoUrl: {
733
+ url: string;
734
+ id?: string;
735
+ name?: string;
736
+ };
737
+ })[];
738
+ readonly toolCalls: /* ToolCall — packages/agent-core-v2/src/human/llm/message.ts */ {
739
+ type: 'function';
740
+ id: string;
741
+ name: string;
742
+ arguments: string | null;
743
+ extras?: Record<string, unknown>;
744
+ rawId?: string;
745
+ _streamIndex?: string | number;
746
+ }[];
747
+ readonly toolCallId?: string;
748
+ readonly partial?: boolean;
749
+ readonly tools?: readonly /* ToolDescription — packages/agent-core-v2/src/human/llm/message.ts */ {
750
+ name: string;
751
+ description: string;
752
+ parameters: Record<string, unknown>;
753
+ deferred?: true;
754
+ }[];
755
+ } & {
756
+ readonly id?: string;
757
+ readonly providerMessageId?: string;
758
+ readonly origin?: /* UserPromptOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
759
+ readonly kind: 'user';
760
+ readonly clientMetadata?: readonly Readonly<Record<string, unknown>>[];
761
+ readonly skillActivations?: readonly /* BundledSkillActivation — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
762
+ readonly activationId: string;
763
+ readonly skillName: string;
764
+ readonly skillArgs?: string;
765
+ readonly skillType?: string;
766
+ readonly skillPath?: string;
767
+ readonly skillSource?: 'project' | 'user' | 'extra' | 'builtin';
768
+ }[];
769
+ readonly attachments?: readonly /* PromptFileAttachment — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
770
+ readonly name: string;
771
+ readonly mediaType: string;
772
+ readonly size: number;
773
+ readonly path: string;
774
+ }[];
775
+ } | /* SkillActivationOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
776
+ readonly kind: 'skill_activation';
777
+ readonly clientMetadata?: readonly Readonly<Record<string, unknown>>[];
778
+ readonly activationId: string;
779
+ readonly skillName: string;
780
+ readonly skillArgs?: string;
781
+ readonly trigger: 'user-slash' | 'model-tool' | 'nested-skill';
782
+ readonly skillType?: string;
783
+ readonly skillPath?: string;
784
+ readonly skillSource?: 'project' | 'user' | 'extra' | 'builtin';
785
+ readonly attachments?: readonly /* PromptFileAttachment — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
786
+ readonly name: string;
787
+ readonly mediaType: string;
788
+ readonly size: number;
789
+ readonly path: string;
790
+ }[];
791
+ } | /* PluginCommandOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
792
+ readonly kind: 'plugin_command';
793
+ readonly activationId: string;
794
+ readonly pluginId: string;
795
+ readonly commandName: string;
796
+ readonly commandArgs?: string;
797
+ readonly trigger: 'user-slash';
798
+ } | /* InjectionOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
799
+ readonly kind: 'injection';
800
+ readonly variant: string;
801
+ readonly ownerPromptId?: string;
802
+ readonly disclosure?: unknown;
803
+ } | /* ShellCommandOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
804
+ readonly kind: 'shell_command';
805
+ readonly phase: 'input' | 'output';
806
+ readonly isError?: boolean;
807
+ } | /* CompactionSummaryOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
808
+ readonly kind: 'compaction_summary';
809
+ } | /* SystemTriggerOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
810
+ readonly kind: 'system_trigger';
811
+ readonly name: string;
812
+ } | /* TaskOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
813
+ readonly kind: 'task';
814
+ readonly taskId: string;
815
+ readonly status: /* AgentTaskStatus — packages/agent-core-v2/src/agent/task/types.ts */ 'completed' | 'failed' | 'running' | 'timed_out' | 'killed' | 'lost';
816
+ readonly notificationId: string;
817
+ } | /* CronJobOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
818
+ readonly kind: 'cron_job';
819
+ readonly jobId: string;
820
+ readonly cron: string;
821
+ readonly recurring: boolean;
822
+ readonly coalescedCount: number;
823
+ readonly stale: boolean;
824
+ } | /* CronMissedOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
825
+ readonly kind: 'cron_missed';
826
+ readonly count: number;
827
+ } | /* HookResultOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
828
+ readonly kind: 'hook_result';
829
+ readonly event: string;
830
+ readonly blocked?: boolean;
831
+ } | /* RetryOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
832
+ readonly kind: 'retry';
833
+ readonly trigger?: string;
834
+ };
835
+ readonly isError?: boolean;
836
+ toolCallDisplays?: Record<string, /* ToolInputDisplay — packages/agent-core-v2/src/tool/toolInputDisplay.ts */ {
837
+ kind: 'command';
838
+ command: string;
839
+ cwd?: string;
840
+ description?: string;
841
+ language?: 'bash';
842
+ } | {
843
+ kind: 'file_io';
844
+ operation: 'read' | 'write' | 'edit' | 'glob' | 'grep';
845
+ path: string;
846
+ detail?: string;
847
+ content?: string;
848
+ before?: string;
849
+ after?: string;
850
+ } | {
851
+ kind: 'diff';
852
+ path: string;
853
+ before: string;
854
+ after: string;
855
+ hunks?: number;
856
+ } | {
857
+ kind: 'search';
858
+ query: string;
859
+ scope?: string;
860
+ } | {
861
+ kind: 'url_fetch';
862
+ url: string;
863
+ method?: string;
864
+ } | {
865
+ kind: 'agent_call';
866
+ agent_name: string;
867
+ prompt: string;
868
+ background?: boolean;
869
+ } | {
870
+ kind: 'skill_call';
871
+ skill_name: string;
872
+ args?: string;
873
+ } | {
874
+ kind: 'todo_list';
875
+ items: {
876
+ title: string;
877
+ status: string;
878
+ }[];
879
+ } | {
880
+ kind: 'task';
881
+ task_id: string;
882
+ status: string;
883
+ description: string;
884
+ task_kind?: string;
885
+ } | {
886
+ kind: 'task_stop';
887
+ task_id: string;
888
+ task_description: string;
889
+ } | {
890
+ kind: 'plan_review';
891
+ plan: string;
892
+ path?: string;
893
+ options?: readonly {
894
+ label: string;
895
+ description: string;
896
+ }[];
897
+ } | {
898
+ kind: 'goal_start';
899
+ objective: string;
900
+ completionCriterion?: string;
901
+ mode: 'manual' | 'yolo';
902
+ } | {
903
+ kind: 'generic';
904
+ summary: string;
905
+ detail?: unknown;
906
+ }>;
907
+ readonly note?: string;
908
+ })[];
909
+ // src/agent/contextProjector/contextProjectorService.ts
910
+ 'contextProjector.lastRepairSignature': string | null;
911
+ // src/agent/fullCompaction/compactionOps.ts
912
+ // replayable · durable — folds: FullCompactionBegin, FullCompactionCancel, FullCompactionComplete
913
+ 'fullCompaction': /* CompactionState — packages/agent-core-v2/src/agent/fullCompaction/compactionOps.ts */ {
914
+ readonly phase: /* CompactionPhase — packages/agent-core-v2/src/agent/fullCompaction/compactionOps.ts */ 'completed' | 'cancelled' | 'running' | 'idle';
915
+ };
916
+ // replayable · durable — folds: ContextApplyCompaction, ContextClear
917
+ 'fullCompaction.wireRanges': readonly /* WireLineRange — packages/agent-core-v2/src/wire/record.ts */ {
918
+ readonly start: number;
919
+ readonly end: number;
920
+ }[];
921
+ // src/agent/fullCompaction/fullCompactionService.ts
922
+ 'fullCompaction.activeTurnId': number | undefined;
923
+ 'fullCompaction.compactionCountInTurn': number;
924
+ 'fullCompaction.consecutiveOverflowCompactions': number;
925
+ 'fullCompaction.lastCompactedTokenCount': number | null;
926
+ 'fullCompaction.observedMaxContextTokensByModel': Map<string, number>;
927
+ // src/agent/interruptionReminder/interruptionReminderOps.ts
928
+ // replayable · durable — folds: InterruptionReminderRecorded
929
+ 'interruptionReminder': null;
930
+ // src/agent/llmRequester/llmRequestOps.ts
931
+ // replayable · durable — folds: LlmToolsSnapshot, LlmRequest
932
+ 'llm.requestTrace': /* LlmRequestTraceState — packages/agent-core-v2/src/agent/llmRequester/llmRequestOps.ts */ {
933
+ readonly seenToolsHashes: readonly string[];
934
+ };
935
+ // src/agent/llmRequester/llmRequesterService.ts
936
+ 'llmRequester.emittedThinkingEffortWarnings': Set<string>;
937
+ 'llmRequester.lastConfigLogSignature': string | undefined;
938
+ 'llmRequester.mediaDegradedTurns': Set<number>;
939
+ 'llmRequester.mediaStrippedTurns': Map<number, /* MediaStripSnapshot — packages/agent-core-v2/src/agent/contextProjector/contextProjector.ts */ {
940
+ readonly "__@mediaStripSnapshotBrand": undefined;
941
+ }>;
942
+ 'llmRequester.turnConfigs': Map<number, /* TurnRequestConfig — packages/agent-core-v2/src/agent/llmRequester/llmRequesterService.ts */ {
943
+ readonly resolved: /* ProfileModelContext — packages/agent-core-v2/src/agent/profile/profile.ts */ {
944
+ readonly modelAlias: string;
945
+ readonly modelCapabilities: /* ModelCapability — packages/agent-core-v2/src/llm-adapter/contract/capability.ts */ {
946
+ readonly image_in: boolean;
947
+ readonly video_in: boolean;
948
+ readonly audio_in: boolean;
949
+ readonly thinking: boolean;
950
+ readonly tool_use: boolean;
951
+ readonly max_context_tokens: number;
952
+ readonly max_input_tokens?: number;
953
+ readonly dynamically_loaded_tools?: boolean;
954
+ };
955
+ readonly maxOutputSize: number | undefined;
956
+ readonly alwaysThinking: boolean | undefined;
957
+ readonly thinkingLevel: /* ThinkingEffort — packages/agent-core-v2/src/human/llm/thinking.ts */ 'off' | 'on' | (string & {});
958
+ readonly reservedContextSize: number | undefined;
959
+ readonly compactionTriggerRatio: number | undefined;
960
+ readonly compactionMaxAttempts: number | undefined;
961
+ };
962
+ readonly params: /* ModelRequestParams — packages/agent-core-v2/src/llm-adapter/model/model-requester.ts */ {
963
+ readonly cacheKey?: string;
964
+ readonly sampling?: /* SamplingOptions — packages/agent-core-v2/src/llm-adapter/model/model-requester.ts */ {
965
+ readonly temperature?: number;
966
+ readonly topP?: number;
967
+ };
968
+ readonly thinkingEffort?: 'off' | 'on' | (string & {});
969
+ readonly thinkingKeep?: string;
970
+ readonly maxCompletionTokens?: number;
971
+ readonly usedContextTokens?: number;
972
+ readonly maxContextTokens?: number;
973
+ readonly onTraceId?: (traceId: string | null) => void;
974
+ };
975
+ readonly systemPrompt: string;
976
+ }>;
977
+ // src/agent/loop/loopService.ts
978
+ 'loop.disposing': boolean;
979
+ 'loop.lastRequestTraceId': string | undefined;
980
+ // src/agent/loop/turnOps.ts
981
+ // replayable · durable — folds: ContextAppendLoopEvent, TurnPrompt, TurnSteer, ContextUndo, ContextApplyCompaction, ContextClear, TurnCancel, TurnEnded
982
+ 'turn': /* TurnModelState — packages/agent-core-v2/src/agent/loop/turnOps.ts */ {
983
+ readonly nextTurnId: number;
984
+ readonly cancelledTurnIds: readonly number[];
985
+ readonly anchorTurnIds: readonly number[];
986
+ readonly lastEnded?: {
987
+ readonly turnId: number;
988
+ readonly reason: 'completed' | 'cancelled' | 'failed' | 'blocked';
989
+ readonly durationMs?: number;
990
+ };
991
+ };
992
+ // src/agent/mcp/mcpDiscoveryOps.ts
993
+ // replayable · durable — folds: McpToolsDiscovered
994
+ 'mcp.discovery': /* McpDiscoveryState — packages/agent-core-v2/src/agent/mcp/mcpDiscoveryOps.ts */ {
995
+ readonly seen: readonly string[];
996
+ };
997
+ // src/agent/mcp/mcpService.ts
998
+ 'mcp.discoveryWritesReady': boolean;
999
+ 'mcp.mcpToolsByServer': Map<string, string[]>;
1000
+ // src/agent/media/mediaResolverService.ts
1001
+ 'media.budgetDropped': Set<string>;
1002
+ 'media.resolved': Map<string, /* ContentPart — packages/agent-core-v2/src/human/llm/message.ts */ /* TextPart — packages/agent-core-v2/src/human/llm/message.ts */ {
1003
+ type: 'text';
1004
+ text: string;
1005
+ } | /* ThinkPart — packages/agent-core-v2/src/human/llm/message.ts */ {
1006
+ type: 'think';
1007
+ think: string;
1008
+ encrypted?: string;
1009
+ detailsIndex?: number;
1010
+ hidden?: boolean;
1011
+ } | /* ImageURLPart — packages/agent-core-v2/src/human/llm/message.ts */ {
1012
+ type: 'image_url';
1013
+ imageUrl: {
1014
+ url: string;
1015
+ id?: string;
1016
+ name?: string;
1017
+ };
1018
+ } | /* AudioURLPart — packages/agent-core-v2/src/human/llm/message.ts */ {
1019
+ type: 'audio_url';
1020
+ audioUrl: {
1021
+ url: string;
1022
+ id?: string;
1023
+ };
1024
+ } | /* VideoURLPart — packages/agent-core-v2/src/human/llm/message.ts */ {
1025
+ type: 'video_url';
1026
+ videoUrl: {
1027
+ url: string;
1028
+ id?: string;
1029
+ name?: string;
1030
+ };
1031
+ }>;
1032
+ // src/agent/media/mediaToolsRegistrar.ts
1033
+ 'media.registeredKey': string | undefined;
1034
+ // src/agent/permissionMode/injection/permissionModeInjection.ts
1035
+ 'permissionMode.lastMode': 'manual' | 'yolo' | 'auto' | undefined;
1036
+ // src/agent/permissionMode/permissionModeOps.ts
1037
+ // replayable · durable — folds: PermissionSetMode
1038
+ 'permissionMode': /* PermissionMode — packages/agent-core-v2/src/agent/permissionPolicy/types.ts */ 'manual' | 'yolo' | 'auto';
1039
+ // replayable · durable — folds: PermissionSetMode
1040
+ 'permissionMode.configured': boolean;
1041
+ // src/agent/permissionRules/permissionRulesOps.ts
1042
+ // replayable · durable — folds: PermissionRulesAdd, PermissionRecordApprovalResult
1043
+ 'permissionRules': /* PermissionRulesModelState — packages/agent-core-v2/src/agent/permissionRules/permissionRulesOps.ts */ {
1044
+ readonly rules: readonly /* PermissionRule — packages/agent-core-v2/src/agent/permissionRules/permissionRules.ts */ {
1045
+ readonly decision: /* PermissionRuleDecision — packages/agent-core-v2/src/agent/permissionRules/permissionRules.ts */ 'allow' | 'deny' | 'ask';
1046
+ readonly scope: /* PermissionRuleScope — packages/agent-core-v2/src/agent/permissionRules/permissionRules.ts */ 'project' | 'user' | 'turn-override' | 'session-runtime';
1047
+ readonly pattern: string;
1048
+ readonly reason?: string;
1049
+ }[];
1050
+ readonly sessionApprovalRulePatterns: readonly string[];
1051
+ };
1052
+ // src/agent/plugin/agentPluginOps.ts
1053
+ // replayable · durable — folds: PluginSessionStartEvent
1054
+ 'pluginSessionStartSnapshot': /* PluginSessionStartSnapshotState — packages/agent-core-v2/src/agent/plugin/agentPluginOps.ts */ {
1055
+ readonly initialized: boolean;
1056
+ readonly content?: string;
1057
+ };
1058
+ // src/agent/plugin/agentPluginService.ts
1059
+ 'agentPlugin.sessionStartRefreshPending': boolean;
1060
+ // src/agent/profile/profileOps.ts
1061
+ // replayable · durable — folds: ProfileBind, ConfigUpdate
1062
+ 'profile': /* ProfileModelState — packages/agent-core-v2/src/agent/profile/profileOps.ts */ {
1063
+ readonly modelAlias?: string;
1064
+ readonly profileName?: string;
1065
+ readonly thinkingLevel: string;
1066
+ readonly systemPrompt: string;
1067
+ readonly environmentDisclosure?: /* EnvironmentDisclosureSnapshot — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ {
1068
+ readonly cwd: string;
1069
+ };
1070
+ readonly renderGeneration: number;
1071
+ readonly agentsMdPaths?: readonly string[];
1072
+ readonly disallowedTools?: readonly string[];
1073
+ readonly subagents?: readonly string[];
1074
+ };
1075
+ // replayable · durable — folds: ToolsSetActiveTools, ToolsResetActiveTools, ProfileBind
1076
+ 'profile.activeTools': /* ActiveToolsState — packages/agent-core-v2/src/agent/profile/profileOps.ts */ readonly string[] | undefined;
1077
+ // src/agent/profile/profileService.ts
1078
+ 'profile.activeToolNamesOverlay': readonly string[] | undefined;
1079
+ 'profile.agentsMdWarning': string | undefined;
1080
+ 'profile.emittedPluginBudgetWarnings': Set<string>;
1081
+ 'profile.emittedThinkingEffortWarnings': Set<string>;
1082
+ 'profile.emittedToolPatternWarnings': Set<string>;
1083
+ // src/agent/runtimeBinding/runtimeBindingOps.ts
1084
+ // replayable · durable — folds: RuntimeSetBinding
1085
+ 'runtimeBinding': /* RuntimeBinding — packages/agent-core-v2/src/runtime/runtime.ts */ {
1086
+ readonly workspaceId: string;
1087
+ readonly runtimeId: string;
1088
+ } | undefined;
1089
+ // src/agent/runtimeBinding/runtimeBindingService.ts
1090
+ 'runtime.binding': /* RuntimeBinding — packages/agent-core-v2/src/runtime/runtime.ts */ {
1091
+ readonly workspaceId: string;
1092
+ readonly runtimeId: string;
1093
+ };
1094
+ // src/agent/shellCommand/shellCommandService.ts
1095
+ 'shellCommand.tasks': Map<string, string>;
1096
+ // src/agent/task/taskOps.ts
1097
+ // replayable · durable — folds: TaskStarted, TaskTerminated
1098
+ 'task': /* TaskModelState — packages/agent-core-v2/src/agent/task/taskOps.ts */ Map<string, /* AgentTaskInfo — packages/agent-core-v2/src/agent/task/types.ts */ /* QuestionTaskInfo — packages/agent-core-v2/src/agent/tools/ask-user-question/question-background-task.ts */ {
1099
+ readonly kind: 'question';
1100
+ readonly questionCount: number;
1101
+ readonly toolCallId?: string;
1102
+ readonly taskId: string;
1103
+ readonly description: string;
1104
+ readonly status: /* AgentTaskStatus — packages/agent-core-v2/src/agent/task/types.ts */ 'completed' | 'failed' | 'running' | 'timed_out' | 'killed' | 'lost';
1105
+ readonly detached?: boolean;
1106
+ readonly startedAt: number;
1107
+ readonly endedAt: number | null;
1108
+ readonly stopReason?: string;
1109
+ readonly terminalNotificationSuppressed?: boolean;
1110
+ readonly resumeReminded?: boolean;
1111
+ readonly timeoutMs?: number;
1112
+ } | /* SubagentTaskInfo — packages/agent-core-v2/src/agent/tools/agent/subagent-task.ts */ {
1113
+ readonly kind: 'agent';
1114
+ readonly agentId?: string;
1115
+ readonly subagentType?: string;
1116
+ readonly parentToolCallId?: string;
1117
+ readonly model?: string;
1118
+ readonly thinkingEffort?: string;
1119
+ readonly stopCode?: string;
1120
+ readonly taskId: string;
1121
+ readonly description: string;
1122
+ readonly status: /* AgentTaskStatus — packages/agent-core-v2/src/agent/task/types.ts */ 'completed' | 'failed' | 'running' | 'timed_out' | 'killed' | 'lost';
1123
+ readonly detached?: boolean;
1124
+ readonly startedAt: number;
1125
+ readonly endedAt: number | null;
1126
+ readonly stopReason?: string;
1127
+ readonly terminalNotificationSuppressed?: boolean;
1128
+ readonly resumeReminded?: boolean;
1129
+ readonly timeoutMs?: number;
1130
+ } | /* ProcessTaskInfo — packages/agent-core-v2/src/agent/tools/os/bash/process-task.ts */ {
1131
+ readonly kind: 'process';
1132
+ readonly command: string;
1133
+ readonly pid: number;
1134
+ readonly exitCode: number | null;
1135
+ readonly parentToolCallId?: string;
1136
+ readonly taskId: string;
1137
+ readonly description: string;
1138
+ readonly status: /* AgentTaskStatus — packages/agent-core-v2/src/agent/task/types.ts */ 'completed' | 'failed' | 'running' | 'timed_out' | 'killed' | 'lost';
1139
+ readonly detached?: boolean;
1140
+ readonly startedAt: number;
1141
+ readonly endedAt: number | null;
1142
+ readonly stopReason?: string;
1143
+ readonly terminalNotificationSuppressed?: boolean;
1144
+ readonly resumeReminded?: boolean;
1145
+ readonly timeoutMs?: number;
1146
+ }>;
1147
+ // src/agent/task/taskService.ts
1148
+ 'task.activeTaskReminderPending': boolean;
1149
+ 'task.deliveredNotificationKeys': Set<string>;
1150
+ 'task.ghosts': Map<string, /* AgentTaskInfo — packages/agent-core-v2/src/agent/task/types.ts */ /* QuestionTaskInfo — packages/agent-core-v2/src/agent/tools/ask-user-question/question-background-task.ts */ {
1151
+ readonly kind: 'question';
1152
+ readonly questionCount: number;
1153
+ readonly toolCallId?: string;
1154
+ readonly taskId: string;
1155
+ readonly description: string;
1156
+ readonly status: /* AgentTaskStatus — packages/agent-core-v2/src/agent/task/types.ts */ 'completed' | 'failed' | 'running' | 'timed_out' | 'killed' | 'lost';
1157
+ readonly detached?: boolean;
1158
+ readonly startedAt: number;
1159
+ readonly endedAt: number | null;
1160
+ readonly stopReason?: string;
1161
+ readonly terminalNotificationSuppressed?: boolean;
1162
+ readonly resumeReminded?: boolean;
1163
+ readonly timeoutMs?: number;
1164
+ } | /* SubagentTaskInfo — packages/agent-core-v2/src/agent/tools/agent/subagent-task.ts */ {
1165
+ readonly kind: 'agent';
1166
+ readonly agentId?: string;
1167
+ readonly subagentType?: string;
1168
+ readonly parentToolCallId?: string;
1169
+ readonly model?: string;
1170
+ readonly thinkingEffort?: string;
1171
+ readonly stopCode?: string;
1172
+ readonly taskId: string;
1173
+ readonly description: string;
1174
+ readonly status: /* AgentTaskStatus — packages/agent-core-v2/src/agent/task/types.ts */ 'completed' | 'failed' | 'running' | 'timed_out' | 'killed' | 'lost';
1175
+ readonly detached?: boolean;
1176
+ readonly startedAt: number;
1177
+ readonly endedAt: number | null;
1178
+ readonly stopReason?: string;
1179
+ readonly terminalNotificationSuppressed?: boolean;
1180
+ readonly resumeReminded?: boolean;
1181
+ readonly timeoutMs?: number;
1182
+ } | /* ProcessTaskInfo — packages/agent-core-v2/src/agent/tools/os/bash/process-task.ts */ {
1183
+ readonly kind: 'process';
1184
+ readonly command: string;
1185
+ readonly pid: number;
1186
+ readonly exitCode: number | null;
1187
+ readonly parentToolCallId?: string;
1188
+ readonly taskId: string;
1189
+ readonly description: string;
1190
+ readonly status: /* AgentTaskStatus — packages/agent-core-v2/src/agent/task/types.ts */ 'completed' | 'failed' | 'running' | 'timed_out' | 'killed' | 'lost';
1191
+ readonly detached?: boolean;
1192
+ readonly startedAt: number;
1193
+ readonly endedAt: number | null;
1194
+ readonly stopReason?: string;
1195
+ readonly terminalNotificationSuppressed?: boolean;
1196
+ readonly resumeReminded?: boolean;
1197
+ readonly timeoutMs?: number;
1198
+ }>;
1199
+ // replayable · durable · undoable — folds: ContextAppendMessage, TaskWaitDelivered
1200
+ 'task.notificationDelivery': readonly string[];
1201
+ 'task.scheduledNotificationKeys': Set<string>;
1202
+ // src/agent/toolDedupe/toolDedupeService.ts
1203
+ 'toolDedupe.activeStep': number;
1204
+ 'toolDedupe.activeTurnId': number | undefined;
1205
+ 'toolDedupe.callKeyByCallId': Map<string, string>;
1206
+ 'toolDedupe.consecutiveCount': number;
1207
+ 'toolDedupe.consecutiveKey': string | null;
1208
+ 'toolDedupe.handoffPhase': /* HandoffPhase — packages/agent-core-v2/src/agent/toolDedupe/toolDedupeService.ts */ 'idle' | 'active' | 'pending' | 'done';
1209
+ 'toolDedupe.originalCallIndex': Map<string, number>;
1210
+ 'toolDedupe.stepCalls': string[];
1211
+ 'toolDedupe.syntheticCallIds': Set<string>;
1212
+ 'toolDedupe.turnCallRecords': Map<string, /* TurnCallRecord — packages/agent-core-v2/src/agent/toolDedupe/toolDedupeService.ts */ {
1213
+ count: number;
1214
+ lastStep: number;
1215
+ }>;
1216
+ 'toolDedupe.turnRepeatCount': number;
1217
+ // src/agent/toolExecutor/toolExecutorService.ts
1218
+ 'toolExecutor.dupTypeTurnId': number | undefined;
1219
+ 'toolExecutor.toolCallDupTypes': Map<string, /* ToolCallDupType — packages/agent-core-v2/src/agent/toolExecutor/toolExecutor.ts */ 'same_step' | 'cross_step'>;
1220
+ // src/agent/toolSelect/toolSelectService.ts
1221
+ 'toolSelect.pendingLoaded': Set<string>;
1222
+ // src/agent/userTool/userToolOps.ts
1223
+ // replayable · durable — folds: ToolsRegisterUserTool, ToolsUnregisterUserTool
1224
+ 'userTool': /* UserToolModelState — packages/agent-core-v2/src/agent/userTool/userToolOps.ts */ Map<string, /* UserToolRegistration — packages/agent-core-v2/src/agent/userTool/userTool.ts */ {
1225
+ readonly name: string;
1226
+ readonly description: string;
1227
+ readonly parameters: Record<string, unknown>;
1228
+ readonly disclosure?: 'deferred' | 'inline';
1229
+ }>;
1230
+ // src/features/externalHooks/agent/agentExternalHooksService.ts
1231
+ 'externalHooks.stopHookContinuationUsed': boolean;
1232
+ // src/features/fileHistory/fileHistoryOps.ts
1233
+ // replayable · durable — folds: FileHistoryCheckpointed, FileHistoryTracked
1234
+ 'fileHistory': /* FileHistoryState — packages/agent-core-v2/src/features/fileHistory/fileHistory.ts */ {
1235
+ readonly checkpoints: readonly /* FileHistoryCheckpointRecord — packages/agent-core-v2/src/features/fileHistory/fileHistory.ts */ {
1236
+ readonly turnId: number;
1237
+ readonly phase?: 'start' | 'end';
1238
+ readonly entries: Readonly<Record<string, /* FileBackupEntry — packages/agent-core-v2/src/features/fileHistory/fileHistory.ts */ {
1239
+ readonly key: string | null;
1240
+ readonly version: number;
1241
+ readonly contentHash?: string;
1242
+ readonly size?: number;
1243
+ readonly oversize?: boolean;
1244
+ readonly mtimeMs?: number;
1245
+ }>>;
1246
+ }[];
1247
+ readonly tracked: readonly string[];
1248
+ };
1249
+ // src/features/plan/injection/planModeInjection.ts
1250
+ 'plan.wasActive': boolean;
1251
+ // src/features/plan/planOps.ts
1252
+ // replayable · durable · undoable — folds: PlanModeEnter, PlanModeCancel, PlanModeExit, PlanRevision
1253
+ 'plan': /* PlanState — packages/agent-core-v2/src/features/plan/planOps.ts */ {
1254
+ readonly active: boolean;
1255
+ readonly id?: string;
1256
+ readonly revisionCount?: Readonly<Record<string, number>>;
1257
+ };
1258
+ // src/features/swarm/swarmOps.ts
1259
+ // replayable · durable — folds: SwarmModeEnter, SwarmModeExit
1260
+ 'swarm': 'tool' | 'task' | 'manual' | null;
1261
+ // src/features/tower/towerOps.ts
1262
+ // replayable · durable — folds: TowerModeEnter, TowerModeExit
1263
+ 'tower': boolean;
1264
+ // replayable · durable — folds: TowerModeEnter, TowerModeExit
1265
+ 'tower.base': string | null;
1266
+ // replayable · durable — folds: TowerModeEnter, TowerModeExit
1267
+ 'tower.owner': string | undefined;
1268
+ }
1269
+
1270
+ export type AgentStateKey = keyof AgentStateSnapshot;
packages/agent-core-v2/docs/wire-manifest.d.ts ADDED
@@ -0,0 +1,946 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Wire Protocol Manifest
2
+ //
3
+ // Generated by scripts/gen-wire-manifest.mts — do not edit by hand.
4
+ // Regenerate with: pnpm --filter @moonshot-ai/agent-core-v2 gen:wire-manifest
5
+ //
6
+ // protocol_version: "1.5" (migrations: 1.0 -> 1.1 -> 1.2 -> 1.3 -> 1.4 -> 1.5)
7
+ //
8
+ // One declaration per durable record type — an Event2 subclass declaring
9
+ // `static type` + `static durable = true` + `static schema` — drained from the
10
+ // runtime EVENT2_REGISTRY ("import = register"). Every payload declaration
11
+ // carries its record type in a `_name` field. Payload sketches use TypeScript
12
+ // type syntax; when a named type is expanded inline, its name appears as a doc
13
+ // comment (`/** ContextMessage */`). Bare type names (ContentPart,
14
+ // ContextMessage, …) refer to the real types in src/ — they are intentionally
15
+ // not resolved here. `// …` marks a capped field list. On disk (wire.jsonl)
16
+ // the journal opens with a metadata line {"type": "metadata",
17
+ // "protocol_version", "created_at"}; each record is {"type", ...payload,
18
+ // "time"} — object payloads spread at the top level.
19
+ //
20
+ // Every listed type is durable by construction — transient Event2 classes
21
+ // never enter EVENT2_REGISTRY, so there is no persisted flag. Declaration
22
+ // header lines: states (every state folding this record type on dispatch and
23
+ // replay; any state beyond the first is what the retired format listed as
24
+ // cross-reducers), blobs (the folding states whose blob codec offloads inline
25
+ // media to blob storage), owner (the source file declaring the class).
26
+
27
+ // Index (59 record types)
28
+ // config.update profile src/agent/profile/profileOps.ts
29
+ // context.append_loop_event contextMemory, turn src/agent/contextMemory/contextEvents.ts
30
+ // context.append_message contextMemory, plan, task.notificationDelivery src/agent/contextMemory/contextEvents.ts
31
+ // context.apply_compaction contextMemory, plan, task.notificationDelivery, turn src/agent/contextMemory/contextEvents.ts
32
+ // context.clear contextMemory, plan, task.notificationDelivery, turn src/agent/contextMemory/contextEvents.ts
33
+ // context.undo contextMemory, plan, task.notificationDelivery, turn src/agent/contextMemory/contextEvents.ts
34
+ // cron.add (none) src/features/cron/cronOps.ts
35
+ // cron.cursor (none) src/features/cron/cronOps.ts
36
+ // cron.delete (none) src/features/cron/cronOps.ts
37
+ // file_history.checkpoint fileHistory src/features/fileHistory/fileHistoryOps.ts
38
+ // file_history.tracked fileHistory src/features/fileHistory/fileHistoryOps.ts
39
+ // forked (none) src/features/goal/goalOps.ts
40
+ // full_compaction.begin fullCompaction src/agent/fullCompaction/compactionOps.ts
41
+ // full_compaction.cancel fullCompaction src/agent/fullCompaction/compactionOps.ts
42
+ // full_compaction.complete fullCompaction src/agent/fullCompaction/compactionOps.ts
43
+ // goal.clear (none) src/features/goal/goalOps.ts
44
+ // goal.create (none) src/features/goal/goalOps.ts
45
+ // goal.update (none) src/features/goal/goalOps.ts
46
+ // interaction.request (none) src/agent/interaction/interactionOps.ts
47
+ // interaction.resolved (none) src/agent/interaction/interactionOps.ts
48
+ // interruptionReminder.recorded interruptionReminder src/agent/interruptionReminder/interruptionReminderOps.ts
49
+ // llm.request llm.requestTrace src/agent/llmRequester/llmRequestOps.ts
50
+ // llm.tools_snapshot llm.requestTrace src/agent/llmRequester/llmRequestOps.ts
51
+ // mcp.tools_discovered mcp.discovery src/agent/mcp/mcpDiscoveryOps.ts
52
+ // permission.record_approval_result permissionRules src/agent/permissionRules/permissionRulesOps.ts
53
+ // permission.set_mode permissionMode, permissionMode.configured src/agent/permissionMode/permissionModeOps.ts
54
+ // plan_mode.cancel plan src/features/plan/planOps.ts
55
+ // plan_mode.enter plan src/features/plan/planOps.ts
56
+ // plan_mode.exit plan src/features/plan/planOps.ts
57
+ // plan.revision plan src/features/plan/planOps.ts
58
+ // plugin.session_start pluginSessionStartSnapshot src/agent/plugin/agentPluginOps.ts
59
+ // profile.bind profile, profile.activeTools src/agent/profile/profileOps.ts
60
+ // prompt.aborted (none) src/agent/prompt/promptEvents.ts
61
+ // prompt.completed (none) src/agent/prompt/promptEvents.ts
62
+ // prompt.steered (none) src/agent/prompt/promptEvents.ts
63
+ // runtime.set_binding runtimeBinding src/agent/runtimeBinding/runtimeBindingOps.ts
64
+ // swarm_mode.enter swarm src/features/swarm/swarmOps.ts
65
+ // swarm_mode.exit contextMemory, swarm src/features/swarm/swarmOps.ts
66
+ // task.started task src/agent/task/taskOps.ts
67
+ // task.terminated task src/agent/task/taskOps.ts
68
+ // task.waitDelivered task.notificationDelivery src/agent/task/taskOps.ts
69
+ // token_counting.measured (none) src/agent/tokenCounting/tokenCountingOps.ts
70
+ // token_counting.rebased (none) src/agent/tokenCounting/tokenCountingOps.ts
71
+ // token_counting.truncated (none) src/agent/tokenCounting/tokenCountingOps.ts
72
+ // token_counting.turn_recorded (none) src/agent/tokenCounting/tokenCountingOps.ts
73
+ // tools.register_user_tool userTool src/agent/userTool/userToolOps.ts
74
+ // tools.reset_active_tools profile.activeTools src/agent/profile/profileOps.ts
75
+ // tools.set_active_tools profile.activeTools src/agent/profile/profileOps.ts
76
+ // tools.unregister_user_tool userTool src/agent/userTool/userToolOps.ts
77
+ // tools.update_store (none) src/features/todo/todoOps.ts
78
+ // tower_mode.enter tower, tower.base, tower.owner src/features/tower/towerOps.ts
79
+ // tower_mode.exit tower, tower.base, tower.owner src/features/tower/towerOps.ts
80
+ // turn.cancel turn src/agent/loop/turnOps.ts
81
+ // turn.ended turn src/agent/loop/turnOps.ts
82
+ // turn.prompt turn src/agent/loop/turnOps.ts
83
+ // turn.steer turn src/agent/loop/turnOps.ts
84
+ // turn.step.interrupted (none) src/agent/loop/turnEvents.ts
85
+ // turn.step.retrying (none) src/agent/loop/turnEvents.ts
86
+ // usage.record (none) src/agent/usage/usageOps.ts
87
+
88
+ /**
89
+ * states: profile
90
+ * owner: src/agent/profile/profileOps.ts
91
+ */
92
+ interface ConfigUpdatePayload {
93
+ _name: 'config.update';
94
+ agentId: string;
95
+ modelAlias?: string;
96
+ profileName?: string;
97
+ thinkingEffort?: ThinkingEffort;
98
+ thinkingLevel?: ThinkingEffort;
99
+ systemPrompt?: string;
100
+ /** EnvironmentDisclosureSnapshot */
101
+ environmentDisclosure?: {
102
+ cwd: string;
103
+ };
104
+ renderGeneration?: number;
105
+ agentsMdPaths?: string[];
106
+ disallowedTools?: string[];
107
+ }
108
+
109
+ /**
110
+ * states: contextMemory, turn · blobs: contextMemory
111
+ * owner: src/agent/contextMemory/contextEvents.ts
112
+ */
113
+ interface ContextAppendLoopEventPayload {
114
+ _name: 'context.append_loop_event';
115
+ agentId: string;
116
+ /** LoopRecordedEvent */
117
+ event: 'step.begin' | 'step.end' | 'content.part' | 'tool.call' | 'tool.result';
118
+ }
119
+
120
+ /**
121
+ * states: contextMemory, plan, task.notificationDelivery · blobs: contextMemory
122
+ * owner: src/agent/contextMemory/contextEvents.ts
123
+ */
124
+ interface ContextAppendMessagePayload {
125
+ _name: 'context.append_message';
126
+ agentId: string;
127
+ /** ContextMessage */
128
+ message: {
129
+ role: Role;
130
+ name?: string;
131
+ content: ContentPart[];
132
+ toolCalls: ToolCall[];
133
+ toolCallId?: string;
134
+ partial?: boolean;
135
+ tools?: ToolDescription[];
136
+ id?: string;
137
+ providerMessageId?: string;
138
+ origin?: 'user' | 'skill_activation' | 'plugin_command' | 'injection' | 'shell_command' | 'compaction_summary' | 'system_trigger' | 'task' | 'cron_job' | 'cron_missed' | 'hook_result' | 'retry' | undefined;
139
+ isError?: boolean;
140
+ toolCallDisplays?: Record<string, ToolInputDisplay>;
141
+ note?: string;
142
+ };
143
+ }
144
+
145
+ /**
146
+ * states: contextMemory, plan, task.notificationDelivery, turn · blobs: contextMemory
147
+ * owner: src/agent/contextMemory/contextEvents.ts
148
+ * shared base: ...contextCompactionBaseShape
149
+ */
150
+ type ContextApplyCompactionPayload = { _name: 'context.apply_compaction'; } & ({ summary: string, compactedCount: number, contextSummary?: string } | { contextSummary: string, compactedCount: number, summary?: string } | { summary: ContextMessage, count: number, compactedCount?: number });
151
+
152
+ /**
153
+ * states: contextMemory, plan, task.notificationDelivery, turn · blobs: contextMemory
154
+ * owner: src/agent/contextMemory/contextEvents.ts
155
+ */
156
+ interface ContextClearPayload {
157
+ _name: 'context.clear';
158
+ agentId: string;
159
+ }
160
+
161
+ /**
162
+ * states: contextMemory, plan, task.notificationDelivery, turn · blobs: contextMemory
163
+ * owner: src/agent/contextMemory/contextEvents.ts
164
+ */
165
+ interface ContextUndoPayload {
166
+ _name: 'context.undo';
167
+ agentId: string;
168
+ count: number;
169
+ }
170
+
171
+ /**
172
+ * states: (none)
173
+ * owner: src/features/cron/cronOps.ts
174
+ */
175
+ interface CronAddPayload {
176
+ _name: 'cron.add';
177
+ task: {
178
+ id: string;
179
+ cron: string;
180
+ prompt: string;
181
+ createdAt: number;
182
+ recurring?: boolean;
183
+ lastFiredAt?: number;
184
+ tags?: Record<string, string>;
185
+ };
186
+ }
187
+
188
+ /**
189
+ * states: (none)
190
+ * owner: src/features/cron/cronOps.ts
191
+ */
192
+ interface CronCursorPayload {
193
+ _name: 'cron.cursor';
194
+ id: string;
195
+ lastFiredAt: number;
196
+ }
197
+
198
+ /**
199
+ * states: (none)
200
+ * owner: src/features/cron/cronOps.ts
201
+ */
202
+ interface CronDeletePayload {
203
+ _name: 'cron.delete';
204
+ ids: string[];
205
+ }
206
+
207
+ /**
208
+ * states: fileHistory
209
+ * owner: src/features/fileHistory/fileHistoryOps.ts
210
+ */
211
+ interface FileHistoryCheckpointPayload {
212
+ _name: 'file_history.checkpoint';
213
+ agentId: string;
214
+ turnId: number;
215
+ phase?: 'start' | 'end';
216
+ entries: Record<string, object>;
217
+ }
218
+
219
+ /**
220
+ * states: fileHistory
221
+ * owner: src/features/fileHistory/fileHistoryOps.ts
222
+ */
223
+ interface FileHistoryTrackedPayload {
224
+ _name: 'file_history.tracked';
225
+ agentId: string;
226
+ turnId: number;
227
+ path: string;
228
+ entry: {
229
+ key: string | null;
230
+ version: number;
231
+ contentHash?: string;
232
+ size?: number;
233
+ oversize?: boolean;
234
+ mtimeMs?: number;
235
+ };
236
+ }
237
+
238
+ /**
239
+ * states: (none)
240
+ * owner: src/features/goal/goalOps.ts
241
+ */
242
+ interface ForkedPayload {
243
+ _name: 'forked';
244
+ agentId: string;
245
+ }
246
+
247
+ /**
248
+ * states: fullCompaction
249
+ * owner: src/agent/fullCompaction/compactionOps.ts
250
+ */
251
+ interface FullCompactionBeginPayload {
252
+ _name: 'full_compaction.begin';
253
+ agentId: string;
254
+ instruction?: string;
255
+ /** CompactionSource */
256
+ source: 'manual' | 'auto';
257
+ }
258
+
259
+ /**
260
+ * states: fullCompaction
261
+ * owner: src/agent/fullCompaction/compactionOps.ts
262
+ */
263
+ interface FullCompactionCancelPayload {
264
+ _name: 'full_compaction.cancel';
265
+ agentId: string;
266
+ }
267
+
268
+ /**
269
+ * states: fullCompaction
270
+ * owner: src/agent/fullCompaction/compactionOps.ts
271
+ */
272
+ interface FullCompactionCompletePayload {
273
+ _name: 'full_compaction.complete';
274
+ agentId: string;
275
+ }
276
+
277
+ /**
278
+ * states: (none)
279
+ * owner: src/features/goal/goalOps.ts
280
+ */
281
+ interface GoalClearPayload {
282
+ _name: 'goal.clear';
283
+ agentId: string;
284
+ }
285
+
286
+ /**
287
+ * states: (none)
288
+ * owner: src/features/goal/goalOps.ts
289
+ */
290
+ interface GoalCreatePayload {
291
+ _name: 'goal.create';
292
+ agentId: string;
293
+ goalId: string;
294
+ objective: string;
295
+ completionCriterion?: string;
296
+ wallClockResumedAt?: number;
297
+ status?: 'active' | 'paused' | 'blocked' | 'complete';
298
+ actor?: 'user' | 'model' | 'runtime' | 'system';
299
+ budgetLimits?: {
300
+ tokenBudget?: number;
301
+ turnBudget?: number;
302
+ wallClockBudgetMs?: number;
303
+ };
304
+ }
305
+
306
+ /**
307
+ * states: (none)
308
+ * owner: src/features/goal/goalOps.ts
309
+ */
310
+ interface GoalUpdatePayload {
311
+ _name: 'goal.update';
312
+ agentId: string;
313
+ goalId?: string;
314
+ status?: 'active' | 'paused' | 'blocked' | 'complete';
315
+ reason?: string;
316
+ turnsUsed?: number;
317
+ tokensUsed?: number;
318
+ wallClockMs?: number;
319
+ wallClockResumedAt?: number;
320
+ budgetLimits?: {
321
+ tokenBudget?: number;
322
+ turnBudget?: number;
323
+ wallClockBudgetMs?: number;
324
+ };
325
+ actor?: 'user' | 'model' | 'runtime' | 'system';
326
+ }
327
+
328
+ /**
329
+ * states: (none)
330
+ * owner: src/agent/interaction/interactionOps.ts
331
+ */
332
+ interface InteractionRequestPayload {
333
+ _name: 'interaction.request';
334
+ agentId: string;
335
+ id: string;
336
+ kind: 'approval' | 'question' | 'user_tool';
337
+ toolCallId?: string;
338
+ request: any;
339
+ }
340
+
341
+ /**
342
+ * states: (none)
343
+ * owner: src/agent/interaction/interactionOps.ts
344
+ */
345
+ interface InteractionResolvedPayload {
346
+ _name: 'interaction.resolved';
347
+ agentId: string;
348
+ id: string;
349
+ response: any;
350
+ }
351
+
352
+ /**
353
+ * states: interruptionReminder
354
+ * owner: src/agent/interruptionReminder/interruptionReminderOps.ts
355
+ */
356
+ interface InterruptionReminderRecordedPayload {
357
+ _name: 'interruptionReminder.recorded';
358
+ agentId: string;
359
+ turnId: number;
360
+ }
361
+
362
+ /**
363
+ * states: llm.requestTrace
364
+ * owner: src/agent/llmRequester/llmRequestOps.ts
365
+ */
366
+ interface LlmRequestPayload {
367
+ _name: 'llm.request';
368
+ agentId: string;
369
+ kind: 'loop' | 'compaction';
370
+ provider: string;
371
+ model: string;
372
+ modelAlias?: string;
373
+ thinkingEffort?: ThinkingEffort;
374
+ thinkingKeep?: string;
375
+ temperature?: number;
376
+ topP?: number;
377
+ maxTokens?: number;
378
+ betaApi?: boolean;
379
+ toolSelect: boolean;
380
+ systemPromptHash: string;
381
+ systemPrompt?: string;
382
+ toolsHash: string;
383
+ messageCount: number;
384
+ turnStep?: string;
385
+ attempt?: string;
386
+ projection?: 'strict' | 'media-degraded' | 'media-stripped' | 'strict-media-degraded' | 'strict-media-stripped';
387
+ droppedCount?: number;
388
+ }
389
+
390
+ /**
391
+ * states: llm.requestTrace
392
+ * owner: src/agent/llmRequester/llmRequestOps.ts
393
+ */
394
+ interface LlmToolsSnapshotPayload {
395
+ _name: 'llm.tools_snapshot';
396
+ agentId: string;
397
+ hash: string;
398
+ tools: {
399
+ name: string;
400
+ description: string;
401
+ parameters: Record<string, any>;
402
+ }[];
403
+ }
404
+
405
+ /**
406
+ * states: mcp.discovery
407
+ * owner: src/agent/mcp/mcpDiscoveryOps.ts
408
+ */
409
+ interface McpToolsDiscoveredPayload {
410
+ _name: 'mcp.tools_discovered';
411
+ agentId: string;
412
+ serverName: string;
413
+ hash: string;
414
+ tools: readonly MCPToolDefinition[];
415
+ enabledNames: string[];
416
+ collisions?: {
417
+ qualified: string;
418
+ toolName: string;
419
+ collidesWith: { kind: 'same_server', toolName: string } | { kind: 'other_server', serverName: string };
420
+ }[];
421
+ }
422
+
423
+ /**
424
+ * states: permissionRules
425
+ * owner: src/agent/permissionRules/permissionRulesOps.ts
426
+ */
427
+ interface PermissionRecordApprovalResultPayload {
428
+ _name: 'permission.record_approval_result';
429
+ agentId: string;
430
+ turnId: number;
431
+ toolCallId: string;
432
+ toolName: string;
433
+ action: string;
434
+ sessionApprovalRule?: string;
435
+ result: PermissionApprovalResultRecord['result'];
436
+ }
437
+
438
+ /**
439
+ * states: permissionMode, permissionMode.configured
440
+ * owner: src/agent/permissionMode/permissionModeOps.ts
441
+ */
442
+ interface PermissionSetModePayload {
443
+ _name: 'permission.set_mode';
444
+ agentId: string;
445
+ /** PermissionMode */
446
+ mode: 'manual' | 'yolo' | 'auto';
447
+ }
448
+
449
+ /**
450
+ * states: plan
451
+ * owner: src/features/plan/planOps.ts
452
+ */
453
+ interface PlanModeCancelPayload {
454
+ _name: 'plan_mode.cancel';
455
+ agentId: string;
456
+ id?: string;
457
+ }
458
+
459
+ /**
460
+ * states: plan
461
+ * owner: src/features/plan/planOps.ts
462
+ */
463
+ interface PlanModeEnterPayload {
464
+ _name: 'plan_mode.enter';
465
+ agentId: string;
466
+ id: string;
467
+ }
468
+
469
+ /**
470
+ * states: plan
471
+ * owner: src/features/plan/planOps.ts
472
+ */
473
+ interface PlanModeExitPayload {
474
+ _name: 'plan_mode.exit';
475
+ agentId: string;
476
+ id?: string;
477
+ }
478
+
479
+ /**
480
+ * states: plan
481
+ * owner: src/features/plan/planOps.ts
482
+ */
483
+ interface PlanRevisionPayload {
484
+ _name: 'plan.revision';
485
+ agentId: string;
486
+ id: string;
487
+ version: number;
488
+ key: string;
489
+ sha256: string;
490
+ bytes: number;
491
+ }
492
+
493
+ /**
494
+ * states: pluginSessionStartSnapshot
495
+ * owner: src/agent/plugin/agentPluginOps.ts
496
+ */
497
+ interface PluginSessionStartPayload {
498
+ _name: 'plugin.session_start';
499
+ agentId: string;
500
+ content: string | null;
501
+ }
502
+
503
+ /**
504
+ * states: profile, profile.activeTools
505
+ * owner: src/agent/profile/profileOps.ts
506
+ */
507
+ interface ProfileBindPayload {
508
+ _name: 'profile.bind';
509
+ agentId: string;
510
+ modelAlias?: string;
511
+ profileName?: string;
512
+ thinkingEffort: ThinkingEffort;
513
+ systemPrompt: string;
514
+ /** EnvironmentDisclosureSnapshot */
515
+ environmentDisclosure?: {
516
+ cwd: string;
517
+ };
518
+ renderGeneration?: number;
519
+ agentsMdPaths?: string[];
520
+ activeToolNames?: string[];
521
+ disallowedTools: string[];
522
+ subagents?: string[];
523
+ }
524
+
525
+ /**
526
+ * states: (none)
527
+ * owner: src/agent/prompt/promptEvents.ts
528
+ */
529
+ interface PromptAbortedPayload {
530
+ _name: 'prompt.aborted';
531
+ agentId: string;
532
+ promptId: string;
533
+ abortedAt: string;
534
+ }
535
+
536
+ /**
537
+ * states: (none)
538
+ * owner: src/agent/prompt/promptEvents.ts
539
+ */
540
+ interface PromptCompletedPayload {
541
+ _name: 'prompt.completed';
542
+ agentId: string;
543
+ promptId: string;
544
+ finishedAt: string;
545
+ reason: 'completed' | 'failed' | 'blocked';
546
+ }
547
+
548
+ /**
549
+ * states: (none)
550
+ * owner: src/agent/prompt/promptEvents.ts
551
+ */
552
+ interface PromptSteeredPayload {
553
+ _name: 'prompt.steered';
554
+ agentId: string;
555
+ activePromptId: string;
556
+ promptIds: string[];
557
+ content: ContentPart[];
558
+ steeredAt: string;
559
+ }
560
+
561
+ /**
562
+ * states: runtimeBinding
563
+ * owner: src/agent/runtimeBinding/runtimeBindingOps.ts
564
+ */
565
+ interface RuntimeSetBindingPayload {
566
+ _name: 'runtime.set_binding';
567
+ agentId: string;
568
+ workspaceId: string;
569
+ runtimeId: string;
570
+ }
571
+
572
+ /**
573
+ * states: swarm
574
+ * owner: src/features/swarm/swarmOps.ts
575
+ */
576
+ interface SwarmModeEnterPayload {
577
+ _name: 'swarm_mode.enter';
578
+ agentId: string;
579
+ /** SwarmModeTrigger */
580
+ trigger: 'manual' | 'task' | 'tool';
581
+ }
582
+
583
+ /**
584
+ * states: contextMemory, swarm · blobs: contextMemory
585
+ * owner: src/features/swarm/swarmOps.ts
586
+ */
587
+ interface SwarmModeExitPayload {
588
+ _name: 'swarm_mode.exit';
589
+ agentId: string;
590
+ }
591
+
592
+ /**
593
+ * states: task
594
+ * owner: src/agent/task/taskOps.ts
595
+ */
596
+ interface TaskStartedPayload {
597
+ _name: 'task.started';
598
+ agentId: string;
599
+ /** AgentTaskInfo */
600
+ info: AgentTaskInfoByKind[AgentTaskKind];
601
+ }
602
+
603
+ /**
604
+ * states: task
605
+ * owner: src/agent/task/taskOps.ts
606
+ */
607
+ interface TaskTerminatedPayload {
608
+ _name: 'task.terminated';
609
+ agentId: string;
610
+ /** AgentTaskInfo */
611
+ info: AgentTaskInfoByKind[AgentTaskKind];
612
+ outputTail?: string;
613
+ }
614
+
615
+ /**
616
+ * states: task.notificationDelivery
617
+ * owner: src/agent/task/taskOps.ts
618
+ */
619
+ interface TaskWaitDeliveredPayload {
620
+ _name: 'task.waitDelivered';
621
+ agentId: string;
622
+ keys: string[];
623
+ }
624
+
625
+ /**
626
+ * states: (none)
627
+ * owner: src/agent/tokenCounting/tokenCountingOps.ts
628
+ */
629
+ interface TokenCountingMeasuredPayload {
630
+ _name: 'token_counting.measured';
631
+ agentId: string;
632
+ length: number;
633
+ tokens: number;
634
+ }
635
+
636
+ /**
637
+ * states: (none)
638
+ * owner: src/agent/tokenCounting/tokenCountingOps.ts
639
+ */
640
+ interface TokenCountingRebasedPayload {
641
+ _name: 'token_counting.rebased';
642
+ agentId: string;
643
+ length: number;
644
+ tokens: number;
645
+ measured: boolean;
646
+ }
647
+
648
+ /**
649
+ * states: (none)
650
+ * owner: src/agent/tokenCounting/tokenCountingOps.ts
651
+ */
652
+ interface TokenCountingTruncatedPayload {
653
+ _name: 'token_counting.truncated';
654
+ agentId: string;
655
+ length: number;
656
+ tokens: number;
657
+ }
658
+
659
+ /**
660
+ * states: (none)
661
+ * owner: src/agent/tokenCounting/tokenCountingOps.ts
662
+ */
663
+ interface TokenCountingTurnRecordedPayload {
664
+ _name: 'token_counting.turn_recorded';
665
+ agentId: string;
666
+ length: number;
667
+ tokens: number;
668
+ turnId: number;
669
+ }
670
+
671
+ /**
672
+ * states: userTool
673
+ * owner: src/agent/userTool/userToolOps.ts
674
+ */
675
+ interface ToolsRegisterUserToolPayload {
676
+ _name: 'tools.register_user_tool';
677
+ agentId: string;
678
+ name: string;
679
+ description: string;
680
+ parameters: UserToolRegistration['parameters'];
681
+ disclosure?: UserToolRegistration['disclosure'];
682
+ }
683
+
684
+ /**
685
+ * states: profile.activeTools
686
+ * owner: src/agent/profile/profileOps.ts
687
+ */
688
+ interface ToolsResetActiveToolsPayload {
689
+ _name: 'tools.reset_active_tools';
690
+ agentId: string;
691
+ }
692
+
693
+ /**
694
+ * states: profile.activeTools
695
+ * owner: src/agent/profile/profileOps.ts
696
+ */
697
+ interface ToolsSetActiveToolsPayload {
698
+ _name: 'tools.set_active_tools';
699
+ agentId: string;
700
+ names: string[];
701
+ }
702
+
703
+ /**
704
+ * states: userTool
705
+ * owner: src/agent/userTool/userToolOps.ts
706
+ */
707
+ interface ToolsUnregisterUserToolPayload {
708
+ _name: 'tools.unregister_user_tool';
709
+ agentId: string;
710
+ name: string;
711
+ }
712
+
713
+ /**
714
+ * states: (none)
715
+ * owner: src/features/todo/todoOps.ts
716
+ */
717
+ interface ToolsUpdateStorePayload {
718
+ _name: 'tools.update_store';
719
+ agentId: string;
720
+ key: string;
721
+ value: any;
722
+ }
723
+
724
+ /**
725
+ * states: tower, tower.base, tower.owner
726
+ * owner: src/features/tower/towerOps.ts
727
+ */
728
+ interface TowerModeEnterPayload {
729
+ _name: 'tower_mode.enter';
730
+ agentId: string;
731
+ sessionId?: string;
732
+ base?: string;
733
+ }
734
+
735
+ /**
736
+ * states: tower, tower.base, tower.owner
737
+ * owner: src/features/tower/towerOps.ts
738
+ */
739
+ interface TowerModeExitPayload {
740
+ _name: 'tower_mode.exit';
741
+ agentId: string;
742
+ }
743
+
744
+ /**
745
+ * states: turn
746
+ * owner: src/agent/loop/turnOps.ts
747
+ */
748
+ interface TurnCancelPayload {
749
+ _name: 'turn.cancel';
750
+ agentId: string;
751
+ turnId?: number;
752
+ target?: 'active' | 'queued';
753
+ reason?: 'user_cancelled' | 'aborted';
754
+ }
755
+
756
+ /**
757
+ * states: turn
758
+ * owner: src/agent/loop/turnOps.ts
759
+ */
760
+ interface TurnEndedPayload {
761
+ _name: 'turn.ended';
762
+ agentId: string;
763
+ turnId: number;
764
+ reason: 'completed' | 'cancelled' | 'failed' | 'blocked';
765
+ /** KimiErrorPayload */
766
+ error?: {
767
+ code: (typeof ErrorCodes)[keyof typeof ErrorCodes];
768
+ message: string;
769
+ name?: string;
770
+ details?: Readonly<Record<string, unknown>>;
771
+ retryable: boolean;
772
+ cause?: {
773
+ code: (typeof ErrorCodes)[keyof typeof ErrorCodes];
774
+ message: string;
775
+ name?: string;
776
+ details?: Readonly<Record<string, unknown>>;
777
+ retryable: boolean;
778
+ cause?: {
779
+ code: (typeof ErrorCodes)[keyof typeof ErrorCodes];
780
+ message: string;
781
+ name?: string;
782
+ details?: Readonly<Record<string, unknown>>;
783
+ retryable: boolean;
784
+ cause?: {
785
+ code: (typeof ErrorCodes)[keyof typeof ErrorCodes];
786
+ message: string;
787
+ name?: string;
788
+ details?: Readonly<Record<string, unknown>>;
789
+ retryable: boolean;
790
+ cause?: {
791
+ code: (typeof ErrorCodes)[keyof typeof ErrorCodes];
792
+ message: string;
793
+ name?: string;
794
+ details?: Readonly<Record<string, unknown>>;
795
+ retryable: boolean;
796
+ cause?: {
797
+ code: ErrorCode;
798
+ message: string;
799
+ name?: string;
800
+ details?: Readonly<Record<string, unknown>>;
801
+ retryable: boolean;
802
+ cause?: ErrorPayload;
803
+ };
804
+ };
805
+ };
806
+ };
807
+ };
808
+ };
809
+ durationMs?: number;
810
+ stopReason?: string;
811
+ }
812
+
813
+ /**
814
+ * states: turn
815
+ * owner: src/agent/loop/turnOps.ts
816
+ */
817
+ interface TurnPromptPayload {
818
+ _name: 'turn.prompt';
819
+ agentId: string;
820
+ input: readonly ContentPart[];
821
+ /** PromptOrigin */
822
+ origin: 'user' | 'skill_activation' | 'plugin_command' | 'injection' | 'shell_command' | 'compaction_summary' | 'system_trigger' | 'task' | 'cron_job' | 'cron_missed' | 'hook_result' | 'retry';
823
+ promptId?: string;
824
+ turnId?: number;
825
+ }
826
+
827
+ /**
828
+ * states: turn
829
+ * owner: src/agent/loop/turnOps.ts
830
+ */
831
+ interface TurnSteerPayload {
832
+ _name: 'turn.steer';
833
+ agentId: string;
834
+ input: readonly ContentPart[];
835
+ /** PromptOrigin */
836
+ origin: 'user' | 'skill_activation' | 'plugin_command' | 'injection' | 'shell_command' | 'compaction_summary' | 'system_trigger' | 'task' | 'cron_job' | 'cron_missed' | 'hook_result' | 'retry';
837
+ }
838
+
839
+ /**
840
+ * states: (none)
841
+ * owner: src/agent/loop/turnEvents.ts
842
+ */
843
+ interface TurnStepInterruptedPayload {
844
+ _name: 'turn.step.interrupted';
845
+ agentId: string;
846
+ turnId: number;
847
+ step: number;
848
+ stepId?: string;
849
+ reason: string;
850
+ message?: string;
851
+ }
852
+
853
+ /**
854
+ * states: (none)
855
+ * owner: src/agent/loop/turnEvents.ts
856
+ */
857
+ interface TurnStepRetryingPayload {
858
+ _name: 'turn.step.retrying';
859
+ agentId: string;
860
+ turnId: number;
861
+ step: number;
862
+ stepId?: string;
863
+ failedAttempt: number;
864
+ nextAttempt: number;
865
+ maxAttempts: number;
866
+ delayMs: number;
867
+ errorName: string;
868
+ errorMessage: string;
869
+ statusCode?: number;
870
+ }
871
+
872
+ /**
873
+ * states: (none)
874
+ * owner: src/agent/usage/usageOps.ts
875
+ */
876
+ interface UsageRecordPayload {
877
+ _name: 'usage.record';
878
+ agentId: string;
879
+ model: string;
880
+ usage: TokenUsage;
881
+ /** UsageRecordScope */
882
+ usageScope?: 'session' | 'turn';
883
+ }
884
+
885
+ /** Record type → payload sketch. */
886
+ interface WirePayloadMap {
887
+ "config.update": ConfigUpdatePayload;
888
+ "context.append_loop_event": ContextAppendLoopEventPayload;
889
+ "context.append_message": ContextAppendMessagePayload;
890
+ "context.apply_compaction": ContextApplyCompactionPayload;
891
+ "context.clear": ContextClearPayload;
892
+ "context.undo": ContextUndoPayload;
893
+ "cron.add": CronAddPayload;
894
+ "cron.cursor": CronCursorPayload;
895
+ "cron.delete": CronDeletePayload;
896
+ "file_history.checkpoint": FileHistoryCheckpointPayload;
897
+ "file_history.tracked": FileHistoryTrackedPayload;
898
+ "forked": ForkedPayload;
899
+ "full_compaction.begin": FullCompactionBeginPayload;
900
+ "full_compaction.cancel": FullCompactionCancelPayload;
901
+ "full_compaction.complete": FullCompactionCompletePayload;
902
+ "goal.clear": GoalClearPayload;
903
+ "goal.create": GoalCreatePayload;
904
+ "goal.update": GoalUpdatePayload;
905
+ "interaction.request": InteractionRequestPayload;
906
+ "interaction.resolved": InteractionResolvedPayload;
907
+ "interruptionReminder.recorded": InterruptionReminderRecordedPayload;
908
+ "llm.request": LlmRequestPayload;
909
+ "llm.tools_snapshot": LlmToolsSnapshotPayload;
910
+ "mcp.tools_discovered": McpToolsDiscoveredPayload;
911
+ "permission.record_approval_result": PermissionRecordApprovalResultPayload;
912
+ "permission.set_mode": PermissionSetModePayload;
913
+ "plan_mode.cancel": PlanModeCancelPayload;
914
+ "plan_mode.enter": PlanModeEnterPayload;
915
+ "plan_mode.exit": PlanModeExitPayload;
916
+ "plan.revision": PlanRevisionPayload;
917
+ "plugin.session_start": PluginSessionStartPayload;
918
+ "profile.bind": ProfileBindPayload;
919
+ "prompt.aborted": PromptAbortedPayload;
920
+ "prompt.completed": PromptCompletedPayload;
921
+ "prompt.steered": PromptSteeredPayload;
922
+ "runtime.set_binding": RuntimeSetBindingPayload;
923
+ "swarm_mode.enter": SwarmModeEnterPayload;
924
+ "swarm_mode.exit": SwarmModeExitPayload;
925
+ "task.started": TaskStartedPayload;
926
+ "task.terminated": TaskTerminatedPayload;
927
+ "task.waitDelivered": TaskWaitDeliveredPayload;
928
+ "token_counting.measured": TokenCountingMeasuredPayload;
929
+ "token_counting.rebased": TokenCountingRebasedPayload;
930
+ "token_counting.truncated": TokenCountingTruncatedPayload;
931
+ "token_counting.turn_recorded": TokenCountingTurnRecordedPayload;
932
+ "tools.register_user_tool": ToolsRegisterUserToolPayload;
933
+ "tools.reset_active_tools": ToolsResetActiveToolsPayload;
934
+ "tools.set_active_tools": ToolsSetActiveToolsPayload;
935
+ "tools.unregister_user_tool": ToolsUnregisterUserToolPayload;
936
+ "tools.update_store": ToolsUpdateStorePayload;
937
+ "tower_mode.enter": TowerModeEnterPayload;
938
+ "tower_mode.exit": TowerModeExitPayload;
939
+ "turn.cancel": TurnCancelPayload;
940
+ "turn.ended": TurnEndedPayload;
941
+ "turn.prompt": TurnPromptPayload;
942
+ "turn.steer": TurnSteerPayload;
943
+ "turn.step.interrupted": TurnStepInterruptedPayload;
944
+ "turn.step.retrying": TurnStepRetryingPayload;
945
+ "usage.record": UsageRecordPayload;
946
+ }
packages/agent-core-v2/scripts/check-import-boundaries.d.mts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface Violation {
2
+ file: string;
3
+ line: number;
4
+ message: string;
5
+ }
6
+
7
+ export const SRC_ROOT: string;
8
+
9
+ export function checkSource(source: string, absFile: string): Violation[];
10
+ export function checkFile(absFile: string): Violation[];
packages/agent-core-v2/scripts/check-import-boundaries.mjs ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
4
+ import { dirname, join, relative, resolve } from 'node:path';
5
+
6
+ const PKG_ROOT = resolve(import.meta.dirname, '..');
7
+ export const SRC_ROOT = join(PKG_ROOT, 'src');
8
+ const TEST_ROOT = join(PKG_ROOT, 'test');
9
+ const HUMAN_ROOT = join(SRC_ROOT, 'human');
10
+ const ADAPTER_ROOT = join(SRC_ROOT, 'llm-adapter');
11
+ const LOOP_MACHINE_ADAPTER_ROOT = join(SRC_ROOT, 'agent/loop/machine');
12
+ const SESSION_LIFECYCLE_ADAPTER_ROOT = join(SRC_ROOT, 'session/agentLifecycle');
13
+
14
+ const SELF_PACKAGE_PREFIX = '@moonshot-ai/agent-core-v2/';
15
+ const KOSONG_PATH_RE = /(?:^|\/)kosong(?:\/|$)/;
16
+ const TRAIT_FILE_RE = /\/trait\.ts$/;
17
+ const FORMAT_LOWER_FILE_RE = /\/bases\/[^/]+\/(?:format|lower)\.ts$/;
18
+ const FORMAT_LOWER_MODULE_RE = /\/bases\/[^/]+\/(?:format|lower)$/;
19
+ const TRAIT_MODULE_RE = /\/trait$/;
20
+ const BASES_DIR_RE = /\/llm\/requester\/bases(?:\/|$)/;
21
+ const BASES_INTERNAL_MODULE_RE =
22
+ /\/llm\/requester\/bases\/[^/]+\/(?:format|lower|patterns|reasoning-key)$/;
23
+ const TEST_DIR_RE = /\/test(?:\/|$)/;
24
+
25
+ function traitBoundaryViolation(absFile, targetAbs, specifier) {
26
+ const message = `format and trait never import each other ('${specifier}') — both sides speak only the neutral wire/chunk types in the protocol's contract.ts`;
27
+ if (TRAIT_FILE_RE.test(absFile) && FORMAT_LOWER_MODULE_RE.test(targetAbs)) {
28
+ return message;
29
+ }
30
+ if (FORMAT_LOWER_FILE_RE.test(absFile) && TRAIT_MODULE_RE.test(targetAbs)) {
31
+ return message;
32
+ }
33
+ return undefined;
34
+ }
35
+
36
+ function basesInternalViolation(absFile, targetAbs, specifier) {
37
+ if (!BASES_INTERNAL_MODULE_RE.test(targetAbs)) return undefined;
38
+ if (TRAIT_FILE_RE.test(absFile) && FORMAT_LOWER_MODULE_RE.test(targetAbs)) return undefined;
39
+ if (BASES_DIR_RE.test(absFile) || TEST_DIR_RE.test(absFile)) return undefined;
40
+ return `protocol format modules are internal to the requester pipeline ('${specifier}') — only llm/requester/bases code and tests may import format/lower/patterns; everyone else speaks contract/trait/requester`;
41
+ }
42
+
43
+ const HUMAN_VOCABULARY = new Set([
44
+ 'agent/origin',
45
+ 'llm/message',
46
+ 'llm/usage',
47
+ 'llm/capability',
48
+ 'llm/thinking',
49
+ 'llm/finish-reason',
50
+ 'llm/response-format',
51
+ 'llm/media/upload',
52
+ 'llm/media/image-formats',
53
+ 'llm/requester/requester',
54
+ 'llm/toolCallIdNormalizer',
55
+ 'llm-kimi/trait',
56
+ 'interaction/interaction',
57
+ 'interaction/machine',
58
+ 'interaction/facade',
59
+ 'utils/watch',
60
+ 'xstate2',
61
+ ]);
62
+
63
+ const V2_ONLY_FIRST_SEGMENTS = new Set([
64
+ 'llm-adapter',
65
+ 'app',
66
+ 'workspace',
67
+ 'features',
68
+ 'state',
69
+ 'wire',
70
+ 'persistence',
71
+ 'os',
72
+ 'mcpCore',
73
+ 'errors',
74
+ 'debug',
75
+ 'program',
76
+ 'runtime',
77
+ '_base',
78
+ ]);
79
+
80
+ function isInside(root, absPath) {
81
+ const rel = relative(root, absPath);
82
+ return rel !== '' && !rel.startsWith('..');
83
+ }
84
+
85
+ function humanSubpathOf(specifier) {
86
+ if (specifier.startsWith('#human/')) return specifier.slice('#human/'.length);
87
+ if (specifier.startsWith(`${SELF_PACKAGE_PREFIX}human/`)) {
88
+ return specifier.slice(`${SELF_PACKAGE_PREFIX}human/`.length);
89
+ }
90
+ return undefined;
91
+ }
92
+
93
+ function stripTs(path) {
94
+ return path.endsWith('.ts') ? path.slice(0, -'.ts'.length) : path;
95
+ }
96
+
97
+ function resolveIntraV2(specifier, fromFile) {
98
+ if (specifier.startsWith('#human/')) {
99
+ return join(HUMAN_ROOT, specifier.slice('#human/'.length));
100
+ }
101
+ if (specifier.startsWith('#/')) {
102
+ if (isInside(HUMAN_ROOT, fromFile)) {
103
+ return join(HUMAN_ROOT, specifier.slice(2));
104
+ }
105
+ return join(SRC_ROOT, specifier.slice(2));
106
+ }
107
+ if (specifier.startsWith(SELF_PACKAGE_PREFIX)) {
108
+ return join(SRC_ROOT, specifier.slice(SELF_PACKAGE_PREFIX.length));
109
+ }
110
+ if (specifier.startsWith('.')) {
111
+ return resolve(dirname(fromFile), specifier);
112
+ }
113
+ return undefined;
114
+ }
115
+
116
+ const IMPORT_RE =
117
+ /(?:import|export)\s+(?:type\s+)?(?:[^'";]*?\s+from\s+)?['"]([^'"]+)['"]|(?:import|require)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
118
+
119
+ export function checkSource(source, absFile) {
120
+ const violations = [];
121
+ const inSrc = !relative(SRC_ROOT, absFile).startsWith('..');
122
+ const inHuman = isInside(HUMAN_ROOT, absFile);
123
+ const inAdapter =
124
+ isInside(ADAPTER_ROOT, absFile) ||
125
+ isInside(LOOP_MACHINE_ADAPTER_ROOT, absFile) ||
126
+ isInside(SESSION_LIFECYCLE_ADAPTER_ROOT, absFile);
127
+
128
+ let match;
129
+ IMPORT_RE.lastIndex = 0;
130
+ while ((match = IMPORT_RE.exec(source)) !== null) {
131
+ const specifier = match[1] ?? match[2];
132
+ if (!specifier) continue;
133
+ const line = source.slice(0, match.index).split('\n').length;
134
+
135
+ if (KOSONG_PATH_RE.test(specifier)) {
136
+ violations.push({
137
+ file: absFile,
138
+ line,
139
+ message: `the kosong kernel is deleted (${specifier}) — request/provider code lives in #human/llm, the v2 compatibility boundary is #/llm-adapter`,
140
+ });
141
+ continue;
142
+ }
143
+
144
+ if (!inSrc) continue;
145
+
146
+ const targetAbs = resolveIntraV2(specifier, absFile);
147
+ if (targetAbs !== undefined) {
148
+ const basesInternal = basesInternalViolation(absFile, stripTs(targetAbs), specifier);
149
+ if (basesInternal !== undefined) {
150
+ violations.push({ file: absFile, line, message: basesInternal });
151
+ }
152
+ }
153
+
154
+ if (inHuman) {
155
+ if (specifier.startsWith('#/')) {
156
+ const first = specifier.slice(2).split('/')[0];
157
+ if (first !== undefined && V2_ONLY_FIRST_SEGMENTS.has(first)) {
158
+ violations.push({
159
+ file: absFile,
160
+ line,
161
+ message: `human must not import outside its kernel ('${specifier}') — human is the pure LLM/agent kernel: it never imports llm-adapter or v2 domains`,
162
+ });
163
+ continue;
164
+ }
165
+ }
166
+ if (targetAbs !== undefined && !isInside(HUMAN_ROOT, targetAbs)) {
167
+ violations.push({
168
+ file: absFile,
169
+ line,
170
+ message: `human must not import outside its kernel ('${specifier}') — human is the pure LLM/agent kernel: it never imports llm-adapter or v2 domains`,
171
+ });
172
+ }
173
+ if (targetAbs !== undefined) {
174
+ const traitBoundary = traitBoundaryViolation(absFile, stripTs(targetAbs), specifier);
175
+ if (traitBoundary !== undefined) {
176
+ violations.push({ file: absFile, line, message: traitBoundary });
177
+ }
178
+ }
179
+ continue;
180
+ }
181
+
182
+ const humanSub = humanSubpathOf(specifier);
183
+ if (humanSub !== undefined && !inAdapter && !HUMAN_VOCABULARY.has(stripTs(humanSub))) {
184
+ violations.push({
185
+ file: absFile,
186
+ line,
187
+ message: `only llm-adapter, agent/loop/machine and session/agentLifecycle may import the human implementation ('${specifier}') — v2 code outside those adapter layers is limited to the vocabulary modules (${[...HUMAN_VOCABULARY].join(', ')})`,
188
+ });
189
+ }
190
+ }
191
+
192
+ return violations;
193
+ }
194
+
195
+ export function checkFile(absFile) {
196
+ return checkSource(readFileSync(absFile, 'utf8'), absFile);
197
+ }
198
+
199
+ function walk(dir) {
200
+ const out = [];
201
+ for (const entry of readdirSync(dir)) {
202
+ if (entry === 'node_modules' || entry === 'dist') continue;
203
+ const abs = join(dir, entry);
204
+ const st = statSync(abs);
205
+ if (st.isDirectory()) out.push(...walk(abs));
206
+ else if (abs.endsWith('.ts')) out.push(abs);
207
+ }
208
+ return out;
209
+ }
210
+
211
+ function main() {
212
+ const files = [...walk(SRC_ROOT), ...walk(TEST_ROOT)];
213
+ const violations = files.flatMap((f) => checkFile(f));
214
+ if (violations.length === 0) {
215
+ console.log(`check-import-boundaries: OK (${files.length} files)`);
216
+ return 0;
217
+ }
218
+ for (const v of violations) {
219
+ console.error(`${relative(PKG_ROOT, v.file)}:${v.line}: ${v.message}`);
220
+ }
221
+ console.error(`\ncheck-import-boundaries: ${violations.length} violation(s)`);
222
+ return 1;
223
+ }
224
+
225
+ const isMain = process.argv[1] && resolve(process.argv[1]) === import.meta.filename;
226
+ if (isMain) {
227
+ process.exit(main());
228
+ }
packages/agent-core-v2/scripts/debarrel.mjs ADDED
@@ -0,0 +1,474 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+ import { Project } from 'ts-morph';
3
+ import path from 'node:path';
4
+ import fs from 'node:fs';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const PKG = path.resolve(__dirname, '..');
9
+ const SRC = path.join(PKG, 'src');
10
+ const ENTRY = path.join(SRC, 'index.ts');
11
+
12
+ const args = process.argv.slice(2);
13
+ const DRY = args.includes('--dry-run');
14
+ const ONLY = (args.find((a) => a.startsWith('--only=')) || '').slice('--only='.length) || null;
15
+ const ENTRY_ONLY = args.includes('--entry');
16
+ const DELETE_BARRELS = args.includes('--delete-barrels');
17
+ const LIST_REGS = args.includes('--list-registers');
18
+ const VERIFY = args.includes('--verify-coverage');
19
+
20
+ const project = new Project({ tsConfigFilePath: path.join(PKG, 'tsconfig.json') });
21
+
22
+ const relSpec = (absFile) =>
23
+ '#/' + path.relative(SRC, absFile).split(path.sep).join('/').replace(/\.ts$/, '');
24
+
25
+ const isUnderSrc = (abs) => abs === SRC || abs.startsWith(SRC + path.sep);
26
+ const isIndexBasename = (abs) => path.basename(abs) === 'index.ts';
27
+ const isBarrelFile = (sf) => {
28
+ const f = sf.getFilePath();
29
+ return isUnderSrc(f) && isIndexBasename(f) && f !== ENTRY;
30
+ };
31
+ const resolvedFile = (decl) => decl.getModuleSpecifierSourceFile() || null;
32
+ const barrelOfDecl = (decl) => {
33
+ const sf = resolvedFile(decl);
34
+ return sf && isBarrelFile(sf) ? sf : null;
35
+ };
36
+
37
+ function resolveName(barrel, name) {
38
+ const decls = barrel.getExportedDeclarations().get(name);
39
+ if (!decls || decls.length === 0) return null;
40
+ const decl = decls[0];
41
+ const leaf = decl.getSourceFile();
42
+ const sym = decl.getSymbol();
43
+ let leafName = null;
44
+ for (const [ln, lds] of leaf.getExportedDeclarations()) {
45
+ if (lds.some((d) => d.getSymbol() === sym)) {
46
+ leafName = ln;
47
+ break;
48
+ }
49
+ }
50
+ if (leafName === null) leafName = (sym && sym.getName()) || name;
51
+ return { leafFile: leaf.getFilePath(), leafName };
52
+ }
53
+
54
+ function expandBarrelClauses(barrel) {
55
+ const clauses = [];
56
+ for (const ed of barrel.getExportDeclarations()) {
57
+ const target = resolvedFile(ed);
58
+ if (!target) continue;
59
+ if (isBarrelFile(target)) {
60
+ clauses.push(...expandBarrelClauses(target));
61
+ continue;
62
+ }
63
+ const file = target.getFilePath();
64
+ const isStar =
65
+ ed.getNamedExports().length === 0 && !ed.getNamespaceExport();
66
+ if (isStar) {
67
+ clauses.push({ kind: 'star', file });
68
+ } else if (ed.getNamespaceExport()) {
69
+ clauses.push({ kind: 'namespace', file, name: ed.getNamespaceExport().getName() });
70
+ } else {
71
+ const declType = ed.isTypeOnly();
72
+ const specs = ed.getNamedExports().map((s) => ({
73
+ name: s.getName(),
74
+ alias: s.getAliasNode()?.getText(),
75
+ isTypeOnly: declType || s.isTypeOnly(),
76
+ }));
77
+ clauses.push({ kind: 'named', file, isTypeOnly: declType, specs });
78
+ }
79
+ }
80
+ return clauses;
81
+ }
82
+
83
+ function allLeavesUnderDir(dirAbs) {
84
+ const out = [];
85
+ const walk = (d) => {
86
+ for (const ent of fs.readdirSync(d, { withFileTypes: true })) {
87
+ const p = path.join(d, ent.name);
88
+ if (ent.isDirectory()) walk(p);
89
+ else if (
90
+ ent.isFile() &&
91
+ p.endsWith('.ts') &&
92
+ !p.endsWith('.test.ts') &&
93
+ !p.endsWith('.d.ts') &&
94
+ path.basename(p) !== 'index.ts'
95
+ ) {
96
+ out.push(p);
97
+ }
98
+ }
99
+ };
100
+ walk(dirAbs);
101
+ return out.sort((a, b) => a.localeCompare(b));
102
+ }
103
+
104
+ function rewriteConsumerFile(sf, onlyBarrelPath) {
105
+ const report = { imports: 0, exports: 0, manuals: [], sideEffects: 0 };
106
+
107
+ for (const decl of sf.getImportDeclarations()) {
108
+ const barrel = barrelOfDecl(decl);
109
+ if (!barrel) continue;
110
+ if (onlyBarrelPath && barrel.getFilePath() !== onlyBarrelPath) continue;
111
+
112
+ if (decl.getNamespaceImport()) {
113
+ report.manuals.push({ sf: sf.getFilePath(), text: decl.getText(), why: 'namespace import' });
114
+ continue;
115
+ }
116
+ const hasDefault = !!decl.getDefaultImport();
117
+ const named = decl.getNamedImports();
118
+ if (!hasDefault && named.length === 0) {
119
+ const leaves = [...new Set(expandBarrelClauses(barrel).map((c) => c.file))];
120
+ const idx = sf.getImportDeclarations().indexOf(decl);
121
+ sf.insertImportDeclarations(
122
+ idx,
123
+ leaves.map((leaf) => ({ moduleSpecifier: relSpec(leaf) })),
124
+ );
125
+ decl.remove();
126
+ report.sideEffects += leaves.length;
127
+ report.imports++;
128
+ continue;
129
+ }
130
+
131
+ const declType = decl.isTypeOnly();
132
+ const groups = new Map();
133
+ const add = (leaf, spec) => {
134
+ if (!groups.has(leaf)) groups.set(leaf, []);
135
+ groups.get(leaf).push(spec);
136
+ };
137
+
138
+ if (hasDefault) {
139
+ const r = resolveName(barrel, 'default');
140
+ if (!r) report.manuals.push({ sf: sf.getFilePath(), text: decl.getText(), why: 'default import unresolved' });
141
+ else add(r.leafFile, { default: decl.getDefaultImport().getText() });
142
+ }
143
+ for (const s of named) {
144
+ const lookup = s.getName();
145
+ const local = s.getAliasNode()?.getText() || s.getName();
146
+ const r = resolveName(barrel, lookup);
147
+ if (!r) {
148
+ report.manuals.push({ sf: sf.getFilePath(), text: s.getText(), why: 'named import unresolved' });
149
+ continue;
150
+ }
151
+ add(r.leafFile, {
152
+ name: r.leafName,
153
+ alias: local !== r.leafName ? local : undefined,
154
+ isTypeOnly: declType || s.isTypeOnly(),
155
+ });
156
+ }
157
+ const structures = buildImportStructures(groups);
158
+ const idx = sf.getImportDeclarations().indexOf(decl);
159
+ if (structures.length) sf.insertImportDeclarations(idx, structures);
160
+ decl.remove();
161
+ report.imports++;
162
+ }
163
+
164
+ for (const decl of sf.getExportDeclarations()) {
165
+ const barrel = barrelOfDecl(decl);
166
+ if (!barrel) continue;
167
+ if (onlyBarrelPath && barrel.getFilePath() !== onlyBarrelPath) continue;
168
+
169
+ const isStar = decl.getNamedExports().length === 0 && !decl.getNamespaceExport();
170
+ if (isStar) {
171
+ const clauses = expandBarrelClauses(barrel);
172
+ decl.replaceWithText(clauses.map(exportClauseToText).join('\n'));
173
+ report.exports++;
174
+ continue;
175
+ }
176
+ if (decl.getNamespaceExport()) {
177
+ report.manuals.push({ sf: sf.getFilePath(), text: decl.getText(), why: 'namespace export' });
178
+ continue;
179
+ }
180
+ const declType = decl.isTypeOnly();
181
+ const groups = new Map();
182
+ for (const s of decl.getNamedExports()) {
183
+ const lookup = s.getName();
184
+ const exportedAs = s.getAliasNode()?.getText() || s.getName();
185
+ const r = resolveName(barrel, lookup);
186
+ if (!r) {
187
+ report.manuals.push({ sf: sf.getFilePath(), text: s.getText(), why: 'named export unresolved' });
188
+ continue;
189
+ }
190
+ if (!groups.has(r.leafFile)) groups.set(r.leafFile, { specs: [], allType: true });
191
+ const g = groups.get(r.leafFile);
192
+ const t = declType || s.isTypeOnly();
193
+ g.allType = g.allType && t;
194
+ g.specs.push({
195
+ name: r.leafName,
196
+ alias: exportedAs !== r.leafName ? exportedAs : undefined,
197
+ isTypeOnly: t,
198
+ });
199
+ }
200
+ const lines = [];
201
+ for (const [leaf, { specs, allType }] of groups) {
202
+ lines.push(renderNamedExport(relSpec(leaf), specs, allType));
203
+ }
204
+ decl.replaceWithText(lines.join('\n'));
205
+ report.exports++;
206
+ }
207
+ return report;
208
+ }
209
+
210
+ function buildImportStructures(groups) {
211
+ const structures = [];
212
+ for (const [leaf, specs] of groups) {
213
+ const spec = relSpec(leaf);
214
+ const defaults = specs.filter((s) => s.default);
215
+ const namedSpecs = specs.filter((s) => !s.default);
216
+ for (const d of defaults) {
217
+ structures.push({ moduleSpecifier: spec, defaultImport: d.default });
218
+ }
219
+ const values = namedSpecs.filter((s) => !s.isTypeOnly);
220
+ const types = namedSpecs.filter((s) => s.isTypeOnly);
221
+ if (values.length)
222
+ structures.push({
223
+ moduleSpecifier: spec,
224
+ namedImports: values.map((v) => ({ name: v.name, alias: v.alias })),
225
+ });
226
+ if (types.length)
227
+ structures.push({
228
+ moduleSpecifier: spec,
229
+ isTypeOnly: true,
230
+ namedImports: types.map((t) => ({ name: t.name, alias: t.alias })),
231
+ });
232
+ }
233
+ return structures;
234
+ }
235
+
236
+ function renderNamedExport(spec, specs, allType) {
237
+ const body = specs
238
+ .map((s) => `${allType ? '' : s.isTypeOnly ? 'type ' : ''}${s.name}${s.alias ? ' as ' + s.alias : ''}`)
239
+ .join(', ');
240
+ return `${allType ? 'export type' : 'export'} { ${body} } from '${spec}';`;
241
+ }
242
+
243
+ function exportClauseToText(c) {
244
+ if (c.kind === 'star') return `export * from '${relSpec(c.file)}';`;
245
+ if (c.kind === 'namespace') return `export * as ${c.name} from '${relSpec(c.file)}';`;
246
+ return renderNamedExport(relSpec(c.file), c.specs, c.isTypeOnly);
247
+ }
248
+
249
+ function regenerateEntry() {
250
+ const entrySf = project.getSourceFileOrThrow(ENTRY);
251
+ const original = entrySf.getFullText();
252
+ const headerMatch = original.match(/^\s*\/\*\*[\s\S]*?\*\//);
253
+ const header = headerMatch ? headerMatch[0] : '/** agent-core-v2 public surface. */';
254
+
255
+ const refs = [];
256
+ for (const decl of [...entrySf.getExportDeclarations(), ...entrySf.getImportDeclarations()]) {
257
+ const barrel = barrelOfDecl(decl);
258
+ if (!barrel) continue;
259
+ let mode;
260
+ if (decl.getKindName() === 'ImportDeclaration') mode = 'side';
261
+ else {
262
+ const isStar = decl.getNamedExports().length === 0 && !decl.getNamespaceExport();
263
+ mode = isStar ? 'star' : 'named';
264
+ }
265
+ refs.push({ decl, barrel, mode });
266
+ }
267
+
268
+ const publicLines = [];
269
+ const loadingLines = [];
270
+ const processed = new Set();
271
+
272
+ for (const { decl, barrel, mode } of refs) {
273
+ const bf = barrel.getFilePath();
274
+ const dirAbs = path.dirname(bf);
275
+ const allLeaves = allLeavesUnderDir(dirAbs);
276
+ const clauses = expandBarrelClauses(barrel);
277
+ const starLeaves = new Set(clauses.filter((c) => c.kind === 'star').map((c) => c.file));
278
+
279
+ if (mode === 'star') {
280
+ for (const c of clauses) publicLines.push(exportClauseToText(c));
281
+ } else if (mode === 'named') {
282
+ const declType = decl.isTypeOnly();
283
+ const groups = new Map();
284
+ for (const s of decl.getNamedExports()) {
285
+ const lookup = s.getName();
286
+ const exportedAs = s.getAliasNode()?.getText() || s.getName();
287
+ const r = resolveName(barrel, lookup);
288
+ if (!r) continue;
289
+ if (!groups.has(r.leafFile)) groups.set(r.leafFile, { specs: [], allType: true });
290
+ const g = groups.get(r.leafFile);
291
+ const t = declType || s.isTypeOnly();
292
+ g.allType = g.allType && t;
293
+ g.specs.push({
294
+ name: r.leafName,
295
+ alias: exportedAs !== r.leafName ? exportedAs : undefined,
296
+ isTypeOnly: t,
297
+ });
298
+ }
299
+ for (const [leaf, { specs, allType }] of groups) {
300
+ publicLines.push(renderNamedExport(relSpec(leaf), specs, allType));
301
+ }
302
+ }
303
+ for (const leaf of allLeaves) {
304
+ const key = leaf;
305
+ if (starLeaves.has(leaf)) continue;
306
+ if (processed.has(key)) continue;
307
+ processed.add(key);
308
+ loadingLines.push(`import '${relSpec(leaf)}';`);
309
+ }
310
+ }
311
+
312
+ const body = [
313
+ header,
314
+ '',
315
+ '// Public surface — precise re-exports of each domain leaf (no barrels).',
316
+ ...publicLines,
317
+ '',
318
+ '// Side-effect loading — ensure every domain leaf (and its top-level',
319
+ '// `register*` calls) is evaluated when the package is imported.',
320
+ ...loadingLines,
321
+ '',
322
+ ].join('\n');
323
+
324
+ if (!DRY) fs.writeFileSync(ENTRY, body);
325
+ return { publicLines: publicLines.length, loadingLines: loadingLines.length };
326
+ }
327
+
328
+ const REGISTER_NAMES = new Set([
329
+ 'registerScopedService',
330
+ 'registerAgentToolService',
331
+ 'registerErrorDomain',
332
+ 'registerConfigSection',
333
+ 'registerAgentProfile',
334
+ 'registerFlagDefinition',
335
+ ]);
336
+
337
+ function isModuleScoped(call) {
338
+ let n = call.getParent();
339
+ while (n) {
340
+ const k = n.getKindName();
341
+ if (
342
+ k === 'FunctionDeclaration' ||
343
+ k === 'FunctionExpression' ||
344
+ k === 'ArrowFunction' ||
345
+ k === 'MethodDeclaration' ||
346
+ k === 'Constructor' ||
347
+ k === 'ClassDeclaration'
348
+ ) {
349
+ return false;
350
+ }
351
+ n = n.getParent();
352
+ }
353
+ return true;
354
+ }
355
+
356
+ function findRegisterFiles() {
357
+ const files = [];
358
+ for (const sf of project.getSourceFiles()) {
359
+ const f = sf.getFilePath();
360
+ if (!isUnderSrc(f) || f.endsWith('.test.ts')) continue;
361
+ let hit = false;
362
+ sf.forEachDescendant((node) => {
363
+ if (hit) return;
364
+ if (node.getKindName() !== 'CallExpression') return;
365
+ const expr = node.getExpression();
366
+ if (expr.getKindName() !== 'Identifier') return;
367
+ if (!REGISTER_NAMES.has(expr.getText())) return;
368
+ if (isModuleScoped(node)) hit = true;
369
+ });
370
+ if (hit) files.push(f);
371
+ }
372
+ return files.sort();
373
+ }
374
+
375
+ function reachedFromEntry() {
376
+ const reached = new Set();
377
+ const visit = (sf) => {
378
+ const f = sf.getFilePath();
379
+ if (reached.has(f)) return;
380
+ reached.add(f);
381
+ if (!isUnderSrc(f)) return;
382
+ const edges = [...sf.getImportDeclarations(), ...sf.getExportDeclarations()];
383
+ for (const d of edges) {
384
+ if (d.isTypeOnly && d.isTypeOnly()) continue;
385
+ const t = resolvedFile(d);
386
+ if (t && isUnderSrc(t.getFilePath())) visit(t);
387
+ }
388
+ };
389
+ visit(project.getSourceFileOrThrow(ENTRY));
390
+ return reached;
391
+ }
392
+
393
+ function verifyCoverage() {
394
+ const regs = findRegisterFiles();
395
+ const reached = reachedFromEntry();
396
+ const missing = regs.filter((f) => !reached.has(f));
397
+ console.log(`register files: ${regs.length}; reachable from entry: ${reached.size}; missing: ${missing.length}`);
398
+ if (missing.length) {
399
+ console.log('MISSING (not reachable from src/index.ts):');
400
+ for (const m of missing) console.log(' ' + path.relative(PKG, m));
401
+ return false;
402
+ }
403
+ return true;
404
+ }
405
+
406
+ function deleteBarrels() {
407
+ let n = 0;
408
+ for (const sf of project.getSourceFiles()) {
409
+ if (!isBarrelFile(sf)) continue;
410
+ const f = sf.getFilePath();
411
+ if (!DRY) fs.unlinkSync(f);
412
+ n++;
413
+ }
414
+ return n;
415
+ }
416
+
417
+ function main() {
418
+ if (LIST_REGS) {
419
+ for (const f of findRegisterFiles()) console.log(path.relative(PKG, f));
420
+ return;
421
+ }
422
+ if (VERIFY) {
423
+ const ok = verifyCoverage();
424
+ process.exit(ok ? 0 : 1);
425
+ }
426
+ if (ENTRY_ONLY) {
427
+ const r = regenerateEntry();
428
+ console.log(`entry regenerated: ${r.publicLines} public lines, ${r.loadingLines} loading lines${DRY ? ' (dry-run)' : ''}`);
429
+ return;
430
+ }
431
+
432
+ let onlyBarrelPath = null;
433
+ if (ONLY) {
434
+ onlyBarrelPath = path.join(SRC, ONLY, 'index.ts');
435
+ if (!fs.existsSync(onlyBarrelPath)) {
436
+ console.error(`--only target not a barrel: ${path.relative(PKG, onlyBarrelPath)}`);
437
+ process.exit(2);
438
+ }
439
+ }
440
+
441
+ const totals = { files: 0, imports: 0, exports: 0, sideEffects: 0, manuals: [] };
442
+ for (const sf of project.getSourceFiles()) {
443
+ const f = sf.getFilePath();
444
+ if (!isUnderSrc(f) && !f.startsWith(path.join(PKG, 'test') + path.sep)) continue;
445
+ if (f === ENTRY) continue;
446
+ const before = sf.getFullText();
447
+ const r = rewriteConsumerFile(sf, onlyBarrelPath);
448
+ if (sf.getFullText() !== before) {
449
+ totals.files++;
450
+ totals.imports += r.imports;
451
+ totals.exports += r.exports;
452
+ totals.sideEffects += r.sideEffects;
453
+ }
454
+ totals.manuals.push(...r.manuals);
455
+ }
456
+
457
+ if (!DRY) project.saveSync();
458
+
459
+ console.log(
460
+ `rewrote ${totals.files} files: ${totals.imports} barrel imports, ${totals.exports} barrel exports, ${totals.sideEffects} side-effect loads${DRY ? ' (dry-run)' : ''}`,
461
+ );
462
+ if (totals.manuals.length) {
463
+ console.log(`MANUAL (${totals.manuals.length}) — could not auto-split:`);
464
+ for (const m of totals.manuals)
465
+ console.log(` ${path.relative(PKG, m.sf)} :: ${m.why} :: ${m.text.replace(/\s+/g, ' ').slice(0, 120)}`);
466
+ }
467
+
468
+ if (DELETE_BARRELS) {
469
+ const n = deleteBarrels();
470
+ console.log(`deleted ${n} domain barrels${DRY ? ' (dry-run)' : ''}`);
471
+ }
472
+ }
473
+
474
+ main();
packages/agent-core-v2/scripts/gen-config-manifest.mts ADDED
@@ -0,0 +1,314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
+ import { join, relative } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+
5
+ import { getConfigOverlayContributions } from '#/app/config/configOverlayContributions';
6
+ import type { ConfigSectionContribution } from '#/app/config/configSectionContributions';
7
+ import { getConfigSectionContributions } from '#/app/config/configSectionContributions';
8
+ import { camelToSnake } from '#/app/config/toml';
9
+
10
+ import {
11
+ asJsonSchema,
12
+ describeType,
13
+ isRecord,
14
+ resolveRef,
15
+ toJsonSchema,
16
+ truncate,
17
+ type JsonSchema,
18
+ } from './lib/jsonSchema.mts';
19
+
20
+ const PKG = join(import.meta.dirname, '..');
21
+ const SRC = join(PKG, 'src');
22
+ export const MANIFEST_PATH = join(PKG, 'docs', 'config-manifest.toml');
23
+
24
+ function walk(dir: string, out: string[] = []): string[] {
25
+ for (const entry of readdirSync(dir)) {
26
+ const p = join(dir, entry);
27
+ if (statSync(p).isDirectory()) walk(p, out);
28
+ else if (entry.endsWith('.ts')) out.push(p);
29
+ }
30
+ return out;
31
+ }
32
+
33
+ function constStringValue(source: string, ident: string): string | undefined {
34
+ const re = new RegExp(`(?:export\\s+)?const\\s+${ident}\\s*(?::[^=;]+)?=\\s*'([^']+)'`);
35
+ return re.exec(source)?.[1];
36
+ }
37
+
38
+ function scanSectionOwners(): Map<string, string> {
39
+ const owners = new Map<string, string>();
40
+ for (const file of walk(SRC)) {
41
+ const source = readFileSync(file, 'utf-8');
42
+ if (!source.includes('registerConfigSection(')) continue;
43
+ for (const match of source.matchAll(/registerConfigSection\(\s*(?:'([^']+)'|([A-Za-z0-9_$]+))/g)) {
44
+ const ident = match[2];
45
+ const domain = match[1] ?? (ident === undefined ? undefined : constStringValue(source, ident));
46
+ if (domain !== undefined) owners.set(domain, relative(PKG, file));
47
+ }
48
+ }
49
+ return owners;
50
+ }
51
+
52
+ function scanOverlayOwners(): Map<string, string> {
53
+ const owners = new Map<string, string>();
54
+ for (const file of walk(SRC)) {
55
+ if (file.endsWith('configOverlayContributions.ts')) continue;
56
+ const source = readFileSync(file, 'utf-8');
57
+ if (!source.includes('registerConfigOverlay(')) continue;
58
+ for (const match of source.matchAll(/registerConfigOverlay\(\s*([A-Za-z0-9_$]+)/g)) {
59
+ const ident = match[1];
60
+ if (ident !== undefined) owners.set(ident, relative(PKG, file));
61
+ }
62
+ }
63
+ return owners;
64
+ }
65
+
66
+ function toTomlValue(value: unknown): string {
67
+ if (typeof value === 'string') return JSON.stringify(value);
68
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
69
+ if (Array.isArray(value)) return `[${value.map(toTomlValue).join(', ')}]`;
70
+ if (isRecord(value)) {
71
+ const entries = Object.entries(value).map(([k, v]) => `${camelToSnake(k)} = ${toTomlValue(v)}`);
72
+ return `{ ${entries.join(', ')} }`;
73
+ }
74
+ return '""';
75
+ }
76
+
77
+ interface EnvRow {
78
+ readonly field: string;
79
+ readonly env: string;
80
+ readonly detail: string;
81
+ }
82
+
83
+ interface EnvBindingFields {
84
+ readonly env?: unknown;
85
+ readonly deprecatedEnv?: unknown;
86
+ readonly parse?: unknown;
87
+ readonly default?: unknown;
88
+ }
89
+
90
+ function flattenEnvBindings(bindings: unknown, path: string[] = []): EnvRow[] {
91
+ if (typeof bindings === 'string') {
92
+ return [{ field: path.join('.'), env: bindings, detail: '' }];
93
+ }
94
+ if (!isRecord(bindings)) return [];
95
+ const binding = bindings as EnvBindingFields;
96
+ if (typeof binding.env === 'string') {
97
+ const detail: string[] = [];
98
+ if (binding.parse !== undefined) detail.push('custom parse');
99
+ if (binding.default !== undefined) detail.push(`default ${JSON.stringify(binding.default)}`);
100
+ if (typeof binding.deprecatedEnv === 'string') {
101
+ detail.push(`deprecated fallback ${binding.deprecatedEnv}`);
102
+ }
103
+ return [{ field: path.join('.'), env: binding.env, detail: detail.join('; ') }];
104
+ }
105
+ return Object.entries(bindings).flatMap(([key, value]) => flattenEnvBindings(value, [...path, key]));
106
+ }
107
+
108
+ function snakePath(field: string): string {
109
+ return field.split('.').map(camelToSnake).join('.');
110
+ }
111
+
112
+ const RULE = `# ${'#'.repeat(74)}`;
113
+
114
+ function renderFieldComments(
115
+ properties: Record<string, unknown>,
116
+ root: JsonSchema,
117
+ indent: string,
118
+ depth = 0,
119
+ ): string[] {
120
+ const lines: string[] = [];
121
+ for (const [name, prop] of Object.entries(properties)) {
122
+ const resolved = resolveRef(prop, root);
123
+ const propDefault = asJsonSchema(resolved)?.default;
124
+ const defNote = propDefault !== undefined ? ` (default: ${JSON.stringify(propDefault)})` : '';
125
+ lines.push(`${indent}# ${camelToSnake(name)}: ${describeType(resolved)}${defNote}`);
126
+ const subProps = asJsonSchema(resolved)?.properties;
127
+ if (depth < 3 && isRecord(subProps) && Object.keys(subProps).length > 0) {
128
+ lines.push(...renderFieldComments(subProps, root, `${indent} `, depth + 1));
129
+ }
130
+ }
131
+ return lines;
132
+ }
133
+
134
+ function renderBody(section: ConfigSectionContribution): string[] {
135
+ const { domain, schema, options } = section;
136
+ const key = camelToSnake(domain);
137
+ const jsonSchema = schema === undefined ? undefined : toJsonSchema(schema);
138
+
139
+ if (jsonSchema === undefined) {
140
+ if (isRecord(options.defaultValue)) {
141
+ return [
142
+ `[${key}]`,
143
+ `# (${schema === undefined ? 'no schema — passthrough' : 'schema uses transforms'}; fields below come from the registered default)`,
144
+ ...Object.entries(options.defaultValue).map(
145
+ ([k, v]) => `${camelToSnake(k)} = ${truncate(toTomlValue(v))}`,
146
+ ),
147
+ ];
148
+ }
149
+ if (options.defaultValue !== undefined) {
150
+ return [`${key} = ${truncate(toTomlValue(options.defaultValue))}`];
151
+ }
152
+ return [`[${key}]`, `# (${schema === undefined ? 'no schema — passthrough' : 'schema uses transforms; see the owner file'})`];
153
+ }
154
+
155
+ if (isRecord(jsonSchema.properties) && Object.keys(jsonSchema.properties).length > 0) {
156
+ const defaults = isRecord(options.defaultValue) ? options.defaultValue : {};
157
+ const lines = [`[${key}]`];
158
+ for (const [name, prop] of Object.entries(jsonSchema.properties)) {
159
+ const fieldKey = camelToSnake(name);
160
+ if (defaults[name] !== undefined) {
161
+ lines.push(`${fieldKey} = ${truncate(toTomlValue(defaults[name]))}`);
162
+ continue;
163
+ }
164
+ const resolved = resolveRef(prop, jsonSchema);
165
+ const subProps = asJsonSchema(resolved)?.properties;
166
+ if (isRecord(subProps) && Object.keys(subProps).length > 0) {
167
+ lines.push('');
168
+ lines.push(`# [${key}.${fieldKey}]`);
169
+ lines.push(...renderFieldComments(subProps, jsonSchema, ' '));
170
+ continue;
171
+ }
172
+ const itemProps = asJsonSchema(
173
+ resolveRef(asJsonSchema(resolved)?.items, jsonSchema),
174
+ )?.properties;
175
+ if (isRecord(itemProps) && Object.keys(itemProps).length > 0) {
176
+ lines.push(`# ${fieldKey}: object[] — one entry per item:`);
177
+ lines.push(...renderFieldComments(itemProps, jsonSchema, ' '));
178
+ continue;
179
+ }
180
+ lines.push(...renderFieldComments({ [name]: prop }, jsonSchema, ''));
181
+ }
182
+ return lines;
183
+ }
184
+
185
+ if (jsonSchema.additionalProperties !== undefined) {
186
+ const valueSchema = resolveRef(jsonSchema.additionalProperties, jsonSchema);
187
+ const valueProps = asJsonSchema(valueSchema)?.properties;
188
+ const lines = [`[${key}]`];
189
+ if (isRecord(valueProps) && Object.keys(valueProps).length > 0) {
190
+ lines.push('');
191
+ lines.push(`# one [${key}."<name>"] table per entry:`);
192
+ lines.push(`# [${key}."<name>"]`);
193
+ lines.push(...renderFieldComments(valueProps, jsonSchema, ' '));
194
+ } else {
195
+ lines.push(`# <name>: ${describeType(valueSchema)}`);
196
+ }
197
+ return lines;
198
+ }
199
+
200
+ if (jsonSchema.type === 'array') {
201
+ const itemProps = asJsonSchema(resolveRef(jsonSchema.items, jsonSchema))?.properties;
202
+ if (isRecord(itemProps) && Object.keys(itemProps).length > 0) {
203
+ return [
204
+ `# one [[${key}]] table per entry:`,
205
+ `# [[${key}]]`,
206
+ ...renderFieldComments(itemProps, jsonSchema, ' '),
207
+ ];
208
+ }
209
+ }
210
+
211
+ if (options.defaultValue !== undefined) {
212
+ return [`${key} = ${truncate(toTomlValue(options.defaultValue))}`];
213
+ }
214
+ return [`# ${key}: ${describeType(jsonSchema)}`];
215
+ }
216
+
217
+ function renderSection(section: ConfigSectionContribution, owner: string | undefined): string[] {
218
+ const { domain, options } = section;
219
+ const key = camelToSnake(domain);
220
+ const lines: string[] = [RULE];
221
+ lines.push(`# ${domain}${key === domain ? '' : ` (config.toml: ${key})`}`);
222
+ lines.push(`# owner: ${owner ?? '(unresolved)'}`);
223
+ lines.push(`# scope: ${options.scope ?? 'core'}`);
224
+ const hooks: string[] = [];
225
+ if (options.merge !== undefined) hooks.push('custom merge');
226
+ if (options.fromToml !== undefined) hooks.push('custom fromToml');
227
+ if (options.toToml !== undefined) hooks.push('custom toToml');
228
+ if (options.stripEnv !== undefined) hooks.push('stripEnv');
229
+ if (hooks.length > 0) lines.push(`# hooks: ${hooks.join(' · ')}`);
230
+ const deprecations = options.deprecations ?? [];
231
+ if (deprecations.length > 0) {
232
+ lines.push('# deprecations (old key is ignored + warns; rename manually):');
233
+ for (const deprecation of deprecations) {
234
+ lines.push(`# ${deprecation.key} -> ${deprecation.replacement}`);
235
+ }
236
+ }
237
+ const envRows = flattenEnvBindings(options.env);
238
+ if (envRows.length > 0) {
239
+ lines.push('# env:');
240
+ for (const row of envRows) {
241
+ lines.push(`# ${snakePath(row.field)} <- ${row.env}${row.detail === '' ? '' : ` (${row.detail})`}`);
242
+ }
243
+ }
244
+ lines.push(RULE);
245
+ lines.push('');
246
+ lines.push(...renderBody(section));
247
+ return lines;
248
+ }
249
+
250
+ export async function buildConfigManifest(): Promise<string> {
251
+ await import('../src/index.ts');
252
+ const sections = getConfigSectionContributions().toSorted((a, b) =>
253
+ a.domain.localeCompare(b.domain),
254
+ );
255
+ const overlays = getConfigOverlayContributions();
256
+ const sectionOwners = scanSectionOwners();
257
+ const overlayOwners = scanOverlayOwners();
258
+
259
+ const out: string[] = [
260
+ '# Config Section Manifest',
261
+ '#',
262
+ '# Generated by scripts/gen-config-manifest.mts — do not edit by hand.',
263
+ '# Regenerate with: pnpm --filter @moonshot-ai/agent-core-v2 gen:config-manifest',
264
+ '#',
265
+ '# One [table] per registered config section, in the on-disk config.toml shape',
266
+ '# (snake_case keys). Un-commented assignments are registered defaults;',
267
+ '# commented "# field: type" lines describe the remaining schema fields.',
268
+ '# Values resolve as: default -> config.toml -> env overlay -> memory.',
269
+ '',
270
+ `# Index (${sections.length} sections · ${overlays.length} overlay(s))`,
271
+ ];
272
+ const width = Math.max(...sections.map((s) => s.domain.length));
273
+ for (const { domain } of sections) {
274
+ out.push(`# ${domain.padEnd(width)} ${sectionOwners.get(domain) ?? '(unresolved)'}`);
275
+ }
276
+ for (const [ident, file] of overlayOwners) {
277
+ out.push(`# ${'(overlay) ' + ident} ${file}`);
278
+ }
279
+ out.push('');
280
+
281
+ for (const section of sections) {
282
+ out.push(...renderSection(section, sectionOwners.get(section.domain)));
283
+ out.push('');
284
+ }
285
+ return out.join('\n');
286
+ }
287
+
288
+ async function main(): Promise<void> {
289
+ const check = process.argv.includes('--check');
290
+ const manifest = await buildConfigManifest();
291
+ if (check) {
292
+ let current: string | undefined;
293
+ try {
294
+ current = readFileSync(MANIFEST_PATH, 'utf-8');
295
+ } catch {
296
+ current = undefined;
297
+ }
298
+ if (current !== manifest) {
299
+ console.error(
300
+ `[gen-config-manifest] ${relative(process.cwd(), MANIFEST_PATH)} is stale. ` +
301
+ 'Regenerate with `pnpm --filter @moonshot-ai/agent-core-v2 gen:config-manifest`.',
302
+ );
303
+ process.exit(1);
304
+ }
305
+ console.log('[gen-config-manifest] up to date');
306
+ return;
307
+ }
308
+ writeFileSync(MANIFEST_PATH, manifest);
309
+ console.log(`[gen-config-manifest] wrote ${relative(process.cwd(), MANIFEST_PATH)}`);
310
+ }
311
+
312
+ if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
313
+ await main();
314
+ }
packages/agent-core-v2/scripts/gen-contract-types.mjs ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { execFileSync } from 'node:child_process';
2
+ import {
3
+ cpSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ readdirSync,
7
+ readFileSync,
8
+ rmSync,
9
+ statSync,
10
+ } from 'node:fs';
11
+ import { dirname, join, relative } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { createRequire } from 'node:module';
14
+
15
+ import { Project, SyntaxKind } from 'ts-morph';
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const PKG = join(__dirname, '..');
19
+ const SRC = join(PKG, 'src');
20
+ const TMP = join(PKG, '.contract-types-tmp');
21
+ const TSCONFIG = join(PKG, 'tsconfig.contract.json');
22
+
23
+ const repoRoot = join(PKG, '..', '..');
24
+ const defaultOut = join(repoRoot, '..', 'kimi-code-mini-bench', 'types', 'agent-core-v2');
25
+ const OUT = process.argv[2] ? join(process.cwd(), process.argv[2]) : defaultOut;
26
+
27
+ const require = createRequire(import.meta.url);
28
+ const tscBin = require.resolve('typescript/bin/tsc');
29
+
30
+ function log(msg) {
31
+ console.log(`[gen-contract-types] ${msg}`);
32
+ }
33
+
34
+ function walk(dir, out) {
35
+ for (const entry of readdirSync(dir)) {
36
+ const p = join(dir, entry);
37
+ const s = statSync(p);
38
+ if (s.isDirectory()) walk(p, out);
39
+ else out.push(p);
40
+ }
41
+ }
42
+
43
+ rmSync(TMP, { recursive: true, force: true });
44
+ mkdirSync(TMP, { recursive: true });
45
+ log(`emitting declarations via tsc -> ${relative(PKG, TMP)}`);
46
+ try {
47
+ execFileSync(process.execPath, [tscBin, '-p', TSCONFIG, '--outDir', TMP], {
48
+ cwd: PKG,
49
+ stdio: 'pipe',
50
+ });
51
+ } catch (err) {
52
+ const code = err && typeof err === 'object' && 'status' in err ? err.status : 'unknown';
53
+ log(`tsc exited ${String(code)} (non-fatal; declarations are still emitted)`);
54
+ }
55
+
56
+ log('scanning for registerScopedService(...) bindings');
57
+ const project = new Project();
58
+ project.addSourceFilesAtPaths(join(SRC, '**', '*.ts'));
59
+
60
+ const dropByDts = new Map();
61
+ const implFiles = [];
62
+
63
+ for (const sf of project.getSourceFiles()) {
64
+ const calls = sf
65
+ .getDescendantsOfKind(SyntaxKind.CallExpression)
66
+ .filter((c) => c.getExpression().getText() === 'registerScopedService');
67
+ if (calls.length === 0) continue;
68
+
69
+ implFiles.push(sf.getFilePath());
70
+ const names = new Set();
71
+ for (const call of calls) {
72
+ const args = call.getArguments();
73
+ if (args.length < 3) continue;
74
+ const text = args[2].getText().trim();
75
+ names.add(/^[A-Za-z_$][\w$]*$/.test(text) ? text : '*');
76
+ }
77
+
78
+ const rel = relative(SRC, sf.getFilePath()).replace(/\.ts$/, '.d.ts');
79
+ const dtsPath = join(TMP, rel);
80
+ const existing = dropByDts.get(dtsPath) ?? new Set();
81
+ for (const n of names) existing.add(n);
82
+ dropByDts.set(dtsPath, existing);
83
+ }
84
+
85
+ log(`found ${implFiles.length} impl files`);
86
+
87
+ let scrubbedFiles = 0;
88
+ let scrubbedClasses = 0;
89
+ for (const [dtsPath, names] of dropByDts) {
90
+ if (!existsSync(dtsPath)) continue;
91
+ const dtsProject = new Project();
92
+ const dts = dtsProject.addSourceFileAtPath(dtsPath);
93
+ const dropAll = names.has('*');
94
+ let removed = 0;
95
+ for (const cls of dts.getClasses()) {
96
+ const clsName = cls.getName();
97
+ if (dropAll || (clsName !== undefined && names.has(clsName))) {
98
+ cls.remove();
99
+ removed++;
100
+ }
101
+ }
102
+ if (removed > 0) {
103
+ dts.saveSync();
104
+ scrubbedFiles++;
105
+ scrubbedClasses += removed;
106
+ }
107
+ }
108
+ log(`scrubbed ${scrubbedClasses} impl class(es) across ${scrubbedFiles} file(s)`);
109
+
110
+ function resolveReexportTarget(dtsPath, spec) {
111
+ const clean = spec.endsWith('.js') ? spec.slice(0, -'.js'.length) : spec;
112
+ if (clean.startsWith('.')) return join(dirname(dtsPath), `${clean}.d.ts`);
113
+ if (clean.startsWith('#/')) return join(TMP, `${clean.slice(2)}.d.ts`);
114
+ return undefined;
115
+ }
116
+
117
+ let scrubbedReexports = 0;
118
+ const emittedDts = [];
119
+ walk(TMP, emittedDts);
120
+ const reexportProject = new Project();
121
+ for (const dtsPath of emittedDts) {
122
+ if (!dtsPath.endsWith('.d.ts')) continue;
123
+ const dts = reexportProject.addSourceFileAtPath(dtsPath);
124
+ let changed = false;
125
+ for (const exp of dts.getExportDeclarations()) {
126
+ const spec = exp.getModuleSpecifierValue();
127
+ if (spec === undefined) continue;
128
+ const target = resolveReexportTarget(dtsPath, spec);
129
+ const names = target === undefined ? undefined : dropByDts.get(target);
130
+ if (names === undefined) continue;
131
+ let removedHere = false;
132
+ for (const specifier of exp.getNamedExports()) {
133
+ const name = specifier.getNameNode().getText();
134
+ if (names.has('*') || names.has(name)) {
135
+ specifier.remove();
136
+ removedHere = true;
137
+ scrubbedReexports++;
138
+ }
139
+ }
140
+ if (removedHere && exp.getNamedExports().length === 0) exp.remove();
141
+ changed = changed || removedHere;
142
+ }
143
+ if (changed) dts.saveSync();
144
+ }
145
+ log(`scrubbed ${scrubbedReexports} re-export(s) of impl classes from alias modules`);
146
+
147
+ rmSync(OUT, { recursive: true, force: true });
148
+ mkdirSync(dirname(OUT), { recursive: true });
149
+ cpSync(TMP, OUT, { recursive: true });
150
+
151
+ const emitted = [];
152
+ walk(OUT, emitted);
153
+ const dtsCount = emitted.filter((f) => f.endsWith('.d.ts')).length;
154
+ log(`wrote ${dtsCount} declaration file(s) -> ${OUT}`);
155
+
156
+ const leaks = [];
157
+ for (const [dtsPath, names] of dropByDts) {
158
+ const outPath = join(OUT, relative(TMP, dtsPath));
159
+ if (!existsSync(outPath) || names.has('*')) continue;
160
+ const text = readFileSync(outPath, 'utf8');
161
+ for (const n of names) {
162
+ const re = new RegExp(`declare\\s+class\\s+${n}\\b`);
163
+ if (re.test(text)) leaks.push(`${relative(OUT, outPath)} still declares ${n}`);
164
+ }
165
+ }
166
+ if (leaks.length > 0) {
167
+ log(`WARNING: ${leaks.length} possible leak(s):`);
168
+ for (const l of leaks) log(` - ${l}`);
169
+ } else {
170
+ log('leak check passed: no registered impl class survives in its declaring file');
171
+ }
packages/agent-core-v2/scripts/gen-state-manifest.mts ADDED
@@ -0,0 +1,882 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { join, relative } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+
5
+ import {
6
+ type CallExpression,
7
+ Node,
8
+ Project,
9
+ SyntaxKind,
10
+ ts,
11
+ type Identifier,
12
+ type PropertyAccessExpression,
13
+ type Signature,
14
+ type SourceFile,
15
+ type Symbol as MorphSymbol,
16
+ type Type as MorphType,
17
+ type TypeChecker,
18
+ type VariableDeclaration,
19
+ } from 'ts-morph';
20
+
21
+ const PKG = join(import.meta.dirname, '..');
22
+ const REPO_ROOT = join(PKG, '..', '..');
23
+ const SRC = join(PKG, 'src');
24
+ export const MANIFEST_PATH = join(PKG, 'docs', 'state-manifest.d.ts');
25
+
26
+ const SCOPES = [
27
+ {
28
+ dir: 'app',
29
+ label: 'App',
30
+ interfaceName: 'AppStateSnapshot',
31
+ keyUnionName: 'AppStateKey',
32
+ },
33
+ {
34
+ dir: 'workspace',
35
+ label: 'Workspace',
36
+ interfaceName: 'WorkspaceStateSnapshot',
37
+ keyUnionName: 'WorkspaceStateKey',
38
+ },
39
+ {
40
+ dir: 'session',
41
+ label: 'Session',
42
+ interfaceName: 'SessionStateSnapshot',
43
+ keyUnionName: 'SessionStateKey',
44
+ },
45
+ {
46
+ dir: 'agent',
47
+ label: 'Agent',
48
+ interfaceName: 'AgentStateSnapshot',
49
+ keyUnionName: 'AgentStateKey',
50
+ },
51
+ ] as const;
52
+
53
+ type ScopeDir = (typeof SCOPES)[number]['dir'];
54
+
55
+ interface KeyDef {
56
+ readonly constName: string;
57
+ readonly keyName: string;
58
+ readonly file: string;
59
+ readonly exported: boolean;
60
+ readonly declaration: VariableDeclaration;
61
+ readonly replayable?: {
62
+ readonly durable: boolean;
63
+ readonly undoable: boolean;
64
+ readonly folds: readonly string[];
65
+ };
66
+ }
67
+
68
+ interface Registration {
69
+ readonly def: KeyDef;
70
+ readonly scope: ScopeDir;
71
+ }
72
+
73
+ interface StateManifestModel {
74
+ readonly registrations: readonly Registration[];
75
+ readonly unregistered: readonly KeyDef[];
76
+ }
77
+
78
+ function scopeDirOf(file: string): ScopeDir | undefined {
79
+ const first = relative(SRC, file).split(/[\\/]/)[0];
80
+ return SCOPES.some((scope) => scope.dir === first) ? (first as ScopeDir) : undefined;
81
+ }
82
+
83
+ function isFeaturesFile(file: string): boolean {
84
+ return relative(SRC, file).split(/[\\/]/)[0] === 'features';
85
+ }
86
+
87
+ const FEATURES_RECEIVER_SCOPE: Readonly<Record<string, ScopeDir>> = {
88
+ IAppStateService: 'app',
89
+ IWorkspaceStateService: 'workspace',
90
+ ISessionStateService: 'session',
91
+ IAgentStateService: 'agent',
92
+ };
93
+
94
+ function receiverScope(
95
+ expression: PropertyAccessExpression,
96
+ checker: TypeChecker,
97
+ ): ScopeDir | undefined {
98
+ const typeName = checker.getTypeAtLocation(expression.getExpression()).getSymbol()?.getName();
99
+ return typeName === undefined ? undefined : FEATURES_RECEIVER_SCOPE[typeName];
100
+ }
101
+
102
+ function featuresRegisterScope(
103
+ expression: PropertyAccessExpression,
104
+ checker: TypeChecker,
105
+ sf: SourceFile,
106
+ ): ScopeDir {
107
+ const scope = receiverScope(expression, checker);
108
+ if (scope === undefined) {
109
+ throw new Error(
110
+ `[gen-state-manifest] cannot resolve the state-service scope of '${expression.getText()}' ` +
111
+ `in ${srcRelative(sf.getFilePath())} — register through an ` +
112
+ 'I{App,Workspace,Session,Agent}StateService-typed member.',
113
+ );
114
+ }
115
+ return scope;
116
+ }
117
+
118
+ function srcRelative(file: string): string {
119
+ return relative(PKG, file).split('\\').join('/');
120
+ }
121
+
122
+ function repoRelative(file: string): string {
123
+ return relative(REPO_ROOT, file).split('\\').join('/');
124
+ }
125
+
126
+ function tsFieldKey(key: string): string {
127
+ return /^[$A-Z_a-z][$\w]*$/.test(key) ? key : JSON.stringify(key);
128
+ }
129
+
130
+ function stableSymbolKey(key: string): string {
131
+ const match = /^__@(.+)@\d+$/.exec(key);
132
+ return match === null ? key : `__@${match[1]}`;
133
+ }
134
+
135
+ function collectKeyDefs(project: Project): Map<VariableDeclaration, KeyDef> {
136
+ const defs = new Map<VariableDeclaration, KeyDef>();
137
+ for (const sf of project.getSourceFiles()) {
138
+ const filePath = sf.getFilePath();
139
+ if (scopeDirOf(filePath) === undefined && !isFeaturesFile(filePath)) continue;
140
+ for (const statement of sf.getVariableStatements()) {
141
+ for (const declaration of statement.getDeclarations()) {
142
+ const initializer = declaration.getInitializer();
143
+ if (initializer === undefined || !Node.isCallExpression(initializer)) continue;
144
+ const parsed = parseDefineStateChain(initializer);
145
+ if (parsed === undefined) continue;
146
+ defs.set(declaration, {
147
+ constName: declaration.getName(),
148
+ keyName: parsed.keyName,
149
+ file: sf.getFilePath(),
150
+ exported: statement.isExported(),
151
+ declaration,
152
+ replayable: parsed.replayable,
153
+ });
154
+ }
155
+ }
156
+ }
157
+ return defs;
158
+ }
159
+
160
+ function parseDefineStateChain(
161
+ initializer: CallExpression,
162
+ ): { keyName: string; replayable?: KeyDef['replayable'] } | undefined {
163
+ let durable = true;
164
+ let undoable = false;
165
+ let replayable = false;
166
+ const folds: string[] = [];
167
+ let current: CallExpression = initializer;
168
+ for (;;) {
169
+ const expression = current.getExpression();
170
+ if (Node.isIdentifier(expression) && expression.getText() === 'defineState') {
171
+ const [nameArg] = current.getArguments();
172
+ if (nameArg === undefined || !Node.isStringLiteral(nameArg)) return undefined;
173
+ return {
174
+ keyName: nameArg.getLiteralValue(),
175
+ replayable: replayable ? { durable, undoable, folds } : undefined,
176
+ };
177
+ }
178
+ if (!Node.isPropertyAccessExpression(expression)) return undefined;
179
+ const method = expression.getName();
180
+ if (method === 'replayable') {
181
+ replayable = true;
182
+ const [arg] = current.getArguments();
183
+ if (arg !== undefined && Node.isObjectLiteralExpression(arg)) {
184
+ const durableProp = arg.getProperty('durable');
185
+ if (durableProp !== undefined && Node.isPropertyAssignment(durableProp)) {
186
+ durable = durableProp.getInitializer()?.getText() !== 'false';
187
+ }
188
+ }
189
+ } else if (method === 'undoable') {
190
+ undoable = true;
191
+ } else if (method === 'on') {
192
+ const [eventArg] = current.getArguments();
193
+ if (eventArg !== undefined) folds.unshift(eventArg.getText());
194
+ } else {
195
+ return undefined;
196
+ }
197
+ const inner = expression.getExpression();
198
+ if (!Node.isCallExpression(inner)) return undefined;
199
+ current = inner;
200
+ }
201
+ }
202
+
203
+ function resolveKeyDef(
204
+ identifier: Identifier,
205
+ defs: ReadonlyMap<VariableDeclaration, KeyDef>,
206
+ ): KeyDef | undefined {
207
+ for (const info of identifier.getDefinitions()) {
208
+ const node = info.getDeclarationNode();
209
+ if (node !== undefined && Node.isVariableDeclaration(node)) {
210
+ const def = defs.get(node);
211
+ if (def !== undefined) return def;
212
+ }
213
+ }
214
+ return undefined;
215
+ }
216
+
217
+ function collectRegistrations(
218
+ project: Project,
219
+ defs: ReadonlyMap<VariableDeclaration, KeyDef>,
220
+ ): Registration[] {
221
+ const checker = project.getTypeChecker();
222
+ const registrations: Registration[] = [];
223
+ const seen = new Map<string, string>();
224
+ for (const sf of project.getSourceFiles()) {
225
+ const fileScope = scopeDirOf(sf.getFilePath());
226
+ const featuresFile = isFeaturesFile(sf.getFilePath());
227
+ if (fileScope === undefined && !featuresFile) continue;
228
+ for (const call of sf.getDescendantsOfKind(SyntaxKind.CallExpression)) {
229
+ const expression = call.getExpression();
230
+ if (
231
+ !Node.isPropertyAccessExpression(expression) ||
232
+ expression.getName() !== 'contributeState'
233
+ ) {
234
+ continue;
235
+ }
236
+ const args = call.getArguments();
237
+ const [arg] = args;
238
+ if (args.length !== 1 || arg === undefined || !Node.isIdentifier(arg)) continue;
239
+ const def = resolveKeyDef(arg, defs);
240
+ if (def === undefined) continue;
241
+ const scope = receiverScope(expression, checker) ?? fileScope ?? featuresRegisterScope(expression, checker, sf);
242
+ if (!def.exported) {
243
+ throw new Error(
244
+ `[gen-state-manifest] state key '${def.keyName}' (${srcRelative(def.file)}) is ` +
245
+ 'registered but its key constant is not exported — the manifest cannot reference it.',
246
+ );
247
+ }
248
+ const dedupe = `${scope}:${def.keyName}`;
249
+ const seenFile = seen.get(dedupe);
250
+ if (seenFile !== undefined) {
251
+ if (seenFile === sf.getFilePath()) continue;
252
+ throw new Error(
253
+ `[gen-state-manifest] state key '${def.keyName}' is registered twice in ${scope} scope.`,
254
+ );
255
+ }
256
+ seen.set(dedupe, sf.getFilePath());
257
+ registrations.push({ def, scope });
258
+ }
259
+ }
260
+ return registrations;
261
+ }
262
+
263
+ function createProject(): Project {
264
+ const project = new Project({
265
+ tsConfigFilePath: join(PKG, 'tsconfig.json'),
266
+ skipAddingFilesFromTsConfig: true,
267
+ });
268
+ project.addSourceFilesAtPaths(join(SRC, '**', '*.ts'));
269
+ return project;
270
+ }
271
+
272
+ const NO_TRUNCATION = ts.TypeFormatFlags.NoTruncation;
273
+
274
+ class TypeRenderer {
275
+ private readonly checker: ts.TypeChecker;
276
+ private readonly expanding = new Set<ts.Type>();
277
+ private readonly expandingNamed: ts.Symbol[] = [];
278
+ readonly externals = new Set<string>();
279
+ readonly warnings = new Set<string>();
280
+
281
+ constructor(private readonly project: Project) {
282
+ this.checker = project.getTypeChecker().compilerObject;
283
+ }
284
+
285
+ renderKeyType(def: KeyDef): string {
286
+ const valueType = def.declaration.getType().getTypeArguments()[0];
287
+ if (valueType === undefined) {
288
+ throw new Error(
289
+ `[gen-state-manifest] cannot resolve the value type of '${def.keyName}' (${srcRelative(def.file)}).`,
290
+ );
291
+ }
292
+ return this.renderType(valueType, def.declaration, 0);
293
+ }
294
+
295
+ private renderType(
296
+ type: MorphType,
297
+ location: Node,
298
+ depth: number,
299
+ skipSymbol?: ts.Symbol,
300
+ ): string {
301
+ if (depth > 40) return this.fallback(type, location, 'depth cap');
302
+
303
+ if ((type.getFlags() & ts.TypeFlags.EnumLiteral) !== 0) {
304
+ return this.renderEnumLiteral(type);
305
+ }
306
+
307
+ if (type.isUnion() && (type.getFlags() & ts.TypeFlags.Boolean) !== 0) return 'boolean';
308
+
309
+ if (type.isUnion()) {
310
+ const alias = this.tryRenderAlias(type, location, depth, skipSymbol);
311
+ if (alias !== undefined) return alias;
312
+ const enumUnion = this.tryRenderEnumUnion(type);
313
+ if (enumUnion !== undefined) return enumUnion;
314
+ return this.renderUnionMembers(type.getUnionTypes(), location, depth);
315
+ }
316
+
317
+ if (type.isIntersection()) {
318
+ const alias = this.tryRenderAlias(type, location, depth, skipSymbol);
319
+ if (alias !== undefined) return alias;
320
+ return type
321
+ .getIntersectionTypes()
322
+ .map((member) => this.renderType(member, location, depth + 1))
323
+ .join(' & ');
324
+ }
325
+
326
+ if (this.isLeaf(type)) return this.leafText(type);
327
+
328
+ if (type.isTuple()) {
329
+ const elements = type
330
+ .getTupleElements()
331
+ .map((element) => this.renderType(element, location, depth + 1));
332
+ return `[${elements.join(', ')}]`;
333
+ }
334
+
335
+ if (type.isArray()) {
336
+ const element = type.getArrayElementType();
337
+ if (element === undefined) return this.fallback(type, location, 'array without element');
338
+ const rendered = this.renderType(element, location, depth + 1);
339
+ const text =
340
+ element.isUnion() || element.isIntersection() ? `(${rendered})[]` : `${rendered}[]`;
341
+ return type.getSymbol()?.getName() === 'ReadonlyArray' ? `readonly ${text}` : text;
342
+ }
343
+
344
+ if (type.isObject()) {
345
+ return this.renderObjectType(type, location, depth, skipSymbol);
346
+ }
347
+
348
+ return this.fallback(type, location, 'unhandled type kind');
349
+ }
350
+
351
+ private renderUnionMembers(
352
+ members: readonly MorphType[],
353
+ location: Node,
354
+ depth: number,
355
+ ): string {
356
+ const booleanLiterals = members.filter((m) => m.isBooleanLiteral());
357
+ const collapseBoolean =
358
+ booleanLiterals.length === 2 &&
359
+ new Set(booleanLiterals.map((m) => this.leafText(m))).size === 2;
360
+ const rest = collapseBoolean ? members.filter((m) => !m.isBooleanLiteral()) : members;
361
+ const rank = (type: MorphType): number => (type.isNull() ? 1 : type.isUndefined() ? 2 : 0);
362
+ const rendered = rest
363
+ .map((member) => ({ member, rank: rank(member) }))
364
+ .toSorted((a, b) => a.rank - b.rank)
365
+ .map(({ member }) => ({
366
+ member,
367
+ text: this.renderType(member, location, depth + 1),
368
+ }));
369
+ const multi = rendered.length + (collapseBoolean ? 1 : 0) > 1;
370
+ const parts = rendered.map(({ member, text }) =>
371
+ multi && this.needsParensInUnion(member) ? `(${text})` : text,
372
+ );
373
+ if (collapseBoolean) parts.unshift('boolean');
374
+ return [...new Set(parts)].join(' | ');
375
+ }
376
+
377
+ private isLeaf(type: MorphType): boolean {
378
+ const flags = type.getFlags();
379
+ return (
380
+ type.isString() ||
381
+ type.isNumber() ||
382
+ type.isBoolean() ||
383
+ type.isStringLiteral() ||
384
+ type.isNumberLiteral() ||
385
+ type.isBooleanLiteral() ||
386
+ type.isNull() ||
387
+ type.isUndefined() ||
388
+ type.isUnknown() ||
389
+ type.isAny() ||
390
+ type.isNever() ||
391
+ type.isTypeParameter() ||
392
+ (flags &
393
+ (ts.TypeFlags.Void |
394
+ ts.TypeFlags.BigInt |
395
+ ts.TypeFlags.BigIntLiteral |
396
+ ts.TypeFlags.ESSymbol |
397
+ ts.TypeFlags.UniqueESSymbol |
398
+ ts.TypeFlags.TemplateLiteral)) !==
399
+ 0
400
+ );
401
+ }
402
+
403
+ private leafText(type: MorphType): string {
404
+ const text = this.checker.typeToString(type.compilerType, undefined, NO_TRUNCATION);
405
+ if (text.length >= 2 && text.startsWith('"') && text.endsWith('"')) {
406
+ const value = JSON.parse(text) as string;
407
+ return value.includes("'") ? JSON.stringify(value) : `'${value}'`;
408
+ }
409
+ return text;
410
+ }
411
+
412
+ private needsParensInUnion(type: MorphType): boolean {
413
+ return type.isIntersection() || (type.isObject() && type.getCallSignatures().length > 0);
414
+ }
415
+
416
+ private fallback(type: MorphType, location: Node, reason: string): string {
417
+ const text = this.checker.typeToString(type.compilerType, location.compilerNode, NO_TRUNCATION);
418
+ this.warnings.add(`${reason}: fell back to '${text.slice(0, 80)}'`);
419
+ return text;
420
+ }
421
+
422
+ private enumLiteralValue(type: MorphType): string {
423
+ const value = (type.compilerType as ts.LiteralType).value;
424
+ if (typeof value === 'string') {
425
+ return value.includes("'") ? JSON.stringify(value) : `'${value}'`;
426
+ }
427
+ if (typeof value === 'number') return String(value);
428
+ return this.leafText(type);
429
+ }
430
+
431
+ private enumDeclOf(type: MorphType): Node | undefined {
432
+ const memberDecl = type.getSymbol()?.getDeclarations()[0];
433
+ if (memberDecl === undefined || !Node.isEnumMember(memberDecl)) return undefined;
434
+ return memberDecl.getParent();
435
+ }
436
+
437
+ private renderEnumLiteral(type: MorphType): string {
438
+ const enumDecl = this.enumDeclOf(type);
439
+ const text = this.enumLiteralValue(type);
440
+ if (enumDecl !== undefined && Node.isEnumDeclaration(enumDecl)) {
441
+ const sym = enumDecl.getSymbol();
442
+ if (sym !== undefined) {
443
+ return `/* ${sym.getName()} — ${repoRelative(enumDecl.getSourceFile().getFilePath())} */ ${text}`;
444
+ }
445
+ }
446
+ return text;
447
+ }
448
+
449
+ private tryRenderEnumUnion(type: MorphType): string | undefined {
450
+ const members = type.getUnionTypes();
451
+ if (members.length === 0) return undefined;
452
+ let enumDecl: Node | undefined;
453
+ for (const member of members) {
454
+ if ((member.getFlags() & ts.TypeFlags.EnumLiteral) === 0) return undefined;
455
+ const parent = this.enumDeclOf(member);
456
+ if (parent === undefined) return undefined;
457
+ if (enumDecl === undefined) enumDecl = parent;
458
+ else if (parent !== enumDecl) return undefined;
459
+ }
460
+ if (enumDecl === undefined || !Node.isEnumDeclaration(enumDecl)) return undefined;
461
+ if (enumDecl.getMembers().length !== members.length) return undefined;
462
+ const sym = enumDecl.getSymbol();
463
+ if (sym === undefined) return undefined;
464
+ const values = members.map((member) => this.enumLiteralValue(member));
465
+ return `/* ${sym.getName()} — ${repoRelative(enumDecl.getSourceFile().getFilePath())} */ ${values.join(' | ')}`;
466
+ }
467
+
468
+ private classify(sym: MorphSymbol): 'named' | 'ambient' | 'inline' {
469
+ const decls = sym.getDeclarations();
470
+ if (decls.length === 0) return 'inline';
471
+ const inNodeModules = (file: string) => /[\\/]node_modules[\\/]/.test(file);
472
+ if (decls.every((d) => !inNodeModules(d.getSourceFile().getFilePath()))) {
473
+ const named = decls.some(
474
+ (d) =>
475
+ Node.isInterfaceDeclaration(d) ||
476
+ Node.isClassDeclaration(d) ||
477
+ Node.isTypeAliasDeclaration(d) ||
478
+ Node.isEnumDeclaration(d),
479
+ );
480
+ const generic = decls.some(
481
+ (d) =>
482
+ (Node.isInterfaceDeclaration(d) ||
483
+ Node.isClassDeclaration(d) ||
484
+ Node.isTypeAliasDeclaration(d)) &&
485
+ d.getTypeParameters().length > 0,
486
+ );
487
+ return named && !generic ? 'named' : 'inline';
488
+ }
489
+ if (decls.every((d) => inNodeModules(d.getSourceFile().getFilePath()))) return 'ambient';
490
+ return 'inline';
491
+ }
492
+
493
+ private noteExternal(sym: MorphSymbol): void {
494
+ const isTsLib = (file: string) => /[\\/]node_modules[\\/]typescript[\\/]lib[\\/]/.test(file);
495
+ if (sym.getDeclarations().some((d) => !isTsLib(d.getSourceFile().getFilePath()))) {
496
+ this.externals.add(sym.getName());
497
+ }
498
+ }
499
+
500
+ private renderNamed(sym: MorphSymbol, expand: () => string): string {
501
+ const decl = sym.getDeclarations()[0];
502
+ const origin =
503
+ decl !== undefined
504
+ ? repoRelative(decl.getSourceFile().getFilePath())
505
+ : '(unknown source)';
506
+ const name = sym.getName();
507
+ if (this.expandingNamed.includes(sym.compilerSymbol)) {
508
+ return `/* ${name} — recursive (${origin}) */ unknown`;
509
+ }
510
+ this.expandingNamed.push(sym.compilerSymbol);
511
+ try {
512
+ return `/* ${name} — ${origin} */ ${expand()}`;
513
+ } finally {
514
+ this.expandingNamed.pop();
515
+ }
516
+ }
517
+
518
+ private tryRenderAlias(
519
+ type: MorphType,
520
+ location: Node,
521
+ depth: number,
522
+ skipSymbol?: ts.Symbol,
523
+ ): string | undefined {
524
+ const alias = type.getAliasSymbol();
525
+ if (alias === undefined || alias.compilerSymbol === skipSymbol) return undefined;
526
+ const kind = this.classify(alias);
527
+ if (kind === 'named') {
528
+ const decl = alias.getDeclarations().find((d) => Node.isTypeAliasDeclaration(d));
529
+ if (decl === undefined || !Node.isTypeAliasDeclaration(decl)) return undefined;
530
+ return this.renderNamed(alias, () =>
531
+ this.renderType(decl.getType(), decl, depth + 1, alias.compilerSymbol),
532
+ );
533
+ }
534
+ if (kind === 'ambient') {
535
+ const args = type.getAliasTypeArguments();
536
+ if (args.length === 0) return undefined;
537
+ this.noteExternal(alias);
538
+ const rendered = args.map((arg) => this.renderType(arg, location, depth + 1));
539
+ return `${alias.getName()}<${rendered.join(', ')}>`;
540
+ }
541
+ return undefined;
542
+ }
543
+
544
+ private renderObjectType(
545
+ type: MorphType,
546
+ location: Node,
547
+ depth: number,
548
+ skipSymbol?: ts.Symbol,
549
+ ): string {
550
+ const alias = this.tryRenderAlias(type, location, depth, skipSymbol);
551
+ if (alias !== undefined) return alias;
552
+ const sym = type.getSymbol();
553
+ const typeArgs = type.getTypeArguments();
554
+ const anonymous = sym === undefined || /^__(type|object)$/.test(sym.getName());
555
+ if (!anonymous && sym.compilerSymbol !== skipSymbol) {
556
+ const kind = this.classify(sym);
557
+ if (kind === 'named' && typeArgs.length === 0) {
558
+ return this.renderNamed(sym, () => this.renderStructural(type, location, depth));
559
+ }
560
+ if (kind === 'ambient') {
561
+ this.noteExternal(sym);
562
+ const name = sym.getName();
563
+ if (typeArgs.length === 0) return name;
564
+ const args = typeArgs.map((arg) => this.renderType(arg, location, depth + 1));
565
+ return `${name}<${args.join(', ')}>`;
566
+ }
567
+ }
568
+ return this.renderStructural(type, location, depth);
569
+ }
570
+
571
+ private renderStructural(type: MorphType, location: Node, depth: number): string {
572
+ if (this.expanding.has(type.compilerType)) {
573
+ return this.fallback(type, location, 'cycle expanding');
574
+ }
575
+ this.expanding.add(type.compilerType);
576
+ try {
577
+ const callSignatures = type.getCallSignatures();
578
+ const props = type.getProperties().filter((prop) => this.isPublic(prop));
579
+ const stringIndex = type.getStringIndexType();
580
+ const numberIndex = type.getNumberIndexType();
581
+ if (
582
+ callSignatures.length > 0 &&
583
+ props.length === 0 &&
584
+ stringIndex === undefined &&
585
+ numberIndex === undefined
586
+ ) {
587
+ if (callSignatures.length === 1 && callSignatures[0] !== undefined) {
588
+ return this.renderSignature(callSignatures[0], location, depth, 'arrow');
589
+ }
590
+ return callSignatures
591
+ .map((sig) => `(${this.renderSignature(sig, location, depth, 'arrow')})`)
592
+ .join(' & ');
593
+ }
594
+ const body = this.renderObjectBody(type, location, depth, callSignatures, props);
595
+ if (body.length === 0) return '{}';
596
+ return `{\n${body.join('\n')}\n}`;
597
+ } finally {
598
+ this.expanding.delete(type.compilerType);
599
+ }
600
+ }
601
+
602
+ private renderObjectBody(
603
+ type: MorphType,
604
+ location: Node,
605
+ depth: number,
606
+ callSignatures?: readonly Signature[],
607
+ props?: readonly MorphSymbol[],
608
+ ): string[] {
609
+ const lines: string[] = [];
610
+ for (const sig of callSignatures ?? type.getCallSignatures()) {
611
+ lines.push(` ${this.renderSignature(sig, location, depth, 'call')};`);
612
+ }
613
+ for (const prop of props ?? type.getProperties().filter((p) => this.isPublic(p))) {
614
+ const decl = prop.getDeclarations()[0];
615
+ const at = decl ?? location;
616
+ const propType = prop.getTypeAtLocation(at);
617
+ const optional = (prop.getFlags() & ts.SymbolFlags.Optional) !== 0;
618
+ const rendered =
619
+ optional && propType.isUnion()
620
+ ? this.renderUnionMembers(
621
+ propType.getUnionTypes().filter((member) => !member.isUndefined()),
622
+ at,
623
+ depth,
624
+ )
625
+ : this.renderType(propType, at, depth + 1);
626
+ const readonly =
627
+ decl !== undefined && Node.isPropertySignature(decl) && decl.isReadonly()
628
+ ? 'readonly '
629
+ : '';
630
+ const propLines = rendered.split('\n');
631
+ propLines[propLines.length - 1] += ';';
632
+ lines.push(
633
+ ` ${readonly}${tsFieldKey(stableSymbolKey(prop.getName()))}${optional ? '?' : ''}: ${propLines[0]}`,
634
+ ...propLines.slice(1).map((line) => ` ${line}`),
635
+ );
636
+ }
637
+ const stringIndex = type.getStringIndexType();
638
+ if (stringIndex !== undefined) {
639
+ lines.push(` [key: string]: ${this.renderType(stringIndex, location, depth + 1)};`);
640
+ }
641
+ const numberIndex = type.getNumberIndexType();
642
+ if (numberIndex !== undefined) {
643
+ lines.push(` [key: number]: ${this.renderType(numberIndex, location, depth + 1)};`);
644
+ }
645
+ return lines;
646
+ }
647
+
648
+ private renderSignature(
649
+ sig: Signature,
650
+ location: Node,
651
+ depth: number,
652
+ style: 'arrow' | 'call',
653
+ ): string {
654
+ const params: string[] = [];
655
+ for (const param of sig.getParameters()) {
656
+ if (param.getName() === 'this') continue;
657
+ const decl = param.getDeclarations()[0];
658
+ const at = decl ?? location;
659
+ const paramType = param.getTypeAtLocation(at);
660
+ let optional = (param.getFlags() & ts.SymbolFlags.Optional) !== 0;
661
+ let rest = false;
662
+ if (decl !== undefined && Node.isParameterDeclaration(decl)) {
663
+ optional = optional || decl.hasQuestionToken() || decl.getInitializer() !== undefined;
664
+ rest = decl.isRestParameter();
665
+ }
666
+ const rendered =
667
+ optional && paramType.isUnion()
668
+ ? this.renderUnionMembers(
669
+ paramType.getUnionTypes().filter((member) => !member.isUndefined()),
670
+ at,
671
+ depth,
672
+ )
673
+ : this.renderType(paramType, at, depth + 1);
674
+ params.push(`${rest ? '...' : ''}${param.getName()}${optional ? '?' : ''}: ${rendered}`);
675
+ }
676
+ const returnType = this.renderType(sig.getReturnType(), location, depth + 1);
677
+ return style === 'arrow'
678
+ ? `(${params.join(', ')}) => ${returnType}`
679
+ : `(${params.join(', ')}): ${returnType}`;
680
+ }
681
+
682
+ private isPublic(prop: MorphSymbol): boolean {
683
+ if (prop.getName().startsWith('#')) return false;
684
+ return !prop.getDeclarations().some((decl) => {
685
+ const modifiers = ts.canHaveModifiers(decl.compilerNode)
686
+ ? ts.getModifiers(decl.compilerNode)
687
+ : undefined;
688
+ return (
689
+ modifiers !== undefined &&
690
+ modifiers.some(
691
+ (m) =>
692
+ m.kind === ts.SyntaxKind.PrivateKeyword || m.kind === ts.SyntaxKind.ProtectedKeyword,
693
+ )
694
+ );
695
+ });
696
+ }
697
+ }
698
+
699
+ function renderManifest(
700
+ model: StateManifestModel,
701
+ project: Project,
702
+ ): { manifest: string; warnings: readonly string[] } {
703
+ const renderer = new TypeRenderer(project);
704
+ const byScope = new Map<ScopeDir, Registration[]>();
705
+ for (const scope of SCOPES) byScope.set(scope.dir, []);
706
+ for (const registration of model.registrations) byScope.get(registration.scope)?.push(registration);
707
+ for (const [dir, regs] of byScope) {
708
+ byScope.set(
709
+ dir,
710
+ regs.toSorted((a, b) => a.def.keyName.localeCompare(b.def.keyName)),
711
+ );
712
+ }
713
+
714
+ const sections: string[] = [];
715
+ for (const scope of SCOPES) {
716
+ const regs = byScope.get(scope.dir) ?? [];
717
+ const lines = [
718
+ `/** ${scope.label}-scope keys registered into I${scope.label}StateService. */`,
719
+ `export interface ${scope.interfaceName} {`,
720
+ ];
721
+ const byFile = new Map<string, Registration[]>();
722
+ for (const r of regs) {
723
+ const list = byFile.get(r.def.file) ?? [];
724
+ list.push(r);
725
+ byFile.set(r.def.file, list);
726
+ }
727
+ for (const file of [...byFile.keys()].toSorted()) {
728
+ lines.push(` // ${srcRelative(file)}`);
729
+ for (const r of byFile.get(file) ?? []) {
730
+ if (r.def.replayable !== undefined) {
731
+ const meta = r.def.replayable;
732
+ const flags = [
733
+ meta.durable ? 'durable' : 'transient',
734
+ ...(meta.undoable ? ['undoable'] : []),
735
+ ];
736
+ lines.push(
737
+ ` // replayable · ${flags.join(' · ')} — folds: ${meta.folds.length > 0 ? meta.folds.join(', ') : '(protocol only)'}`,
738
+ );
739
+ }
740
+ const rendered = renderer.renderKeyType(r.def).split('\n');
741
+ rendered[rendered.length - 1] += ';';
742
+ lines.push(` '${r.def.keyName}': ${rendered[0]}`, ...rendered.slice(1).map((l) => ` ${l}`));
743
+ }
744
+ }
745
+ lines.push('}');
746
+ lines.push('');
747
+ lines.push(`export type ${scope.keyUnionName} = keyof ${scope.interfaceName};`);
748
+ sections.push(lines.join('\n'));
749
+ }
750
+
751
+ const counts = SCOPES.map((s) => `${s.label}: ${byScope.get(s.dir)?.length ?? 0} keys`).join(
752
+ ' · ',
753
+ );
754
+ const out: string[] = [
755
+ '// App, Workspace, Session & Agent State Manifest',
756
+ '//',
757
+ '// Generated by scripts/gen-state-manifest.mts — do not edit by hand.',
758
+ '// Regenerate with: pnpm --filter @moonshot-ai/agent-core-v2 gen:state-manifest',
759
+ '//',
760
+ '// Every state key registered into the App-scope IAppStateService, the',
761
+ '// Workspace-scope IWorkspaceStateService, the Session-scope',
762
+ '// ISessionStateService, or the Agent-scope IAgentStateService (see',
763
+ '// src/_base/state/stateRegistry.ts), collected statically from the',
764
+ '// `states.contributeState(...)` call sites and the replayable key chains — a',
765
+ '// `defineState(...).replayable(...)` key is contributed into the Agent-scope',
766
+ '// service by its owner service at construction, and',
767
+ '// carries a `// replayable · durable|transient · undoable? — folds: ...` line.',
768
+ '// Replayable values are excluded from snapshot()/inspect(). A key defined via',
769
+ '// defineState but never registered nor replayable does not appear here. Each entry shows the',
770
+ '// compile-time StateKey<T> value type fully expanded inline, so the manifest is',
771
+ '// self-contained (no imports, no helper declarations). A named type is marked',
772
+ '// at its expansion site with a `/* TypeName — source/file.ts */` comment; a',
773
+ '// `/* TypeName — recursive (...) */ unknown` marker stops a recursive expansion.',
774
+ '// Lib globals (Map/Set/Record/…) are referenced as-is. Generic instantiations',
775
+ '// expand structurally; classes render as their public instance shape. The',
776
+ '// defining source file heads each group.',
777
+ ];
778
+ if (renderer.externals.size > 0) {
779
+ out.push(
780
+ '//',
781
+ `// External ambient types referenced but not expanded (from node_modules): ${[...renderer.externals].toSorted().join(', ')}`,
782
+ );
783
+ }
784
+ out.push(
785
+ '//',
786
+ '// snapshot() returns JSON-safe deep copies of these values: Maps become plain',
787
+ '// objects (or [key, value] entry arrays when a key is not string/number), Sets',
788
+ '// become arrays, bigints become strings, functions are dropped, circular',
789
+ "// references become '(circular)', and class instances collapse to a '(ClassName)'",
790
+ '// marker — the wire shape of an entry is the JSON projection of the type here.',
791
+ '//',
792
+ `// Index (${counts})`,
793
+ );
794
+ for (const scope of SCOPES) {
795
+ const regs = byScope.get(scope.dir) ?? [];
796
+ const width = Math.max(0, ...regs.map((r) => r.def.keyName.length));
797
+ out.push(`// ${scope.label}`);
798
+ for (const r of regs) {
799
+ out.push(`// ${r.def.keyName.padEnd(width)} ${srcRelative(r.def.file)}`);
800
+ }
801
+ }
802
+ out.push('');
803
+ out.push(...sections.flatMap((section) => [section, '']));
804
+ return { manifest: `${out.join('\n').trimEnd()}\n`, warnings: [...renderer.warnings] };
805
+ }
806
+
807
+ interface BuildResult {
808
+ readonly model: StateManifestModel;
809
+ readonly manifest: string;
810
+ readonly warnings: readonly string[];
811
+ }
812
+
813
+ function buildAll(): BuildResult {
814
+ const project = createProject();
815
+ const defs = collectKeyDefs(project);
816
+ const registrations = collectRegistrations(project, defs);
817
+ const registered = new Set(registrations.map((r) => r.def));
818
+ for (const def of defs.values()) {
819
+ if (def.replayable === undefined) continue;
820
+ if (!registered.has(def)) {
821
+ throw new Error(
822
+ `[gen-state-manifest] replayable state key '${def.keyName}' (${srcRelative(def.file)}) is ` +
823
+ 'never contributed — its owner service must contributeState it into the Agent-scope state service.',
824
+ );
825
+ }
826
+ for (const registration of registrations) {
827
+ if (registration.def === def && registration.scope !== 'agent') {
828
+ throw new Error(
829
+ `[gen-state-manifest] replayable state key '${def.keyName}' (${srcRelative(def.file)}) is ` +
830
+ `contributed into the ${registration.scope} scope — replayable keys belong to the Agent scope.`,
831
+ );
832
+ }
833
+ }
834
+ }
835
+ const unregistered = [...defs.values()].filter((def) => !registered.has(def));
836
+ const model: StateManifestModel = { registrations, unregistered };
837
+ const { manifest, warnings } = renderManifest(model, project);
838
+ return { model, manifest, warnings };
839
+ }
840
+
841
+ export function buildStateManifest(): string {
842
+ return buildAll().manifest;
843
+ }
844
+
845
+ function main(): void {
846
+ const check = process.argv.includes('--check');
847
+ const { model, manifest, warnings } = buildAll();
848
+ for (const warning of warnings) {
849
+ console.warn(`[gen-state-manifest] warning: ${warning}`);
850
+ }
851
+ if (check) {
852
+ let current: string | undefined;
853
+ try {
854
+ current = readFileSync(MANIFEST_PATH, 'utf-8');
855
+ } catch {
856
+ current = undefined;
857
+ }
858
+ if (current !== manifest) {
859
+ console.error(
860
+ `[gen-state-manifest] ${relative(process.cwd(), MANIFEST_PATH)} is stale. ` +
861
+ 'Regenerate with `pnpm --filter @moonshot-ai/agent-core-v2 gen:state-manifest`.',
862
+ );
863
+ process.exit(1);
864
+ }
865
+ console.log('[gen-state-manifest] up to date');
866
+ return;
867
+ }
868
+ writeFileSync(MANIFEST_PATH, manifest);
869
+ console.log(`[gen-state-manifest] wrote ${relative(process.cwd(), MANIFEST_PATH)}`);
870
+ if (model.unregistered.length > 0) {
871
+ console.log(
872
+ `[gen-state-manifest] note: ${model.unregistered.length} defineState key(s) never registered (excluded):`,
873
+ );
874
+ for (const def of model.unregistered) {
875
+ console.log(` - ${def.keyName} (${srcRelative(def.file)})`);
876
+ }
877
+ }
878
+ }
879
+
880
+ if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
881
+ main();
882
+ }
packages/agent-core-v2/scripts/gen-wire-manifest.mts ADDED
@@ -0,0 +1,1039 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join, relative } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+
5
+ import { EVENT2_REGISTRY } from '#/app/event/event2';
6
+
7
+ import {
8
+ asJsonSchema,
9
+ describeType,
10
+ isRecord,
11
+ resolveRef,
12
+ toJsonSchema,
13
+ truncate,
14
+ type JsonSchema,
15
+ } from './lib/jsonSchema.mts';
16
+
17
+ const PKG = join(import.meta.dirname, '..');
18
+ const SRC = join(PKG, 'src');
19
+ export const MANIFEST_PATH = join(PKG, 'docs', 'wire-manifest.d.ts');
20
+
21
+ function walk(dir: string, out: string[] = []): string[] {
22
+ for (const entry of readdirSync(dir)) {
23
+ const p = join(dir, entry);
24
+ if (statSync(p).isDirectory()) walk(p, out);
25
+ else if (entry.endsWith('.ts')) out.push(p);
26
+ }
27
+ return out;
28
+ }
29
+
30
+ const TYPE_DECL_RE = /static\s+override\s+readonly\s+type\s*=\s*'([^']+)'/g;
31
+ const DURABLE_DECL_RE = /static\s+override\s+readonly\s+durable\s*=\s*true/;
32
+ const CLASS_DECL_RE = /class\s+(\w+)\s+extends\s+(?:AgentEvent2|Event2)/g;
33
+
34
+ function scanEventDeclarations(): {
35
+ owners: Map<string, string>;
36
+ importFiles: string[];
37
+ durableTypes: Set<string>;
38
+ classTypes: Map<string, string>;
39
+ } {
40
+ const owners = new Map<string, string>();
41
+ const importFiles: string[] = [];
42
+ const durableTypes = new Set<string>();
43
+ const classTypes = new Map<string, string>();
44
+ for (const file of walk(SRC)) {
45
+ const source = readFileSync(file, 'utf-8');
46
+ const matches = [...source.matchAll(TYPE_DECL_RE)];
47
+ const hasStates = source.includes('.replayable(');
48
+ if (matches.length > 0 || hasStates) importFiles.push(file);
49
+ for (const [i, match] of matches.entries()) {
50
+ const type = match[1];
51
+ if (type === undefined) continue;
52
+ owners.set(type, relative(PKG, file));
53
+ const windowEnd = i + 1 < matches.length ? matches[i + 1]!.index : source.length;
54
+ if (DURABLE_DECL_RE.test(source.slice(match.index, windowEnd))) durableTypes.add(type);
55
+ }
56
+ const classMatches = [...source.matchAll(CLASS_DECL_RE)];
57
+ for (const [i, match] of classMatches.entries()) {
58
+ const className = match[1];
59
+ if (className === undefined) continue;
60
+ const windowEnd = i + 1 < classMatches.length ? classMatches[i + 1]!.index : source.length;
61
+ const typeMatch = /static\s+override\s+readonly\s+type\s*=\s*'([^']+)'/.exec(
62
+ source.slice(match.index, windowEnd),
63
+ );
64
+ if (typeMatch?.[1] !== undefined) classTypes.set(className, typeMatch[1]);
65
+ }
66
+ }
67
+ return { owners, importFiles, durableTypes, classTypes };
68
+ }
69
+
70
+ function scanMigrationChain(): string {
71
+ const dir = join(SRC, 'wire', 'migration');
72
+ const pairs: { source: string; target: string }[] = [];
73
+ for (const entry of readdirSync(dir)) {
74
+ if (!/^v[\d.]+\.ts$/.test(entry)) continue;
75
+ const source = readFileSync(join(dir, entry), 'utf-8');
76
+ const sourceVersion = /sourceVersion:\s*'([^']+)'/.exec(source)?.[1];
77
+ const targetVersion = /targetVersion:\s*'([^']+)'/.exec(source)?.[1];
78
+ if (sourceVersion !== undefined && targetVersion !== undefined) {
79
+ pairs.push({ source: sourceVersion, target: targetVersion });
80
+ }
81
+ }
82
+ pairs.sort((a, b) => a.source.localeCompare(b.source, undefined, { numeric: true }));
83
+ const chain = pairs.flatMap((p, i) => (i === 0 ? [p.source, p.target] : [p.target]));
84
+ return chain.join(' -> ');
85
+ }
86
+
87
+ interface ReplayableStateScan {
88
+ readonly keyName: string;
89
+ readonly constName: string;
90
+ readonly undoable: boolean;
91
+ readonly blobs: boolean;
92
+ readonly foldClasses: string[];
93
+ }
94
+
95
+ const ON_FOLD_RE = /\.on\(\s*([A-Za-z_$][\w$]*)/g;
96
+ const KEY_ON_RE = /\b([A-Za-z_$][\w$]*)\.on\(\s*([A-Za-z_$][\w$]*)/g;
97
+ const PROTOCOL_EVENT_RE = /(?:appendMessage|applyCompaction|clear|undo):\s*([A-Za-z_$][\w$]*)/g;
98
+
99
+ function readCallArguments(text: string, parenIndex: number): string {
100
+ let depth = 0;
101
+ for (let i = parenIndex; i < text.length; i++) {
102
+ const ch = text[i];
103
+ if (ch === "'" || ch === '"' || ch === '`') {
104
+ const quote = ch;
105
+ i += 1;
106
+ while (i < text.length && text[i] !== quote) {
107
+ if (text[i] === '\\') i += 1;
108
+ i += 1;
109
+ }
110
+ continue;
111
+ }
112
+ if (ch === '(') depth += 1;
113
+ else if (ch === ')') {
114
+ depth -= 1;
115
+ if (depth === 0) return text.slice(parenIndex + 1, i);
116
+ }
117
+ }
118
+ return text.slice(parenIndex + 1);
119
+ }
120
+
121
+ function readChain(source: string, start: number): string {
122
+ let depth = 0;
123
+ const n = source.length;
124
+ for (let i = start; i < n; i++) {
125
+ const ch = source[i];
126
+ if (ch === "'" || ch === '"' || ch === '`') {
127
+ const quote = ch;
128
+ i += 1;
129
+ while (i < n && source[i] !== quote) {
130
+ if (source[i] === '\\') i += 1;
131
+ i += 1;
132
+ }
133
+ continue;
134
+ }
135
+ if (ch === '{' || ch === '(' || ch === '[') depth += 1;
136
+ else if (ch === '}' || ch === ')' || ch === ']') depth = Math.max(0, depth - 1);
137
+ else if (ch === ';' && depth === 0) return source.slice(start, i);
138
+ }
139
+ return source.slice(start);
140
+ }
141
+
142
+ function scanReplayableStates(): ReplayableStateScan[] {
143
+ const states: ReplayableStateScan[] = [];
144
+ const byConst = new Map<string, ReplayableStateScan>();
145
+ const constChainRe =
146
+ /(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*=\s*defineState\(\s*'([^']+)'/g;
147
+ for (const file of walk(SRC)) {
148
+ const source = readFileSync(file, 'utf-8');
149
+ if (!source.includes('.replayable(') && !source.includes('.on(')) continue;
150
+ for (const match of source.matchAll(constChainRe)) {
151
+ const constName = match[1];
152
+ const keyName = match[2];
153
+ if (constName === undefined || keyName === undefined) continue;
154
+ const chain = readChain(source, source.indexOf('defineState', match.index));
155
+ const replayableIndex = chain.indexOf('.replayable(');
156
+ if (replayableIndex === -1) continue;
157
+ const replayableArgs = readCallArguments(chain, replayableIndex + '.replayable'.length);
158
+ const scan: ReplayableStateScan = {
159
+ keyName,
160
+ constName,
161
+ undoable: chain.includes('.undoable('),
162
+ blobs: /\bblobs\s*:/.test(replayableArgs),
163
+ foldClasses: [...chain.matchAll(ON_FOLD_RE)].map((m) => m[1]!),
164
+ };
165
+ states.push(scan);
166
+ byConst.set(constName, scan);
167
+ }
168
+ }
169
+ for (const file of walk(SRC)) {
170
+ const source = readFileSync(file, 'utf-8');
171
+ if (!source.includes('.on(')) continue;
172
+ for (const match of source.matchAll(KEY_ON_RE)) {
173
+ const scan = byConst.get(match[1]!);
174
+ const cls = match[2];
175
+ if (scan === undefined || cls === undefined) continue;
176
+ if (!scan.foldClasses.includes(cls)) scan.foldClasses.push(cls);
177
+ }
178
+ }
179
+ return states;
180
+ }
181
+
182
+ function scanUndoableProtocolTypes(classTypes: ReadonlyMap<string, string>): string[] {
183
+ for (const file of walk(SRC)) {
184
+ const source = readFileSync(file, 'utf-8');
185
+ const index = source.indexOf('registerUndoableProtocol(');
186
+ if (index === -1) continue;
187
+ const window = readChain(source, index);
188
+ const types: string[] = [];
189
+ for (const match of window.matchAll(PROTOCOL_EVENT_RE)) {
190
+ const cls = match[1]!;
191
+ const type = classTypes.get(cls);
192
+ if (type === undefined) {
193
+ throw new Error(
194
+ `[gen-wire-manifest] undoable protocol event class '${cls}' has no resolved type`,
195
+ );
196
+ }
197
+ types.push(type);
198
+ }
199
+ return types;
200
+ }
201
+ throw new Error('[gen-wire-manifest] registerUndoableProtocol call not found under src/');
202
+ }
203
+
204
+ type SketchDict = { [key: string]: Sketch };
205
+ type Sketch = string | SketchDict | [Sketch];
206
+
207
+ const TYPE_KEY = '_type';
208
+ const MORE_KEY = '…';
209
+
210
+ function stringifySketch(sketch: Sketch): string {
211
+ if (typeof sketch === 'string') return sketch;
212
+ if (Array.isArray(sketch)) {
213
+ const inner = stringifySketch(sketch[0]);
214
+ return inner.includes('|') ? `(${inner})[]` : `${inner}[]`;
215
+ }
216
+ return `{ ${Object.entries(sketch)
217
+ .map(([k, v]) => `${k}: ${stringifySketch(v)}`)
218
+ .join(', ')} }`;
219
+ }
220
+
221
+ function sketchFromJsonSchema(schema: unknown, root: JsonSchema, depth: number): Sketch {
222
+ const resolved = resolveRef(schema, root);
223
+ const s = asJsonSchema(resolved);
224
+ if (s !== undefined && depth < 4) {
225
+ if (isRecord(s.properties) && Object.keys(s.properties).length > 0) {
226
+ const required = new Set(Array.isArray(s.required) ? s.required : []);
227
+ const dict: SketchDict = {};
228
+ for (const [name, prop] of Object.entries(s.properties)) {
229
+ dict[required.has(name) ? name : `${name}?`] = sketchFromJsonSchema(prop, root, depth + 1);
230
+ }
231
+ return dict;
232
+ }
233
+ if (s.type === 'array' && s.items !== undefined) {
234
+ const inner = sketchFromJsonSchema(s.items, root, depth + 1);
235
+ if (typeof inner !== 'string') return [inner];
236
+ }
237
+ }
238
+ return describeType(resolved, tsQuote);
239
+ }
240
+
241
+ function buildPayloadSketch(
242
+ schema: unknown,
243
+ staticSketch?: string | Map<string, Sketch>,
244
+ ): Sketch {
245
+ const jsonSchema = toJsonSchema(schema);
246
+ if (jsonSchema === undefined) {
247
+ if (typeof staticSketch === 'string') return staticSketch;
248
+ if (staticSketch !== undefined && staticSketch.size > 0) {
249
+ return Object.fromEntries(staticSketch);
250
+ }
251
+ return '(schema uses transforms; see the owner file)';
252
+ }
253
+ if (isRecord(jsonSchema.properties) && Object.keys(jsonSchema.properties).length > 0) {
254
+ const required = new Set(Array.isArray(jsonSchema.required) ? jsonSchema.required : []);
255
+ const dict: SketchDict = {};
256
+ for (const [name, prop] of Object.entries(jsonSchema.properties)) {
257
+ dict[required.has(name) ? name : `${name}?`] = sketchFromJsonSchema(prop, jsonSchema, 0);
258
+ }
259
+ return dict;
260
+ }
261
+ if (
262
+ jsonSchema.type === 'object' &&
263
+ (jsonSchema.additionalProperties === undefined || jsonSchema.additionalProperties === false)
264
+ ) {
265
+ return {};
266
+ }
267
+ return describeType(jsonSchema, tsQuote);
268
+ }
269
+
270
+ function pascalCase(name: string): string {
271
+ return name
272
+ .split(/[^A-Za-z0-9]+/)
273
+ .filter(Boolean)
274
+ .map((part) => (part[0] ?? '').toUpperCase() + part.slice(1))
275
+ .join('');
276
+ }
277
+
278
+ function tsFieldKey(key: string): string {
279
+ return /^[$A-Z_a-z][$\w]*$/.test(key) ? key : JSON.stringify(key);
280
+ }
281
+
282
+ function sketchStringToTs(text: string): { type: string; doc?: string } {
283
+ let t = text.trim();
284
+ const docs: string[] = [];
285
+ const named = /^([A-Z][$\w]*) = ([\s\S]+)$/.exec(t);
286
+ if (named?.[1] !== undefined && named[2] !== undefined) {
287
+ docs.push(named[1]);
288
+ t = named[2].trim();
289
+ }
290
+ const spread = /^((?:\.\.\.[$\w]+(?: \+ )?)+) & ([\s\S]+)$/.exec(t);
291
+ if (spread?.[1] !== undefined && spread[2] !== undefined) {
292
+ docs.push(`shared base: ${spread[1]}`);
293
+ t = spread[2].trim();
294
+ }
295
+ t = t.replaceAll(/union on [$\w]+: /g, '');
296
+ t = t.replaceAll(/\brecord</g, 'Record<');
297
+ t = t.replaceAll(/\binteger\b/g, 'number');
298
+ return { type: t, doc: docs.length > 0 ? docs.join(' · ') : undefined };
299
+ }
300
+
301
+ function renderTsType(sketch: Sketch, indent: string): { doc?: string; lines: string[] } {
302
+ if (typeof sketch === 'string') {
303
+ const { type, doc } = sketchStringToTs(sketch);
304
+ return { doc, lines: [type] };
305
+ }
306
+ if (Array.isArray(sketch)) {
307
+ const inner = renderTsType(sketch[0], indent);
308
+ const lines = [...inner.lines];
309
+ lines[lines.length - 1] += '[]';
310
+ return { doc: inner.doc, lines };
311
+ }
312
+ const doc = typeof sketch[TYPE_KEY] === 'string' ? sketch[TYPE_KEY] : undefined;
313
+ const lines = ['{'];
314
+ emitTsDict(lines, sketch, indent + ' ');
315
+ lines.push(`${indent}}`);
316
+ return { doc, lines };
317
+ }
318
+
319
+ function emitTsDict(lines: string[], dict: SketchDict, indent: string): void {
320
+ for (const [key, sketch] of Object.entries(dict)) {
321
+ if (key === MORE_KEY) {
322
+ lines.push(`${indent}// …`);
323
+ continue;
324
+ }
325
+ if (key === TYPE_KEY) continue;
326
+ if (key.startsWith('...')) {
327
+ lines.push(`${indent}// spread: ${key}`);
328
+ continue;
329
+ }
330
+ const optional = key.endsWith('?');
331
+ const fieldKey = tsFieldKey(optional ? key.slice(0, -1) : key);
332
+ const { doc, lines: typeLines } = renderTsType(sketch, indent);
333
+ if (doc !== undefined) lines.push(`${indent}/** ${doc} */`);
334
+ lines.push(`${indent}${fieldKey}${optional ? '?' : ''}: ${typeLines[0]}${typeLines.length === 1 ? ';' : ''}`);
335
+ if (typeLines.length > 1) {
336
+ lines.push(...typeLines.slice(1, -1));
337
+ lines.push(`${typeLines[typeLines.length - 1]};`);
338
+ }
339
+ }
340
+ }
341
+
342
+ function renderPayloadDecl(
343
+ entry: { type: string },
344
+ owner: string | undefined,
345
+ states: string[],
346
+ flags: string[],
347
+ sketch: Sketch,
348
+ ): string[] {
349
+ const name = `${pascalCase(entry.type)}Payload`;
350
+ const nameField = `_name: '${entry.type}';`;
351
+ const header = [
352
+ '/**',
353
+ ` * states: ${states.length > 0 ? states.join(', ') : '(none)'}${flags.length > 0 ? ` · ${flags.join(' · ')}` : ''}`,
354
+ ` * owner: ${owner ?? '(unresolved)'}`,
355
+ ];
356
+ if (typeof sketch === 'string') {
357
+ const { type, doc } = sketchStringToTs(sketch);
358
+ if (type.startsWith('(')) {
359
+ header.push(` * ${type.slice(1, -1)}`);
360
+ header.push(' */');
361
+ return [...header, `interface ${name} {\n ${nameField}\n}`, ''];
362
+ }
363
+ if (doc !== undefined) header.push(` * ${doc}`);
364
+ header.push(' */');
365
+ return [...header, `type ${name} = { ${nameField} } & (${type});`, ''];
366
+ }
367
+ if (Array.isArray(sketch)) {
368
+ const inner = renderTsType(sketch[0], ' ');
369
+ const lines = [...inner.lines];
370
+ lines[lines.length - 1] += '[]';
371
+ header.push(' */');
372
+ if (lines.length === 1) {
373
+ return [...header, `type ${name} = { ${nameField} } & (${lines[0]});`, ''];
374
+ }
375
+ return [
376
+ ...header,
377
+ `type ${name} = { ${nameField} } & (${lines[0]}`,
378
+ ...lines.slice(1, -1),
379
+ `${lines[lines.length - 1]});`,
380
+ '',
381
+ ];
382
+ }
383
+ const payloadType = typeof sketch[TYPE_KEY] === 'string' ? sketch[TYPE_KEY] : undefined;
384
+ if (payloadType !== undefined) header.push(` * payload type: ${payloadType}`);
385
+ header.push(' */');
386
+ const lines = [...header, `interface ${name} {`, ` ${nameField}`];
387
+ emitTsDict(lines, sketch, ' ');
388
+ lines.push('}', '');
389
+ return lines;
390
+ }
391
+
392
+ function matchDelimiter(source: string, start: number, open: string, close: string): number {
393
+ let depth = 0;
394
+ for (let i = start; i < source.length; i++) {
395
+ const ch = source[i];
396
+ if (ch === '/' && source[i + 1] === '/') {
397
+ i = source.indexOf('\n', i);
398
+ if (i === -1) return -1;
399
+ continue;
400
+ }
401
+ if (ch === '/' && source[i + 1] === '*') {
402
+ i = source.indexOf('*/', i);
403
+ if (i === -1) return -1;
404
+ i += 1;
405
+ continue;
406
+ }
407
+ if (ch === "'" || ch === '"' || ch === '`') {
408
+ const quote = ch;
409
+ i += 1;
410
+ while (i < source.length && source[i] !== quote) {
411
+ if (source[i] === '\\') i += 1;
412
+ i += 1;
413
+ }
414
+ continue;
415
+ }
416
+ if (ch === open) depth += 1;
417
+ else if (ch === close) {
418
+ depth -= 1;
419
+ if (depth === 0) return i;
420
+ }
421
+ }
422
+ return -1;
423
+ }
424
+
425
+ function splitTopLevel(body: string, separators: readonly string[] = [',']): string[] {
426
+ const parts: string[] = [];
427
+ let depth = 0;
428
+ let partStart = 0;
429
+ const n = body.length;
430
+ for (let i = 0; i < n; i++) {
431
+ const ch = body[i];
432
+ if (ch === "'" || ch === '"' || ch === '`') {
433
+ const quote = ch;
434
+ i += 1;
435
+ while (i < n && body[i] !== quote) {
436
+ if (body[i] === '\\') i += 1;
437
+ i += 1;
438
+ }
439
+ continue;
440
+ }
441
+ if (ch === '{' || ch === '(' || ch === '[' || ch === '<') depth += 1;
442
+ else if (ch === '}' || ch === ')' || ch === ']' || ch === '>') depth = Math.max(0, depth - 1);
443
+ else if (ch !== undefined && depth === 0 && separators.includes(ch)) {
444
+ parts.push(body.slice(partStart, i).trim());
445
+ partStart = i + 1;
446
+ }
447
+ }
448
+ parts.push(body.slice(partStart).trim());
449
+ return parts.filter((p) => p !== '');
450
+ }
451
+
452
+ function splitObjectFields(body: string): Map<string, string> {
453
+ const fields = new Map<string, string>();
454
+ for (const part of splitTopLevel(body)) {
455
+ const keyMatch = /^([$\w]+|'[^']+'|"[^"]+")\s*:/.exec(part);
456
+ if (keyMatch?.[1] !== undefined) {
457
+ const key = keyMatch[1].replace(/^['"]|['"]$/g, '');
458
+ fields.set(key, part.slice(keyMatch[0].length).trim());
459
+ } else if (part.startsWith('...')) {
460
+ fields.set(part, '');
461
+ }
462
+ }
463
+ return fields;
464
+ }
465
+
466
+ function objectBody(text: string, braceIndex: number): string | undefined {
467
+ const end = matchDelimiter(text, braceIndex, '{', '}');
468
+ return end === -1 ? undefined : text.slice(braceIndex + 1, end);
469
+ }
470
+
471
+ function readExpression(source: string, start: number): string {
472
+ let depth = 0;
473
+ const n = source.length;
474
+ for (let i = start; i < n; i++) {
475
+ const ch = source[i];
476
+ if (ch === "'" || ch === '"' || ch === '`') {
477
+ const quote = ch;
478
+ i += 1;
479
+ while (i < n && source[i] !== quote) {
480
+ if (source[i] === '\\') i += 1;
481
+ i += 1;
482
+ }
483
+ continue;
484
+ }
485
+ if (ch === '{' || ch === '(' || ch === '[' || ch === '<') depth += 1;
486
+ else if (ch === '}' || ch === ')' || ch === ']' || ch === '>') depth = Math.max(0, depth - 1);
487
+ else if (ch === ';' && depth === 0) return source.slice(start, i);
488
+ }
489
+ return source.slice(start);
490
+ }
491
+
492
+ function tsQuote(raw: string): string {
493
+ return raw.includes("'") ? JSON.stringify(raw) : `'${raw}'`;
494
+ }
495
+
496
+ function escapeRegExp(raw: string): string {
497
+ return raw.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&');
498
+ }
499
+
500
+ function resolveSchemaLiteral(expr: string, source: string, depth = 0): string | undefined {
501
+ if (depth > 2) return undefined;
502
+ const inline = /^z\.\w*[oO]bject\s*\(/.exec(expr);
503
+ if (inline !== null) {
504
+ const rest = expr.slice(inline[0].length).trimStart();
505
+ if (rest.startsWith('{')) return objectBody(rest, 0);
506
+ const shapeName = /^([$\w]+)/.exec(rest)?.[1];
507
+ if (shapeName !== undefined) {
508
+ const constRe = new RegExp(`const\\s+${shapeName}\\s*(?::[^=;]+)?=\\s*\\{`);
509
+ const m = constRe.exec(source);
510
+ if (m !== null) return objectBody(source, m.index + m[0].length - 1);
511
+ }
512
+ return undefined;
513
+ }
514
+ const ident = /^([$\w]+)$/.exec(expr.trim())?.[1];
515
+ if (ident !== undefined) {
516
+ const constRe = new RegExp(`const\\s+${ident}\\s*(?::[^=;]+)?=\\s*`);
517
+ const m = constRe.exec(source);
518
+ if (m !== null) {
519
+ const rhs = readExpression(source, m.index + m[0].length).trim();
520
+ return resolveSchemaLiteral(rhs, source, depth + 1);
521
+ }
522
+ }
523
+ return undefined;
524
+ }
525
+
526
+ interface Budget {
527
+ remaining: number;
528
+ }
529
+
530
+ function spend(budget: Budget): boolean {
531
+ if (budget.remaining <= 0) return false;
532
+ budget.remaining -= 1;
533
+ return true;
534
+ }
535
+
536
+ const TS_BUDGET = (): Budget => ({ remaining: 24 });
537
+
538
+ const _fileCache = new Map<string, string>();
539
+
540
+ function readCached(file: string): string {
541
+ let text = _fileCache.get(file);
542
+ if (text === undefined) {
543
+ text = readFileSync(file, 'utf-8');
544
+ _fileCache.set(file, text);
545
+ }
546
+ return text;
547
+ }
548
+
549
+ interface TsField {
550
+ readonly type: string;
551
+ readonly optional: boolean;
552
+ }
553
+
554
+ function splitTsTypeFields(body: string): Map<string, TsField> {
555
+ const fields = new Map<string, TsField>();
556
+ for (const part of splitTopLevel(body, [';', ','])) {
557
+ const m = /^(?:readonly\s+)?([$\w]+|'[^']+'|"[^"]+")\s*(\?)?\s*:\s*(.+)$/.exec(part);
558
+ if (m?.[1] !== undefined && m[3] !== undefined) {
559
+ fields.set(m[1].replace(/^['"]|['"]$/g, ''), {
560
+ type: m[3].trim(),
561
+ optional: m[2] !== undefined,
562
+ });
563
+ }
564
+ }
565
+ return fields;
566
+ }
567
+
568
+ const TS_PRIMITIVES = new Set(['string', 'number', 'boolean', 'unknown', 'any', 'null', 'undefined', 'void']);
569
+
570
+ function renderTsFields(
571
+ fields: Map<string, TsField>,
572
+ file: string,
573
+ budget: Budget,
574
+ charBudget: number,
575
+ depth: number,
576
+ ): SketchDict {
577
+ const dict: SketchDict = {};
578
+ let count = 0;
579
+ for (const [name, f] of fields) {
580
+ if (count >= 8) {
581
+ dict[MORE_KEY] = '…';
582
+ break;
583
+ }
584
+ count += 1;
585
+ dict[`${name}${f.optional ? '?' : ''}`] = summarizeTsTypeExpr(
586
+ f.type,
587
+ file,
588
+ budget,
589
+ Math.max(120, Math.floor(charBudget / 2)),
590
+ depth + 1,
591
+ );
592
+ }
593
+ return dict;
594
+ }
595
+
596
+ function findTsTypeDef(name: string, file: string): string | undefined {
597
+ const source = readCached(file);
598
+ const typeRe = new RegExp(`(?:export\\s+)?type\\s+${name}(?:<[^>;=]*>)?\\s*=\\s*`);
599
+ const m = typeRe.exec(source);
600
+ if (m !== null) return readExpression(source, m.index + m[0].length).trim();
601
+ const ifaceRe = new RegExp(`(?:export\\s+)?interface\\s+${name}(?:<[^>]*>)?(?:\\s+extends[^{]+)?\\s*\\{`);
602
+ const im = ifaceRe.exec(source);
603
+ if (im !== null) {
604
+ const body = objectBody(source, im.index + im[0].length - 1);
605
+ if (body !== undefined) return `{ ${body} }`;
606
+ }
607
+ return undefined;
608
+ }
609
+
610
+ function findImportSource(file: string, name: string): string | undefined {
611
+ const source = readCached(file);
612
+ const re = /(?:import|export)\s+(?:type\s+)?\{([^}]+)\}\s*from\s*'([^']+)'/g;
613
+ for (const m of source.matchAll(re)) {
614
+ for (const part of m[1]!.split(',')) {
615
+ const named = /^(?:type\s+)?([\w$]+)(?:\s+as\s+([\w$]+))?$/.exec(part.trim());
616
+ if (named === null) continue;
617
+ if ((named[2] ?? named[1]) === name) return m[2];
618
+ }
619
+ }
620
+ return undefined;
621
+ }
622
+
623
+ function resolveModuleFile(fromFile: string, specifier: string): string | undefined {
624
+ let base: string;
625
+ if (specifier.startsWith('#/')) base = join(SRC, specifier.slice(2));
626
+ else if (specifier.startsWith('.')) base = join(dirname(fromFile), specifier);
627
+ else return undefined;
628
+ for (const candidate of [`${base}.ts`, join(base, 'index.ts')]) {
629
+ if (existsSync(candidate)) return candidate;
630
+ }
631
+ return undefined;
632
+ }
633
+
634
+ function summarizeTsUnion(
635
+ members: string[],
636
+ file: string,
637
+ budget: Budget,
638
+ charBudget: number,
639
+ depth: number,
640
+ ): string {
641
+ const resolved = members.map((m) => {
642
+ const t = m.trim();
643
+ if (/^[$\w]+$/.test(t)) {
644
+ const def = findTsTypeDef(t, file);
645
+ if (def !== undefined) return def;
646
+ }
647
+ return t;
648
+ });
649
+ const bodies = resolved.map((m) => (m.trim().startsWith('{') ? objectBody(m.trim(), 0) : undefined));
650
+ if (bodies.length > 0 && bodies.every((b) => b !== undefined)) {
651
+ const fieldMaps = bodies.map((b) => splitTsTypeFields(b!));
652
+ for (const [name, info] of fieldMaps[0]!) {
653
+ if (
654
+ /^'[^']*'$/.test(info.type) &&
655
+ fieldMaps.every((fm) => /^'[^']*'$/.test(fm.get(name)?.type ?? ''))
656
+ ) {
657
+ const values = fieldMaps.map((fm) => tsQuote(fm.get(name)!.type.slice(1, -1)));
658
+ return truncate(`union on ${name}: ${values.join(' | ')}`, charBudget);
659
+ }
660
+ }
661
+ return truncate(
662
+ fieldMaps
663
+ .map((fm) => stringifySketch(renderTsFields(fm, file, budget, charBudget, depth + 1)))
664
+ .join(' | '),
665
+ charBudget * 2,
666
+ );
667
+ }
668
+ return truncate(
669
+ members
670
+ .map((m) => stringifySketch(summarizeTsTypeExpr(m, file, budget, charBudget, depth + 1)))
671
+ .join(' | '),
672
+ charBudget * 2,
673
+ );
674
+ }
675
+
676
+ function summarizeTsTypeExpr(
677
+ rhs: string,
678
+ file: string,
679
+ budget: Budget,
680
+ charBudget = 480,
681
+ depth = 0,
682
+ ): Sketch {
683
+ let text = rhs.replaceAll(/\s+/g, ' ').trim();
684
+ if (text.startsWith('readonly ')) text = text.slice('readonly '.length).trim();
685
+ const literal = /^'([^']*)'$/.exec(text);
686
+ if (literal?.[1] !== undefined) return tsQuote(literal[1]);
687
+ if (TS_PRIMITIVES.has(text)) return text;
688
+ if (text.endsWith('[]')) {
689
+ const inner = summarizeTsTypeExpr(text.slice(0, -2), file, budget, charBudget, depth + 1);
690
+ if (typeof inner !== 'string') return [inner];
691
+ return inner.includes('|') ? `(${inner})[]` : `${inner}[]`;
692
+ }
693
+ const members = splitTopLevel(text, ['|']);
694
+ if (members.length > 1) {
695
+ return spend(budget)
696
+ ? summarizeTsUnion(members, file, budget, charBudget, depth)
697
+ : truncate(text, 80);
698
+ }
699
+ const intersections = splitTopLevel(text, ['&']);
700
+ if (intersections.length > 1) {
701
+ if (!spend(budget)) return truncate(text, 80);
702
+ const sides = intersections.map((m) => summarizeTsTypeExpr(m, file, budget, charBudget, depth + 1));
703
+ if (sides.every((side) => typeof side !== 'string' && !Array.isArray(side))) {
704
+ return Object.assign({}, ...sides) as SketchDict;
705
+ }
706
+ return truncate(sides.map(stringifySketch).join(' & '), charBudget * 2);
707
+ }
708
+ if (text.startsWith('{')) {
709
+ if (!spend(budget) || depth >= 4) return 'object';
710
+ const body = objectBody(text, 0);
711
+ if (body !== undefined) {
712
+ return renderTsFields(splitTsTypeFields(body), file, budget, charBudget, depth);
713
+ }
714
+ }
715
+ if (/^[$\w]+$/.test(text)) {
716
+ const summary = summarizeTsType(text, file, budget);
717
+ if (summary !== undefined) return summary;
718
+ }
719
+ return truncate(text, 80);
720
+ }
721
+
722
+ function summarizeTsType(name: string, fromFile: string, budget: Budget): Sketch | undefined {
723
+ if (!spend(budget)) return undefined;
724
+ const def = findTsTypeDef(name, fromFile);
725
+ if (def !== undefined) return summarizeTsTypeExpr(def, fromFile, budget);
726
+ const specifier = findImportSource(fromFile, name);
727
+ if (specifier !== undefined) {
728
+ const target = resolveModuleFile(fromFile, specifier);
729
+ if (target !== undefined) return summarizeTsType(name, target, budget);
730
+ }
731
+ for (const m of readCached(fromFile).matchAll(/export\s+\*\s+from\s*'([^']+)'/g)) {
732
+ const target = m[1] === undefined ? undefined : resolveModuleFile(fromFile, m[1]);
733
+ if (target === undefined) continue;
734
+ const summary = summarizeTsType(name, target, budget);
735
+ if (summary !== undefined) return summary;
736
+ }
737
+ return undefined;
738
+ }
739
+
740
+ function friendlyZodExpr(expr: string, ownerFile: string, depth = 0): Sketch {
741
+ let text = expr.replaceAll(/\s+/g, ' ').trim();
742
+ let stripped = true;
743
+ while (stripped) {
744
+ stripped = false;
745
+ for (const suffix of ['.optional()', '.nullable()', '.nullish()', '.readonly()']) {
746
+ if (text.endsWith(suffix)) {
747
+ text = text.slice(0, -suffix.length).trim();
748
+ stripped = true;
749
+ }
750
+ }
751
+ }
752
+ const stringLiteral = /^'([^']*)'$/.exec(text);
753
+ if (stringLiteral?.[1] !== undefined) return tsQuote(stringLiteral[1]);
754
+ const custom = /^z\.custom<(.+)>\(\)$/.exec(text);
755
+ if (custom?.[1] !== undefined) {
756
+ const typeName = custom[1].trim();
757
+ if (depth > 1) return typeName;
758
+ const summary = summarizeTsType(typeName, ownerFile, TS_BUDGET());
759
+ if (summary === undefined) return typeName;
760
+ if (typeof summary !== 'string' && !Array.isArray(summary)) {
761
+ return { [TYPE_KEY]: typeName, ...summary };
762
+ }
763
+ return truncate(`${typeName} = ${stringifySketch(summary)}`, 1024);
764
+ }
765
+ if (/^z\.string\(\)$/.test(text)) return 'string';
766
+ if (/^z\.number\(\)$/.test(text)) return 'number';
767
+ if (/^z\.boolean\(\)$/.test(text)) return 'boolean';
768
+ if (/^z\.(?:int|integer)\(\)$/.test(text)) return 'integer';
769
+ const array = /^z\.array\((.+)\)$/.exec(text);
770
+ if (array?.[1] !== undefined) {
771
+ const inner = friendlyZodExpr(array[1], ownerFile, depth + 1);
772
+ if (typeof inner !== 'string') return [inner];
773
+ return `${inner}[]`;
774
+ }
775
+ const literal = /^z\.literal\((.+)\)$/.exec(text);
776
+ if (literal?.[1] !== undefined) return friendlyZodExpr(literal[1], ownerFile, depth + 1);
777
+ const union = /^z\.union\((.+)\)$/.exec(text);
778
+ if (union?.[1] !== undefined) return friendlyZodUnion(union[1], ownerFile, depth);
779
+ const enumMatch = /^z\.enum\((.+)\)$/.exec(text);
780
+ if (enumMatch?.[1] !== undefined) {
781
+ const body = enumMatch[1].trim().replace(/^\[/, '').replace(/\]$/, '');
782
+ return splitTopLevel(body)
783
+ .map((member) => stringifySketch(friendlyZodExpr(member, ownerFile, depth + 1)))
784
+ .join(' | ');
785
+ }
786
+ const record = /^z\.record\((.+)\)$/.exec(text);
787
+ if (record?.[1] !== undefined) {
788
+ const parts = splitTopLevel(record[1]);
789
+ if (parts.length === 2) {
790
+ return `record<string, ${stringifySketch(friendlyZodExpr(parts[1]!, ownerFile, depth + 1))}>`;
791
+ }
792
+ }
793
+ if (/^z\.\w*[oO]bject\(/.test(text)) {
794
+ if (depth >= 3) return 'object';
795
+ const body = resolveSchemaLiteral(text, readCached(ownerFile));
796
+ if (body === undefined) return 'object';
797
+ const dict: SketchDict = {};
798
+ for (const [key, fieldExpr] of splitObjectFields(body)) {
799
+ if (fieldExpr === '') {
800
+ dict[key] = '(spread)';
801
+ continue;
802
+ }
803
+ const optional = /\.(?:optional|nullish)\(\)$/.test(fieldExpr);
804
+ dict[`${key}${optional ? '?' : ''}`] = friendlyZodExpr(fieldExpr, ownerFile, depth + 1);
805
+ }
806
+ return dict;
807
+ }
808
+ const ident = /^([$\w]+)$/.exec(text)?.[1];
809
+ if (ident !== undefined && depth < 4) {
810
+ const source = readCached(ownerFile);
811
+ const constRe = new RegExp(`const\\s+${ident}\\s*(?::[^=;]+)?=\\s*`);
812
+ const m = constRe.exec(source);
813
+ if (m !== null) {
814
+ const rhs = readExpression(source, m.index + m[0].length).trim();
815
+ return friendlyZodExpr(rhs, ownerFile, depth + 1);
816
+ }
817
+ if (depth <= 1) {
818
+ const summary = summarizeTsType(ident, ownerFile, TS_BUDGET());
819
+ if (summary !== undefined) {
820
+ if (typeof summary !== 'string' && !Array.isArray(summary)) {
821
+ return { [TYPE_KEY]: ident, ...summary };
822
+ }
823
+ return truncate(`${ident} = ${stringifySketch(summary)}`, 320);
824
+ }
825
+ }
826
+ }
827
+ return truncate(text, 80);
828
+ }
829
+
830
+ function friendlyZodUnion(body: string, ownerFile: string, depth: number): string {
831
+ const members = splitTopLevel(body.trim().replace(/^\[/, '').replace(/\]$/, ''));
832
+ const source = readCached(ownerFile);
833
+ const bodies = members.map((m) => resolveSchemaLiteral(m, source));
834
+ if (members.length > 0 && bodies.every((b) => b !== undefined)) {
835
+ const fieldMaps = bodies.map((b) => splitObjectFields(b));
836
+ const spreadSets = fieldMaps.map((fm) => [...fm.keys()].filter((k) => fm.get(k) === ''));
837
+ const commonSpreads = (spreadSets[0] ?? []).filter((s) =>
838
+ spreadSets.every((set) => set.includes(s)),
839
+ );
840
+ const sketches = fieldMaps.map((fm) => {
841
+ const dict: SketchDict = {};
842
+ for (const [key, expr] of fm) {
843
+ if (expr === '') continue;
844
+ const optional = /\.(?:optional|nullish)\(\)$/.test(expr);
845
+ dict[`${key}${optional ? '?' : ''}`] = friendlyZodExpr(expr, ownerFile, depth + 1);
846
+ }
847
+ return stringifySketch(dict);
848
+ });
849
+ const prefix = commonSpreads.length > 0 ? `${commonSpreads.join(' + ')} & ` : '';
850
+ return truncate(`${prefix}${sketches.join(' | ')}`, 320);
851
+ }
852
+ return truncate(
853
+ members.map((m) => stringifySketch(friendlyZodExpr(m, ownerFile, depth + 1))).join(' | '),
854
+ 320,
855
+ );
856
+ }
857
+
858
+ function sketchPayloadFromSource(
859
+ ownerFile: string,
860
+ type: string,
861
+ ): string | Map<string, Sketch> | undefined {
862
+ const absFile = join(PKG, ownerFile);
863
+ const source = readCached(absFile);
864
+ const typeRe = new RegExp(
865
+ `static\\s+override\\s+readonly\\s+type\\s*=\\s*'${escapeRegExp(type)}'`,
866
+ );
867
+ const typeMatch = typeRe.exec(source);
868
+ if (typeMatch === null) return undefined;
869
+ const rest = source.slice(typeMatch.index + typeMatch[0].length);
870
+ const nextType = /static\s+override\s+readonly\s+type\s*=/.exec(rest);
871
+ const classWindow = nextType === null ? rest : rest.slice(0, nextType.index);
872
+ const schemaMatch = /static\s+override\s+readonly\s+schema\s*=\s*/.exec(classWindow);
873
+ if (schemaMatch === null) return undefined;
874
+ const schemaExpr = readExpression(
875
+ classWindow,
876
+ schemaMatch.index + schemaMatch[0].length,
877
+ ).trim();
878
+ if (schemaExpr === '') return undefined;
879
+ const literal = resolveSchemaLiteral(schemaExpr, source);
880
+ if (literal === undefined) {
881
+ const sketch = friendlyZodExpr(schemaExpr, absFile);
882
+ if (typeof sketch === 'string') return sketch;
883
+ if (!Array.isArray(sketch)) return new Map(Object.entries(sketch));
884
+ return stringifySketch(sketch);
885
+ }
886
+ const sketch = new Map<string, Sketch>();
887
+ for (const [key, expr] of splitObjectFields(literal)) {
888
+ if (expr === '') {
889
+ sketch.set(key, '(spread)');
890
+ continue;
891
+ }
892
+ const optional = /\.(?:optional|nullish)\(\)$/.test(expr);
893
+ sketch.set(`${key}${optional ? '?' : ''}`, friendlyZodExpr(expr, absFile));
894
+ }
895
+ return sketch;
896
+ }
897
+
898
+ export async function buildWireManifest(): Promise<string> {
899
+ const { owners, importFiles, durableTypes, classTypes } = scanEventDeclarations();
900
+ await import('../src/index.ts');
901
+ for (const file of importFiles) {
902
+ await import(relative(join(PKG, 'scripts'), file));
903
+ }
904
+ const { WIRE_PROTOCOL_VERSION } = (await import('#/wire/migration/migration')) as {
905
+ WIRE_PROTOCOL_VERSION: string;
906
+ };
907
+
908
+ const entries = [...EVENT2_REGISTRY.values()].toSorted((a, b) => a.type.localeCompare(b.type));
909
+ const migrationChain = scanMigrationChain();
910
+
911
+ const folding = new Map<string, { states: string[]; blobs: string[] }>();
912
+ const protocolTypes = scanUndoableProtocolTypes(classTypes);
913
+ for (const state of scanReplayableStates()) {
914
+ const eventTypes = new Set<string>();
915
+ for (const cls of state.foldClasses) {
916
+ const type = classTypes.get(cls);
917
+ if (type === undefined) {
918
+ throw new Error(
919
+ `[gen-wire-manifest] state '${state.keyName}' folds unresolved event class '${cls}'`,
920
+ );
921
+ }
922
+ eventTypes.add(type);
923
+ }
924
+ if (state.undoable) {
925
+ for (const type of protocolTypes) eventTypes.add(type);
926
+ }
927
+ for (const type of eventTypes) {
928
+ let info = folding.get(type);
929
+ if (info === undefined) {
930
+ info = { states: [], blobs: [] };
931
+ folding.set(type, info);
932
+ }
933
+ info.states.push(state.keyName);
934
+ if (state.blobs) info.blobs.push(state.keyName);
935
+ }
936
+ }
937
+ for (const info of folding.values()) {
938
+ info.states.sort();
939
+ info.blobs.sort();
940
+ }
941
+
942
+ const unregistered = [...durableTypes].filter((type) => !EVENT2_REGISTRY.has(type));
943
+ if (unregistered.length > 0) {
944
+ console.error(
945
+ `[gen-wire-manifest] declared durable but never registered (no fold, not in EVENT2_REGISTRY): ${unregistered.toSorted().join(', ')}`,
946
+ );
947
+ }
948
+
949
+ const out: string[] = [
950
+ '// Wire Protocol Manifest',
951
+ '//',
952
+ '// Generated by scripts/gen-wire-manifest.mts — do not edit by hand.',
953
+ '// Regenerate with: pnpm --filter @moonshot-ai/agent-core-v2 gen:wire-manifest',
954
+ '//',
955
+ `// protocol_version: "${WIRE_PROTOCOL_VERSION}" (migrations: ${migrationChain})`,
956
+ '//',
957
+ '// One declaration per durable record type — an Event2 subclass declaring',
958
+ '// `static type` + `static durable = true` + `static schema` — drained from the',
959
+ '// runtime EVENT2_REGISTRY ("import = register"). Every payload declaration',
960
+ '// carries its record type in a `_name` field. Payload sketches use TypeScript',
961
+ '// type syntax; when a named type is expanded inline, its name appears as a doc',
962
+ '// comment (`/** ContextMessage */`). Bare type names (ContentPart,',
963
+ '// ContextMessage, …) refer to the real types in src/ — they are intentionally',
964
+ '// not resolved here. `// …` marks a capped field list. On disk (wire.jsonl)',
965
+ '// the journal opens with a metadata line {"type": "metadata",',
966
+ '// "protocol_version", "created_at"}; each record is {"type", ...payload,',
967
+ '// "time"} — object payloads spread at the top level.',
968
+ '//',
969
+ '// Every listed type is durable by construction — transient Event2 classes',
970
+ '// never enter EVENT2_REGISTRY, so there is no persisted flag. Declaration',
971
+ '// header lines: states (every state folding this record type on dispatch and',
972
+ '// replay; any state beyond the first is what the retired format listed as',
973
+ '// cross-reducers), blobs (the folding states whose blob codec offloads inline',
974
+ '// media to blob storage), owner (the source file declaring the class).',
975
+ '',
976
+ `// Index (${entries.length} record types)`,
977
+ ];
978
+ const width = Math.max(...entries.map((e) => e.type.length));
979
+ const statesWidth = Math.max(
980
+ ...entries.map((e) => (folding.get(e.type)?.states.join(', ') ?? '(none)').length),
981
+ );
982
+ for (const entry of entries) {
983
+ const states = folding.get(entry.type)?.states.join(', ') ?? '(none)';
984
+ out.push(
985
+ `// ${entry.type.padEnd(width)} ${states.padEnd(statesWidth)} ${owners.get(entry.type) ?? '(unresolved)'}`,
986
+ );
987
+ }
988
+ out.push('');
989
+ const declNames: [string, string][] = [];
990
+ for (const entry of entries) {
991
+ const info = folding.get(entry.type);
992
+ const states = info?.states ?? [];
993
+ const flags: string[] = [];
994
+ if (info !== undefined && info.blobs.length > 0) flags.push(`blobs: ${info.blobs.join(', ')}`);
995
+ const owner = owners.get(entry.type);
996
+ const staticSketch =
997
+ owner === undefined ? undefined : sketchPayloadFromSource(owner, entry.type);
998
+ const sketch = buildPayloadSketch(entry.schema as unknown, staticSketch);
999
+ out.push(...renderPayloadDecl(entry, owner, states, flags, sketch));
1000
+ declNames.push([entry.type, `${pascalCase(entry.type)}Payload`]);
1001
+ }
1002
+
1003
+ out.push('/** Record type → payload sketch. */');
1004
+ out.push('interface WirePayloadMap {');
1005
+ for (const [type, declName] of declNames) {
1006
+ out.push(` ${JSON.stringify(type)}: ${declName};`);
1007
+ }
1008
+ out.push('}');
1009
+ out.push('');
1010
+ return out.join('\n');
1011
+ }
1012
+
1013
+ async function main(): Promise<void> {
1014
+ const check = process.argv.includes('--check');
1015
+ const manifest = await buildWireManifest();
1016
+ if (check) {
1017
+ let current: string | undefined;
1018
+ try {
1019
+ current = readFileSync(MANIFEST_PATH, 'utf-8');
1020
+ } catch {
1021
+ current = undefined;
1022
+ }
1023
+ if (current !== manifest) {
1024
+ console.error(
1025
+ `[gen-wire-manifest] ${relative(process.cwd(), MANIFEST_PATH)} is stale. ` +
1026
+ 'Regenerate with `pnpm --filter @moonshot-ai/agent-core-v2 gen:wire-manifest`.',
1027
+ );
1028
+ process.exit(1);
1029
+ }
1030
+ console.log('[gen-wire-manifest] up to date');
1031
+ return;
1032
+ }
1033
+ writeFileSync(MANIFEST_PATH, manifest);
1034
+ console.log(`[gen-wire-manifest] wrote ${relative(process.cwd(), MANIFEST_PATH)}`);
1035
+ }
1036
+
1037
+ if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
1038
+ await main();
1039
+ }
packages/agent-core-v2/scripts/generate-webp-dec-wasm.mjs ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createRequire } from 'node:module';
2
+ import { readFileSync, writeFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+
5
+ const packageRoot = resolve(import.meta.dirname, '..');
6
+ const require = createRequire(resolve(packageRoot, 'package.json'));
7
+
8
+ const wasmPath = require.resolve('@jsquash/webp/codec/dec/webp_dec.wasm');
9
+ const version = require('@jsquash/webp/package.json').version;
10
+ const wasm = readFileSync(wasmPath);
11
+
12
+ const target = resolve(packageRoot, 'src/agent/media/webp-dec-wasm.ts');
13
+ writeFileSync(
14
+ target,
15
+ `// GENERATED FILE — do not edit by hand.
16
+ // WebP decoder wasm from @jsquash/webp@${version} (codec/dec/webp_dec.wasm),
17
+ // base64-encoded so the bundled CLI needs no on-disk wasm asset.
18
+ // Regenerate with: node scripts/generate-webp-dec-wasm.mjs
19
+
20
+ export const WEBP_DECODER_WASM_BASE64 =
21
+ '${wasm.toString('base64')}';
22
+ `,
23
+ );
24
+ console.log(`Wrote ${target} (${wasm.length} bytes of wasm)`);
packages/agent-core-v2/src/_base/asyncEventQueue.ts ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export class AsyncEventQueue<T> implements AsyncIterable<T>, AsyncIterator<T> {
2
+ private readonly values: T[] = [];
3
+ private readonly waiters: Array<{
4
+ resolve: (result: IteratorResult<T>) => void;
5
+ reject: (reason?: unknown) => void;
6
+ }> = [];
7
+ private error: unknown;
8
+ private failed = false;
9
+ private ended = false;
10
+
11
+ push(value: T): void {
12
+ if (this.failed || this.ended) return;
13
+ const waiter = this.waiters.shift();
14
+ if (waiter !== undefined) {
15
+ waiter.resolve({ done: false, value });
16
+ return;
17
+ }
18
+ this.values.push(value);
19
+ }
20
+
21
+ end(): void {
22
+ if (this.failed || this.ended) return;
23
+ this.ended = true;
24
+ for (const waiter of this.waiters.splice(0)) {
25
+ waiter.resolve({ done: true, value: undefined });
26
+ }
27
+ }
28
+
29
+ fail(error: unknown): void {
30
+ if (this.failed || this.ended) return;
31
+ this.error = error;
32
+ this.failed = true;
33
+ if (this.values.length > 0) return;
34
+ for (const waiter of this.waiters.splice(0)) {
35
+ waiter.reject(error);
36
+ }
37
+ }
38
+
39
+ next(): Promise<IteratorResult<T>> {
40
+ if (this.values.length > 0) {
41
+ const value = this.values.shift()!;
42
+ return Promise.resolve({ done: false, value });
43
+ }
44
+ if (this.failed) {
45
+ return Promise.reject(this.error);
46
+ }
47
+ if (this.ended) {
48
+ return Promise.resolve({ done: true, value: undefined });
49
+ }
50
+ return new Promise<IteratorResult<T>>((resolve, reject) => {
51
+ this.waiters.push({ resolve, reject });
52
+ });
53
+ }
54
+
55
+ [Symbol.asyncIterator](): AsyncIterator<T> {
56
+ return this;
57
+ }
58
+ }
packages/agent-core-v2/src/_base/event.ts ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { onUnexpectedError, safelyCallListener } from './errors/unexpectedError';
2
+ import {
3
+ Disposable,
4
+ DisposableStore,
5
+ combinedDisposable,
6
+ type IDisposable,
7
+ type IDisposableDebugLabel,
8
+ } from './di/lifecycle';
9
+ import { LinkedList } from './di/util/linkedList';
10
+
11
+ export interface Event<T> {
12
+ (
13
+ listener: (e: T) => unknown,
14
+ thisArg?: unknown,
15
+ disposables?: IDisposable[] | DisposableStore,
16
+ ): IDisposable;
17
+ }
18
+
19
+ interface ListenerEntry<T> {
20
+ listener: (e: T) => unknown;
21
+ thisArg: unknown;
22
+ }
23
+
24
+ export class EventSubscription implements IDisposable, IDisposableDebugLabel {
25
+ readonly debugLabel: string | undefined;
26
+ private _removed = false;
27
+
28
+ constructor(
29
+ debugName: string | undefined,
30
+ private readonly _remove: () => void,
31
+ ) {
32
+ this.debugLabel = debugName === undefined ? undefined : `on:${debugName}`;
33
+ }
34
+
35
+ dispose(): void {
36
+ if (this._removed) return;
37
+ this._removed = true;
38
+ this._remove();
39
+ }
40
+ }
41
+
42
+ export class Emitter<T> {
43
+ protected _listeners: Set<ListenerEntry<T>> | undefined;
44
+ private _disposed = false;
45
+ private _event: Event<T> | undefined;
46
+
47
+ constructor(public readonly debugName?: string) {}
48
+
49
+ get event(): Event<T> {
50
+ this._event ??= (listener, thisArg, disposables) => {
51
+ if (this._disposed) {
52
+ return Disposable.None;
53
+ }
54
+ this._listeners ??= new Set();
55
+ const entry: ListenerEntry<T> = { listener, thisArg };
56
+ this._listeners.add(entry);
57
+
58
+ const subscription = new EventSubscription(this.debugName, () => {
59
+ if (this._disposed) {
60
+ return;
61
+ }
62
+ this._listeners?.delete(entry);
63
+ });
64
+
65
+ if (disposables !== undefined) {
66
+ if (disposables instanceof DisposableStore) {
67
+ disposables.add(subscription);
68
+ } else {
69
+ disposables.push(subscription);
70
+ }
71
+ }
72
+ return subscription;
73
+ };
74
+ return this._event;
75
+ }
76
+
77
+ get listenerCount(): number {
78
+ return this._listeners?.size ?? 0;
79
+ }
80
+
81
+ fire(value: T): void {
82
+ if (this._disposed || this._listeners === undefined) {
83
+ return;
84
+ }
85
+ const snapshot = Array.from(this._listeners);
86
+ for (const entry of snapshot) {
87
+ safelyCallListener(() => {
88
+ entry.listener.call(entry.thisArg, value);
89
+ });
90
+ }
91
+ }
92
+
93
+ dispose(): void {
94
+ if (this._disposed) return;
95
+ this._disposed = true;
96
+ this._listeners?.clear();
97
+ this._listeners = undefined;
98
+ }
99
+
100
+ get isDisposed(): boolean {
101
+ return this._disposed;
102
+ }
103
+ }
104
+
105
+ export interface IWaitUntil {
106
+ readonly signal: AbortSignal;
107
+ waitUntil(thenable: Promise<unknown>): void;
108
+ }
109
+
110
+ export type IWaitUntilData<T> = Omit<T, 'waitUntil' | 'signal'>;
111
+
112
+ export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
113
+ private _asyncDeliveryQueue?: LinkedList<[(event: T) => void, IWaitUntilData<T>]>;
114
+
115
+ async fireAsyncConcurrent(data: IWaitUntilData<T>, signal: AbortSignal): Promise<void> {
116
+ if (this.isDisposed || this._listeners === undefined || signal.aborted) {
117
+ return;
118
+ }
119
+ const snapshot = Array.from(this._listeners);
120
+ await Promise.all(
121
+ snapshot.map((entry) =>
122
+ this.deliverAsync(
123
+ (event) => {
124
+ entry.listener.call(entry.thisArg, event);
125
+ },
126
+ data,
127
+ signal,
128
+ ),
129
+ ),
130
+ );
131
+ }
132
+
133
+ async fireAsync(data: IWaitUntilData<T>, signal: AbortSignal): Promise<void> {
134
+ if (this.isDisposed || this._listeners === undefined) {
135
+ return;
136
+ }
137
+
138
+ this._asyncDeliveryQueue ??= new LinkedList();
139
+ for (const entry of this._listeners) {
140
+ this._asyncDeliveryQueue.push([
141
+ (event) => {
142
+ entry.listener.call(entry.thisArg, event);
143
+ },
144
+ data,
145
+ ]);
146
+ }
147
+
148
+ while (this._asyncDeliveryQueue.size > 0 && !signal.aborted) {
149
+ const [deliver, eventData] = this._asyncDeliveryQueue.shift()!;
150
+ await this.deliverAsync(deliver, eventData, signal);
151
+ }
152
+ }
153
+
154
+ private async deliverAsync(
155
+ deliver: (event: T) => void,
156
+ data: IWaitUntilData<T>,
157
+ signal: AbortSignal,
158
+ ): Promise<void> {
159
+ const thenables: Promise<unknown>[] = [];
160
+ const event = {
161
+ ...data,
162
+ signal,
163
+ waitUntil: (p: Promise<unknown>): void => {
164
+ if (Object.isFrozen(thenables)) {
165
+ throw new Error('waitUntil can NOT be called asynchronously');
166
+ }
167
+ thenables.push(p);
168
+ },
169
+ } as T;
170
+ try {
171
+ deliver(event);
172
+ } catch (error) {
173
+ onUnexpectedError(error);
174
+ return;
175
+ }
176
+ void Object.freeze(thenables);
177
+ const settled = await Promise.allSettled(thenables);
178
+ for (const result of settled) {
179
+ if (result.status === 'rejected') {
180
+ onUnexpectedError(result.reason);
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ export function handleVetos(
187
+ vetos: (boolean | Promise<boolean>)[],
188
+ onError: (error: unknown) => void,
189
+ ): Promise<boolean> {
190
+ if (vetos.length === 0) {
191
+ return Promise.resolve(false);
192
+ }
193
+
194
+ const promises: Promise<void>[] = [];
195
+ let lazyValue = false;
196
+
197
+ for (const valueOrPromise of vetos) {
198
+ if (valueOrPromise === true) {
199
+ return Promise.resolve(true);
200
+ }
201
+ if (typeof valueOrPromise === 'boolean') {
202
+ continue;
203
+ }
204
+ promises.push(
205
+ valueOrPromise.then(
206
+ (value) => {
207
+ if (value) {
208
+ lazyValue = true;
209
+ }
210
+ },
211
+ (error) => {
212
+ onError(error);
213
+ lazyValue = true;
214
+ },
215
+ ),
216
+ );
217
+ }
218
+
219
+ return Promise.allSettled(promises).then(() => lazyValue);
220
+ }
221
+
222
+ export namespace Event {
223
+ export const None: Event<unknown> = () => Disposable.None;
224
+
225
+ export function once<T>(event: Event<T>): Event<T> {
226
+ return (listener, thisArg, disposables) => {
227
+ let fired = false;
228
+ const subscription = event(
229
+ (e) => {
230
+ if (fired) return;
231
+ fired = true;
232
+ subscription.dispose();
233
+ try {
234
+ listener.call(thisArg, e);
235
+ } catch (error) {
236
+ onUnexpectedError(error);
237
+ }
238
+ },
239
+ undefined,
240
+ disposables,
241
+ );
242
+ return subscription;
243
+ };
244
+ }
245
+
246
+ export function map<I, O>(event: Event<I>, map: (i: I) => O): Event<O> {
247
+ return (listener, thisArg, disposables) =>
248
+ event(
249
+ (i) => listener.call(thisArg, map(i)),
250
+ undefined,
251
+ disposables,
252
+ );
253
+ }
254
+
255
+ export function filter<T>(event: Event<T>, filter: (e: T) => boolean): Event<T> {
256
+ return (listener, thisArg, disposables) =>
257
+ event(
258
+ (e) => {
259
+ if (filter(e)) listener.call(thisArg, e);
260
+ },
261
+ undefined,
262
+ disposables,
263
+ );
264
+ }
265
+
266
+ export function any<T>(...events: Event<T>[]): Event<T> {
267
+ return (listener, thisArg, disposables) => {
268
+ const combined = combinedDisposable(
269
+ ...events.map((e) => e((value) => listener.call(thisArg, value))),
270
+ );
271
+ if (disposables !== undefined) {
272
+ if (disposables instanceof DisposableStore) {
273
+ disposables.add(combined);
274
+ } else {
275
+ disposables.push(combined);
276
+ }
277
+ }
278
+ return combined;
279
+ };
280
+ }
281
+ }
packages/agent-core-v2/src/_base/version.ts ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ export function getCoreVersion(): string {
2
+ return '0.0.0';
3
+ }
packages/agent-core-v2/src/app/scopes.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { setScopeTopology } from '#/_base/di/scope';
2
+
3
+ export enum LifecycleScope {
4
+ App = 'app',
5
+ Session = 'session',
6
+ Agent = 'agent',
7
+ }
8
+
9
+ export const SCOPE_TOPOLOGY: readonly LifecycleScope[] = [
10
+ LifecycleScope.App,
11
+ LifecycleScope.Session,
12
+ LifecycleScope.Agent,
13
+ ];
14
+
15
+ setScopeTopology(SCOPE_TOPOLOGY);
packages/agent-core-v2/src/debug/debugCascade.ts ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* oxlint-disable typescript-eslint/no-unsafe-declaration-merging, eslint-plugin-import/namespace -- Event2 class+payload-interface declaration merging is the sanctioned event-declaration idiom. */
2
+ import type { CascadeAction, UnitState } from '#/_base/di/cascadeEngine';
3
+ import { createDecorator } from '#/_base/di/instantiation';
4
+ import { Event2 } from '#/app/event/event2';
5
+
6
+ export interface DebugCascadeEntry {
7
+ readonly scopePath: string;
8
+ readonly seq: number;
9
+ readonly reason: string;
10
+ readonly changes: ReadonlyArray<{ token: string; action: CascadeAction }>;
11
+ readonly affected: readonly string[];
12
+ readonly tornDown: readonly string[];
13
+ readonly rebuilt: readonly string[];
14
+ readonly failed: readonly string[];
15
+ readonly abortWaited: boolean;
16
+ readonly abortTimedOut: boolean;
17
+ readonly durationMs: number;
18
+ }
19
+
20
+ export interface DebugPendingUnit {
21
+ readonly token: string;
22
+ readonly missing: string[];
23
+ }
24
+
25
+ export interface DebugFailedUnit {
26
+ readonly token: string;
27
+ readonly error?: string;
28
+ }
29
+
30
+ export interface DebugPendingGroup {
31
+ readonly scopePath: string;
32
+ readonly waiting: DebugPendingUnit[];
33
+ readonly failed: DebugFailedUnit[];
34
+ }
35
+
36
+ export interface DiUnitChangedPayload {
37
+ readonly scope: string;
38
+ readonly token: string;
39
+ readonly state: UnitState;
40
+ readonly error?: string;
41
+ }
42
+
43
+ export class DiUnitChanged extends Event2<{ readonly payload: DiUnitChangedPayload }> {
44
+ static override readonly type = 'event.di.unit_changed';
45
+ }
46
+ export interface DiUnitChanged {
47
+ readonly payload: DiUnitChangedPayload;
48
+ }
49
+
50
+ export const DI_UNIT_CHANGED_EVENT = DiUnitChanged.type;
51
+
52
+ export interface IDebugCascadeService {
53
+ readonly _serviceBrand: undefined;
54
+
55
+ history(): DebugCascadeEntry[];
56
+ pending(): DebugPendingGroup[];
57
+ unprovide(scopePath: string, token: string): Promise<void>;
58
+ update(scopePath: string, token: string, config?: unknown): Promise<void>;
59
+ dispose(scopePath: string, token: string): Promise<void>;
60
+ }
61
+
62
+ export const IDebugCascadeService =
63
+ createDecorator<IDebugCascadeService>('debugCascadeService');
packages/agent-core-v2/src/debug/debugCascadeService.ts ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { CascadeEngine } from '#/_base/di/cascadeEngine';
2
+ import {
3
+ IInstantiationService,
4
+ type ServiceIdentifier,
5
+ } from '#/_base/di/instantiation';
6
+ import type { InstantiationService } from '#/_base/di/instantiationService';
7
+ import { DisposableStore, type IDisposable } from '#/_base/di/lifecycle';
8
+ import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
9
+ import { IEventService } from '#/app/event/event';
10
+ import { LifecycleScope } from '#/app/scopes';
11
+ import { Error2, ErrorCodes } from '#/errors';
12
+
13
+ import {
14
+ DiUnitChanged,
15
+ IDebugCascadeService,
16
+ type DebugCascadeEntry,
17
+ type DebugFailedUnit,
18
+ type DebugPendingGroup,
19
+ type DebugPendingUnit,
20
+ type DiUnitChangedPayload,
21
+ } from './debugCascade';
22
+ import {
23
+ resolveScopeContainer,
24
+ scopePathOfEngine,
25
+ walkScopeContainers,
26
+ } from './scopeTree';
27
+
28
+ export class DebugCascadeService implements IDebugCascadeService {
29
+ declare readonly _serviceBrand: undefined;
30
+
31
+ private readonly root: InstantiationService;
32
+ private readonly events: IEventService;
33
+ private readonly store = new DisposableStore();
34
+ private readonly engineSubscriptions = new Map<CascadeEngine, IDisposable>();
35
+ private tornDown = false;
36
+
37
+ constructor(
38
+ @IInstantiationService instantiation: IInstantiationService,
39
+ @IEventService events: IEventService,
40
+ ) {
41
+ this.root = instantiation as InstantiationService;
42
+ this.events = events;
43
+ const tree = this.root.cascadeTree;
44
+ for (const engine of tree.engines) {
45
+ this._watchEngine(engine);
46
+ }
47
+ this.store.add(
48
+ tree.onDidAddEngine((engine) => {
49
+ this._watchEngine(engine);
50
+ }),
51
+ );
52
+ this.store.add(
53
+ tree.onDidRemoveEngine((engine) => {
54
+ this._unwatchEngine(engine);
55
+ }),
56
+ );
57
+ }
58
+
59
+ history(): DebugCascadeEntry[] {
60
+ const entries: DebugCascadeEntry[] = [];
61
+ for (const info of walkScopeContainers(this.root)) {
62
+ for (const entry of info.container.cascade.history()) {
63
+ entries.push({ scopePath: info.path, ...entry });
64
+ }
65
+ }
66
+ return entries.toSorted(
67
+ (a, b) => a.seq - b.seq || a.scopePath.localeCompare(b.scopePath),
68
+ );
69
+ }
70
+
71
+ pending(): DebugPendingGroup[] {
72
+ const groups: DebugPendingGroup[] = [];
73
+ for (const info of walkScopeContainers(this.root)) {
74
+ const waiting: DebugPendingUnit[] = [];
75
+ for (const [token, missing] of info.container.cascade.pendingSnapshot()) {
76
+ waiting.push({ token, missing: [...missing] });
77
+ }
78
+ const failed: DebugFailedUnit[] = info.container.cascade
79
+ .unitsSnapshot()
80
+ .filter((unit) => unit.state === 'Failed')
81
+ .map((unit) => ({ token: unit.token, error: unit.error }));
82
+ if (waiting.length > 0 || failed.length > 0) {
83
+ groups.push({ scopePath: info.path, waiting, failed });
84
+ }
85
+ }
86
+ return groups;
87
+ }
88
+
89
+ async unprovide(scopePath: string, token: string): Promise<void> {
90
+ const { container, id } = this._resolve(scopePath, token);
91
+ container.unprovide(id);
92
+ await container.cascade.whenIdle();
93
+ }
94
+
95
+ async update(scopePath: string, token: string, config?: unknown): Promise<void> {
96
+ const { container, id } = this._resolve(scopePath, token);
97
+ if (config === undefined) {
98
+ await container.cascade.update(id, `debug update ${token}`);
99
+ } else {
100
+ await container.fiberHost.updateToken(id, config, true);
101
+ }
102
+ }
103
+
104
+ async dispose(scopePath: string, token: string): Promise<void>;
105
+ dispose(): void;
106
+ async dispose(scopePath?: string, token?: string): Promise<void> {
107
+ if (scopePath === undefined && token === undefined) {
108
+ if (!this.tornDown) {
109
+ this.tornDown = true;
110
+ this.store.dispose();
111
+ for (const subscription of this.engineSubscriptions.values()) {
112
+ subscription.dispose();
113
+ }
114
+ this.engineSubscriptions.clear();
115
+ }
116
+ return;
117
+ }
118
+ if (scopePath === undefined || token === undefined) {
119
+ throw new Error2(
120
+ ErrorCodes.DEBUG_TOKEN_NOT_FOUND,
121
+ 'dispose requires both a scope path and a token',
122
+ );
123
+ }
124
+ const { container, id } = this._resolve(scopePath, token);
125
+ await container.cascade.submit({
126
+ action: 'unprovide',
127
+ token: id,
128
+ reason: `debug dispose ${token}`,
129
+ });
130
+ }
131
+
132
+ private _resolve(
133
+ scopePath: string,
134
+ token: string,
135
+ ): { container: InstantiationService; id: ServiceIdentifier<unknown> } {
136
+ const container = resolveScopeContainer(this.root, scopePath);
137
+ if (container === undefined) {
138
+ throw new Error2(
139
+ ErrorCodes.DEBUG_SCOPE_NOT_FOUND,
140
+ `no DI container at scope path '${scopePath}'`,
141
+ );
142
+ }
143
+ const id = container.findIdentifier(token);
144
+ if (id === undefined) {
145
+ throw new Error2(
146
+ ErrorCodes.DEBUG_TOKEN_NOT_FOUND,
147
+ `token '${token}' is not registered in container '${scopePath}'`,
148
+ );
149
+ }
150
+ return { container, id };
151
+ }
152
+
153
+ private _watchEngine(engine: CascadeEngine): void {
154
+ if (this.engineSubscriptions.has(engine)) {
155
+ return;
156
+ }
157
+ this.engineSubscriptions.set(
158
+ engine,
159
+ engine.onDidChangeUnitState((change) => {
160
+ const payload: DiUnitChangedPayload = {
161
+ scope: scopePathOfEngine(this.root, engine) ?? '#unknown',
162
+ token: change.token,
163
+ state: change.state,
164
+ error: change.error,
165
+ };
166
+ this.events.publish(new DiUnitChanged({ payload }));
167
+ }),
168
+ );
169
+ }
170
+
171
+ private _unwatchEngine(engine: CascadeEngine): void {
172
+ this.engineSubscriptions.get(engine)?.dispose();
173
+ this.engineSubscriptions.delete(engine);
174
+ }
175
+ }
176
+
177
+ registerScopedService(
178
+ LifecycleScope.App,
179
+ IDebugCascadeService,
180
+ DebugCascadeService,
181
+ ScopeActivation.OnScopeCreated,
182
+ 'debug',
183
+ );
packages/agent-core-v2/src/debug/debugGraph.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { UnitState } from '#/_base/di/cascadeEngine';
2
+ import type { DependencyEdgeKind } from '#/_base/di/dependencyGraph';
3
+ import { createDecorator } from '#/_base/di/instantiation';
4
+
5
+ export interface DebugGraphNode {
6
+ readonly id: string;
7
+ readonly token: string;
8
+ readonly scopePath: string;
9
+ readonly uid?: number;
10
+ readonly state?: UnitState;
11
+ }
12
+
13
+ export interface DebugGraphEdge {
14
+ readonly from: string;
15
+ readonly to: string;
16
+ readonly kind: DependencyEdgeKind;
17
+ }
18
+
19
+ export interface DebugGraph {
20
+ readonly nodes: DebugGraphNode[];
21
+ readonly edges: DebugGraphEdge[];
22
+ }
23
+
24
+ export interface IDebugGraphService {
25
+ readonly _serviceBrand: undefined;
26
+
27
+ graph(): DebugGraph;
28
+ }
29
+
30
+ export const IDebugGraphService = createDecorator<IDebugGraphService>('debugGraphService');
packages/agent-core-v2/src/debug/debugGraphService.ts ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { IInstantiationService } from '#/_base/di/instantiation';
2
+ import type { InstantiationService } from '#/_base/di/instantiationService';
3
+ import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
4
+ import { LifecycleScope } from '#/app/scopes';
5
+
6
+ import {
7
+ IDebugGraphService,
8
+ type DebugGraph,
9
+ type DebugGraphEdge,
10
+ type DebugGraphNode,
11
+ } from './debugGraph';
12
+ import { walkScopeContainers } from './scopeTree';
13
+
14
+ export class DebugGraphService implements IDebugGraphService {
15
+ declare readonly _serviceBrand: undefined;
16
+
17
+ private readonly root: InstantiationService;
18
+
19
+ constructor(@IInstantiationService instantiation: IInstantiationService) {
20
+ this.root = instantiation as InstantiationService;
21
+ }
22
+
23
+ graph(): DebugGraph {
24
+ const infos = walkScopeContainers(this.root);
25
+ const pathByContainer = new Map<InstantiationService, string>(
26
+ infos.map((info) => [info.container, info.path]),
27
+ );
28
+ const nodes = new Map<string, DebugGraphNode>();
29
+ for (const info of infos) {
30
+ const states = new Map(
31
+ info.container.cascade.unitsSnapshot().map((unit) => [unit.token, unit]),
32
+ );
33
+ for (const registration of info.container.servicesSnapshot()) {
34
+ const id = nodeId(info.path, registration.token);
35
+ nodes.set(id, {
36
+ id,
37
+ token: registration.token,
38
+ scopePath: info.path,
39
+ uid: registration.uid,
40
+ state: states.get(registration.token)?.state,
41
+ });
42
+ }
43
+ }
44
+ const pathOf = (scope: object): string =>
45
+ pathByContainer.get(scope as InstantiationService) ??
46
+ `#${this.root.cascadeTree.seqOf(scope)}`;
47
+ const edges: DebugGraphEdge[] = [];
48
+ const endpointNode = (path: string, token: string): string => {
49
+ const id = nodeId(path, token);
50
+ if (!nodes.has(id)) {
51
+ nodes.set(id, { id, token, scopePath: path });
52
+ }
53
+ return id;
54
+ };
55
+ for (const edge of this.root.dependencyGraph.edges()) {
56
+ edges.push({
57
+ from: endpointNode(pathOf(edge.consumer.scope), edge.consumer.token.toString()),
58
+ to: endpointNode(pathOf(edge.dependency.scope), edge.dependency.token.toString()),
59
+ kind: edge.kind,
60
+ });
61
+ }
62
+ return { nodes: [...nodes.values()], edges };
63
+ }
64
+ }
65
+
66
+ function nodeId(scopePath: string, token: string): string {
67
+ return `${scopePath}::${token}`;
68
+ }
69
+
70
+ registerScopedService(
71
+ LifecycleScope.App,
72
+ IDebugGraphService,
73
+ DebugGraphService,
74
+ ScopeActivation.OnDemand,
75
+ 'debug',
76
+ );
packages/agent-core-v2/src/debug/debugLedger.ts ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { UnitState } from '#/_base/di/cascadeEngine';
2
+ import { createDecorator } from '#/_base/di/instantiation';
3
+ import type { LedgerEntryInfo } from '#/_base/lifecycle/ledger';
4
+
5
+ export interface DebugUnit {
6
+ readonly token: string;
7
+ readonly uid: number;
8
+ readonly state?: UnitState;
9
+ readonly error?: string;
10
+ readonly everActive?: boolean;
11
+ readonly inFlight?: boolean;
12
+ }
13
+
14
+ export interface DebugLedgerNode {
15
+ readonly path: string;
16
+ readonly label: string;
17
+ readonly units: DebugUnit[];
18
+ readonly ledger: LedgerEntryInfo[];
19
+ readonly children: DebugLedgerNode[];
20
+ }
21
+
22
+ export interface IDebugLedgerService {
23
+ readonly _serviceBrand: undefined;
24
+
25
+ tree(): DebugLedgerNode;
26
+ }
27
+
28
+ export const IDebugLedgerService =
29
+ createDecorator<IDebugLedgerService>('debugLedgerService');
packages/agent-core-v2/src/debug/debugLedgerService.ts ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { IInstantiationService } from '#/_base/di/instantiation';
2
+ import type { InstantiationService } from '#/_base/di/instantiationService';
3
+ import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
4
+ import { LifecycleScope } from '#/app/scopes';
5
+
6
+ import { IDebugLedgerService, type DebugLedgerNode, type DebugUnit } from './debugLedger';
7
+ import { scopeContainerLabel } from './scopeTree';
8
+
9
+ export class DebugLedgerService implements IDebugLedgerService {
10
+ declare readonly _serviceBrand: undefined;
11
+
12
+ private readonly root: InstantiationService;
13
+
14
+ constructor(@IInstantiationService instantiation: IInstantiationService) {
15
+ this.root = instantiation as InstantiationService;
16
+ }
17
+
18
+ tree(): DebugLedgerNode {
19
+ return this._node(this.root, scopeContainerLabel(this.root));
20
+ }
21
+
22
+ private _node(container: InstantiationService, path: string): DebugLedgerNode {
23
+ return {
24
+ path,
25
+ label: scopeContainerLabel(container),
26
+ units: joinUnits(container),
27
+ ledger: container.ledger.entries(),
28
+ children: container.children.map((child) =>
29
+ this._node(child, `${path}/${scopeContainerLabel(child)}`),
30
+ ),
31
+ };
32
+ }
33
+ }
34
+
35
+ function joinUnits(container: InstantiationService): DebugUnit[] {
36
+ const states = new Map(container.cascade.unitsSnapshot().map((unit) => [unit.token, unit]));
37
+ return container.servicesSnapshot().map((registration) => {
38
+ const unit = states.get(registration.token);
39
+ return {
40
+ token: registration.token,
41
+ uid: registration.uid,
42
+ state: unit?.state,
43
+ error: unit?.error,
44
+ everActive: unit?.everActive,
45
+ inFlight: unit?.inFlight,
46
+ };
47
+ });
48
+ }
49
+
50
+ registerScopedService(
51
+ LifecycleScope.App,
52
+ IDebugLedgerService,
53
+ DebugLedgerService,
54
+ ScopeActivation.OnDemand,
55
+ 'debug',
56
+ );
packages/agent-core-v2/src/debug/errors.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { registerErrorDomain, type ErrorDomain } from '#/_base/errors/codes';
2
+
3
+ export const DebugErrors = {
4
+ codes: {
5
+ DEBUG_SCOPE_NOT_FOUND: 'debug.scope_not_found',
6
+ DEBUG_TOKEN_NOT_FOUND: 'debug.token_not_found',
7
+ },
8
+ } as const satisfies ErrorDomain;
9
+
10
+ registerErrorDomain(DebugErrors);
packages/agent-core-v2/src/debug/index.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ export * from './debugLedger';
2
+ export * from './debugGraph';
3
+ export * from './debugCascade';
4
+ export * from './errors';
5
+ export * from './debugLedgerService';
6
+ export * from './debugGraphService';
7
+ export * from './debugCascadeService';
packages/agent-core-v2/src/debug/scopeTree.ts ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { CascadeEngine } from '#/_base/di/cascadeEngine';
2
+ import type { InstantiationService } from '#/_base/di/instantiationService';
3
+
4
+ export interface ScopeContainerInfo {
5
+ readonly container: InstantiationService;
6
+ readonly path: string;
7
+ readonly label: string;
8
+ }
9
+
10
+ export function scopeContainerLabel(container: InstantiationService): string {
11
+ return container.debugLabel ?? `#${container.cascadeTree.seqOf(container)}`;
12
+ }
13
+
14
+ export function walkScopeContainers(root: InstantiationService): ScopeContainerInfo[] {
15
+ const out: ScopeContainerInfo[] = [];
16
+ const visit = (container: InstantiationService, path: string): void => {
17
+ out.push({ container, path, label: scopeContainerLabel(container) });
18
+ for (const child of container.children) {
19
+ visit(child, `${path}/${scopeContainerLabel(child)}`);
20
+ }
21
+ };
22
+ visit(root, scopeContainerLabel(root));
23
+ return out;
24
+ }
25
+
26
+ export function resolveScopeContainer(
27
+ root: InstantiationService,
28
+ path: string,
29
+ ): InstantiationService | undefined {
30
+ return walkScopeContainers(root).find((info) => info.path === path)?.container;
31
+ }
32
+
33
+ export function scopePathOfEngine(
34
+ root: InstantiationService,
35
+ engine: CascadeEngine,
36
+ ): string | undefined {
37
+ return walkScopeContainers(root).find((info) => info.container.cascade === engine)?.path;
38
+ }
packages/agent-core-v2/src/env.d.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ declare module '*?raw' {
2
+ const content: string;
3
+ export default content;
4
+ }
packages/agent-core-v2/src/errors.ts ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { CoreErrors } from '#/_base/errors/codes';
2
+ import type { KimiErrorPayload } from '#/_base/errors/serialize';
3
+ import { AgentLifecycleErrors } from '#/session/agentLifecycle/errors';
4
+ import { AuthErrors } from '#/app/auth/errors';
5
+ import { TaskErrors } from '#/agent/task/errors';
6
+ import { ProtocolErrors } from '#/llm-adapter/protocol/errors';
7
+ import { ConfigErrors } from '#/app/config/errors';
8
+ import { CapabilityErrors } from '#/app/capability/errors';
9
+ import { CronErrors } from '#/features/cron/errors';
10
+ import { DebugErrors } from '#/debug/errors';
11
+ import { EventErrors } from '#/app/event/errors';
12
+ import { FileErrors } from '#/app/file/fileService';
13
+ import { FsErrors } from '#/workspace/workspaceFs/internal/errors';
14
+ import { FullCompactionErrors } from '#/agent/fullCompaction/errors';
15
+ import { GoalErrors } from '#/features/goal/errors';
16
+ import { LoopErrors } from '#/agent/loop/errors';
17
+ import { McpErrors } from '#/mcpCore/errors';
18
+ import { ModelCatalogErrors } from '#/llm-adapter/model/errors';
19
+ import { OsFsErrors } from '#/os/interface/hostFsErrors';
20
+ import { OsProcessErrors } from '#/os/interface/hostProcess';
21
+ import { PluginErrors } from '#/app/plugin/errors';
22
+ import { ProfileErrors } from '#/agent/profile/errors';
23
+ import { PromptErrors } from '#/agent/prompt/errors';
24
+ import { ModelsDevImportErrors } from '#/app/kosongConfig/errors';
25
+ import { SessionExportErrors } from '#/app/sessionExport/errors';
26
+ import { SessionErrors } from '#/session/errors';
27
+ import { SkillErrors } from '#/features/skill/catalog/errors';
28
+ import { StateErrors } from '#/state/errors';
29
+ import { StorageErrors } from '#/persistence/interface/storage';
30
+ import { TerminalErrors } from '#/os/interface/terminalErrors';
31
+ import { UsageErrors } from '#/agent/usage/errors';
32
+ import { WebErrors } from '#/app/web/errors';
33
+ import { WireErrors } from '#/wire/errors';
34
+ import { WorkspaceErrors } from '#/app/workspace/errors';
35
+
36
+ export * from '#/_base/errors/codes';
37
+ export * from '#/_base/errors/errorMessage';
38
+ export * from '#/_base/errors/errors';
39
+ export * from '#/_base/errors/serialize';
40
+ export * from '#/_base/errors/unexpectedError';
41
+ export { AgentLifecycleErrors } from '#/session/agentLifecycle/errors';
42
+ export { AuthErrors } from '#/app/auth/errors';
43
+ export { TaskErrors } from '#/agent/task/errors';
44
+ export { ProtocolErrors } from '#/llm-adapter/protocol/errors';
45
+ export { ConfigErrors } from '#/app/config/errors';
46
+ export { CapabilityErrors } from '#/app/capability/errors';
47
+ export { CronErrors } from '#/features/cron/errors';
48
+ export { DebugErrors } from '#/debug/errors';
49
+ export { FileErrors } from '#/app/file/fileService';
50
+ export { FsErrors } from '#/workspace/workspaceFs/internal/errors';
51
+ export { FullCompactionErrors } from '#/agent/fullCompaction/errors';
52
+ export { GoalErrors } from '#/features/goal/errors';
53
+ export { LoopErrors } from '#/agent/loop/errors';
54
+ export { McpErrors } from '#/mcpCore/errors';
55
+ export { ModelCatalogErrors } from '#/llm-adapter/model/errors';
56
+ export { OsFsErrors } from '#/os/interface/hostFsErrors';
57
+ export { OsProcessErrors } from '#/os/interface/hostProcess';
58
+ export { PluginErrors } from '#/app/plugin/errors';
59
+ export { ProfileErrors } from '#/agent/profile/errors';
60
+ export { PromptErrors } from '#/agent/prompt/errors';
61
+ export { ModelsDevImportErrors } from '#/app/kosongConfig/errors';
62
+ export { SessionExportErrors } from '#/app/sessionExport/errors';
63
+ export { SessionErrors } from '#/session/errors';
64
+ export { SkillErrors } from '#/features/skill/catalog/errors';
65
+ export { StorageErrors } from '#/persistence/interface/storage';
66
+ export { TerminalErrors } from '#/os/interface/terminalErrors';
67
+ export { UsageErrors } from '#/agent/usage/errors';
68
+ export { WebErrors } from '#/app/web/errors';
69
+ export { WireErrors } from '#/wire/errors';
70
+ export { WorkspaceErrors } from '#/app/workspace/errors';
71
+ export { EventErrors } from '#/app/event/errors';
72
+ export { StateErrors } from '#/state/errors';
73
+
74
+ export const ErrorCodes = {
75
+ ...CoreErrors.codes,
76
+ ...AgentLifecycleErrors.codes,
77
+ ...AuthErrors.codes,
78
+ ...TaskErrors.codes,
79
+ ...ProtocolErrors.codes,
80
+ ...ConfigErrors.codes,
81
+ ...CapabilityErrors.codes,
82
+ ...CronErrors.codes,
83
+ ...DebugErrors.codes,
84
+ ...FileErrors.codes,
85
+ ...FsErrors.codes,
86
+ ...FullCompactionErrors.codes,
87
+ ...GoalErrors.codes,
88
+ ...LoopErrors.codes,
89
+ ...McpErrors.codes,
90
+ ...ModelCatalogErrors.codes,
91
+ ...OsFsErrors.codes,
92
+ ...OsProcessErrors.codes,
93
+ ...PluginErrors.codes,
94
+ ...ProfileErrors.codes,
95
+ ...PromptErrors.codes,
96
+ ...ModelsDevImportErrors.codes,
97
+ ...SessionExportErrors.codes,
98
+ ...SessionErrors.codes,
99
+ ...SkillErrors.codes,
100
+ ...StorageErrors.codes,
101
+ ...TerminalErrors.codes,
102
+ ...UsageErrors.codes,
103
+ ...WebErrors.codes,
104
+ ...WireErrors.codes,
105
+ ...WorkspaceErrors.codes,
106
+ ...EventErrors.codes,
107
+ ...StateErrors.codes,
108
+ } as const;
109
+
110
+ export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
111
+
112
+ export type KimiErrorCode =
113
+ | 'config.invalid'
114
+ | 'config.persist_blocked'
115
+ | 'session.not_found'
116
+ | 'session.already_exists'
117
+ | 'session.id_invalid'
118
+ | 'session.id_required'
119
+ | 'session.id_empty'
120
+ | 'session.title_empty'
121
+ | 'session.state_not_found'
122
+ | 'session.state_invalid'
123
+ | 'session.fork_active_turn'
124
+ | 'session.undo_unavailable'
125
+ | 'session.export_not_found'
126
+ | 'session.export_missing_version'
127
+ | 'session.export_output_conflict'
128
+ | 'session.export_too_large'
129
+ | 'session.closed'
130
+ | 'session.permission_mode_invalid'
131
+ | 'session.thinking_empty'
132
+ | 'session.model_empty'
133
+ | 'session.plan_mode_invalid'
134
+ | 'session.approval_handler_error'
135
+ | 'session.question_handler_error'
136
+ | 'session.init_failed'
137
+ | 'agent.not_found'
138
+ | 'agent.already_exists'
139
+ | 'agent.already_running'
140
+ | 'agent.not_a_subagent'
141
+ | 'agent.not_owned'
142
+ | 'agent.type_not_allowed'
143
+ | 'agent.max_tokens_exceeded'
144
+ | 'turn.agent_busy'
145
+ | 'goal.already_exists'
146
+ | 'goal.not_found'
147
+ | 'goal.objective_empty'
148
+ | 'goal.objective_too_long'
149
+ | 'goal.status_invalid'
150
+ | 'goal.metadata_reserved'
151
+ | 'goal.not_resumable'
152
+ | 'goal.unsupported_agent'
153
+ | 'model.not_configured'
154
+ | 'model.config_invalid'
155
+ | 'profile.thinking_alias_conflict'
156
+ | 'profile.unknown'
157
+ | 'profile.already_bound'
158
+ | 'profile.not_bound'
159
+ | 'model.not_found'
160
+ | 'auth.login_required'
161
+ | 'auth.provisioning_required'
162
+ | 'auth.token_missing'
163
+ | 'auth.token_unauthorized'
164
+ | 'auth.model_not_resolved'
165
+ | 'context.overflow'
166
+ | 'loop.max_steps_exceeded'
167
+ | 'provider.api_error'
168
+ | 'provider.filtered'
169
+ | 'provider.rate_limit'
170
+ | 'provider.auth_error'
171
+ | 'provider.connection_error'
172
+ | 'provider.overloaded'
173
+ | 'provider.not_found'
174
+ | 'skill.not_found'
175
+ | 'skill.type_unsupported'
176
+ | 'skill.name_empty'
177
+ | 'skill.parse_failed'
178
+ | 'skill.nested_too_deep'
179
+ | 'records.write_failed'
180
+ | 'compaction.failed'
181
+ | 'compaction.unable'
182
+ | 'task.task_id_empty'
183
+ | 'task.limit_exceeded'
184
+ | 'usage.turn_id_conflict'
185
+ | 'mcp.server_not_found'
186
+ | 'mcp.server_disabled'
187
+ | 'mcp.startup_failed'
188
+ | 'mcp.tool_name_collision'
189
+ | 'mcp.oauth_failed'
190
+ | 'message.not_found'
191
+ | 'plugin.not_found'
192
+ | 'plugin.load_failed'
193
+ | 'request.invalid'
194
+ | 'request.work_dir_required'
195
+ | 'request.prompt_input_empty'
196
+ | 'prompt.id_conflict'
197
+ | 'prompt.not_found'
198
+ | 'session.busy'
199
+ | 'shell.git_bash_not_found'
200
+ | 'workspace.not_found'
201
+ | 'terminal.not_found'
202
+ | 'file.not_found'
203
+ | 'file.too_large'
204
+ | 'fs.path_not_found'
205
+ | 'fs.permission_denied'
206
+ | 'fs.path_escapes'
207
+ | 'fs.is_directory'
208
+ | 'fs.is_binary'
209
+ | 'fs.too_large'
210
+ | 'fs.already_exists'
211
+ | 'fs.too_many_results'
212
+ | 'fs.grep_timeout'
213
+ | 'fs.git_unavailable'
214
+ | 'os.fs.not_found'
215
+ | 'os.fs.is_directory'
216
+ | 'os.fs.not_directory'
217
+ | 'os.fs.already_exists'
218
+ | 'os.fs.permission_denied'
219
+ | 'os.fs.not_empty'
220
+ | 'os.fs.unavailable'
221
+ | 'os.fs.unknown'
222
+ | 'os.process.spawn_failed'
223
+ | 'os.process.kill_failed'
224
+ | 'storage.not_found'
225
+ | 'storage.decode_failed'
226
+ | 'storage.corrupted'
227
+ | 'storage.io_failed'
228
+ | 'storage.locked'
229
+ | 'storage.permission_denied'
230
+ | 'storage.disk_full'
231
+ | 'wire.duplicate_op'
232
+ | 'wire.cycle'
233
+ | 'wire.unknown_record'
234
+ | 'wire.migration_missing'
235
+ | 'cron.expression_invalid'
236
+ | 'web.invalid_url'
237
+ | 'web.private_address'
238
+ | 'web.fetch_failed'
239
+ | 'validation.failed'
240
+ | 'not_implemented'
241
+ | 'internal';
242
+
243
+ export interface ErrorEvent {
244
+ readonly type: 'error';
245
+ readonly code: KimiErrorCode;
246
+ readonly message: string;
247
+ readonly name?: string;
248
+ readonly details?: Record<string, unknown>;
249
+ readonly retryable: boolean;
250
+ readonly cause?: KimiErrorPayload;
251
+ }
252
+
253
+ export interface WarningEvent {
254
+ readonly type: 'warning';
255
+ readonly message: string;
256
+ readonly code?: string;
257
+ }
packages/agent-core-v2/src/events.ts ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { CompactionBlockedEvent, CompactionCancelledEvent, CompactionCompletedEvent, CompactionStartedEvent } from '#/agent/fullCompaction/compactionOps';
2
+ import type { TurnStartedEvent, TurnStepCompletedEvent, TurnStepInterruptedEvent, TurnStepRetryingEvent, TurnStepStartedEvent, AssistantDeltaEvent, ThinkingDeltaEvent } from '#/agent/loop/turnEvents';
3
+ import type { TurnEndedEvent } from '#/agent/loop/turnOps';
4
+ import type { PluginCommandActivatedEvent } from '#/agent/pluginCommand/pluginCommand';
5
+ import type { PromptAbortedEvent, PromptCompletedEvent, PromptSteeredEvent, PromptSubmittedEvent } from '#/agent/prompt/promptEvents';
6
+ import type { BackgroundTaskStartedEvent, BackgroundTaskTerminatedEvent, TaskStartedEvent, TaskTerminatedEvent } from '#/agent/task/types';
7
+ import type { McpServerStatusEvent, ShellCompletedEvent, ShellOutputEvent, ShellStartedEvent, ToolCallDeltaEvent, ToolCallStartedEvent, ToolListUpdatedEvent, ToolProgressEvent } from '#/agent/toolExecutor/toolExecutorEvents';
8
+ import type { ToolResultEventPayload } from '#/agent/toolExecutor/toolExecutorEvents';
9
+ import type { AgentStatusUpdatedEvent } from '#/agent/usage/usageEvents';
10
+ import type { CapabilityChangedEvent } from '#/app/capability/capabilityEvents';
11
+ import type { ConfigChangedEvent, ConfigWarningEvent } from '#/app/config/configEvents';
12
+ import type { ModelCatalogChangedEvent } from '#/app/kosongConfig/discovery';
13
+ import type { PluginChangedEvent } from '#/app/plugin/pluginEvents';
14
+ import type { SessionCreatedEvent, SessionStatusChangedEvent, SessionWorkChangedEvent } from '#/app/sessionLegacy/sessionProtocol';
15
+ import type { WorkspaceCreatedEvent, WorkspaceDeletedEvent, WorkspaceUpdatedEvent } from '#/app/workspace/workspaceProtocol';
16
+ import type { CronFiredEvent } from '#/features/cron/cronOps';
17
+ import type { HookResultEvent } from '#/features/externalHooks/agent/agentExternalHooksService';
18
+ import type { GoalUpdatedEvent } from '#/features/goal/goalOps';
19
+ import type { SkillActivatedEvent } from '#/features/skill/skillOps';
20
+ import type { SubagentSuspendedEvent } from '#/features/swarm/session/sessionSwarmService';
21
+ import type { SessionMetaUpdatedEvent } from '#/session/sessionMetadata/sessionMetaEvents';
22
+ import type { SubagentCancelledEvent, SubagentCompletedEvent, SubagentFailedEvent, SubagentSpawnedEvent, SubagentStartedEvent } from '#/session/subagent/mirrorAgentRun';
23
+
24
+ import type { ErrorEvent, WarningEvent } from './errors';
25
+
26
+ export interface ToolResultEvent extends Omit<ToolResultEventPayload, 'agentId'> {
27
+ readonly type: 'tool.result';
28
+ }
29
+
30
+ export type AgentEvent =
31
+ | ErrorEvent
32
+ | WarningEvent
33
+ | AgentStatusUpdatedEvent
34
+ | SessionMetaUpdatedEvent
35
+ | SessionCreatedEvent
36
+ | WorkspaceCreatedEvent
37
+ | WorkspaceUpdatedEvent
38
+ | WorkspaceDeletedEvent
39
+ | SessionWorkChangedEvent
40
+ | SessionStatusChangedEvent
41
+ | ConfigChangedEvent
42
+ | ConfigWarningEvent
43
+ | ModelCatalogChangedEvent
44
+ | PluginChangedEvent
45
+ | CapabilityChangedEvent
46
+ | GoalUpdatedEvent
47
+ | SkillActivatedEvent
48
+ | PluginCommandActivatedEvent
49
+ | TurnStartedEvent
50
+ | TurnEndedEvent
51
+ | TurnStepStartedEvent
52
+ | TurnStepCompletedEvent
53
+ | TurnStepRetryingEvent
54
+ | TurnStepInterruptedEvent
55
+ | AssistantDeltaEvent
56
+ | HookResultEvent
57
+ | ThinkingDeltaEvent
58
+ | ToolCallDeltaEvent
59
+ | ToolCallStartedEvent
60
+ | ToolProgressEvent
61
+ | ShellOutputEvent
62
+ | ShellStartedEvent
63
+ | ShellCompletedEvent
64
+ | ToolResultEvent
65
+ | ToolListUpdatedEvent
66
+ | McpServerStatusEvent
67
+ | SubagentSpawnedEvent
68
+ | SubagentStartedEvent
69
+ | SubagentSuspendedEvent
70
+ | SubagentCompletedEvent
71
+ | SubagentFailedEvent
72
+ | SubagentCancelledEvent
73
+ | CompactionStartedEvent
74
+ | CompactionBlockedEvent
75
+ | CompactionCancelledEvent
76
+ | CompactionCompletedEvent
77
+ | TaskStartedEvent
78
+ | TaskTerminatedEvent
79
+ | BackgroundTaskStartedEvent
80
+ | BackgroundTaskTerminatedEvent
81
+ | CronFiredEvent
82
+ | PromptSubmittedEvent
83
+ | PromptCompletedEvent
84
+ | PromptAbortedEvent
85
+ | PromptSteeredEvent;
86
+
87
+ export type Event = AgentEvent & { agentId: string; sessionId: string };
packages/agent-core-v2/src/features/feature.ts ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { type CollectionToken } from '#/_base/di/collection';
2
+ import {
3
+ ScopeUnits,
4
+ type Fiber,
5
+ type FiberHandle,
6
+ type FiberProvideOptions,
7
+ type ServiceClassRecipe,
8
+ } from '#/_base/di/fiber';
9
+ import { ScopeActivation, type ServiceIdentifier } from '#/_base/di/instantiation';
10
+ import { toDisposable } from '#/_base/di/lifecycle';
11
+ import { Service } from '#/_base/di/service';
12
+ import {
13
+ AgentProfileContribution,
14
+ AGENT_PROFILE_SOURCE_PRIORITY,
15
+ } from '#/app/agentProfileCatalog/agentProfileContribution';
16
+ import { FeatureServiceContribution } from '#/app/feature/featureServiceContribution';
17
+ import type { AgentProfile } from '#/app/agentProfileCatalog/agentProfileCatalog';
18
+ import type { ConfigSchema, RegisterSectionOptions } from '#/app/config/config';
19
+ import { ConfigSectionContribution } from '#/app/config/configSectionContributions';
20
+ import { LifecycleScope } from '#/app/scopes';
21
+ import {
22
+ CommandContribution,
23
+ type CommandContribution as CommandContributionPayload,
24
+ } from '#/agent/command/commandContribution';
25
+ import {
26
+ AgentToolContribution,
27
+ type AgentToolContributionOptions,
28
+ type AgentToolCtor,
29
+ type AnyAgentTool,
30
+ } from '#/agent/toolRegistry/toolContribution';
31
+ import type {
32
+ AgentModel,
33
+ AgentModelDefinition,
34
+ SessionModelDefinition,
35
+ } from '#/state/agentModel';
36
+ import { AgentModelContribution, SessionModelContribution } from '#/state/agentModel';
37
+
38
+ export abstract class Feature extends Service {
39
+ contribute<T>(token: CollectionToken<T>, value: T): FiberHandle {
40
+ return this.provide(token, value);
41
+ }
42
+
43
+ contributeSessionModel<State>(definition: SessionModelDefinition<State>): FiberHandle {
44
+ return this.provide(SessionModelContribution, definition as SessionModelDefinition);
45
+ }
46
+
47
+ contributeAgentModel<S, M extends AgentModel<S>>(
48
+ definition: AgentModelDefinition<S, M>,
49
+ ): FiberHandle {
50
+ return this.provide(AgentModelContribution, definition as AgentModelDefinition<any, any>);
51
+ }
52
+
53
+ contributeConfig<T>(
54
+ domain: string,
55
+ schema: ConfigSchema<T>,
56
+ options: RegisterSectionOptions<T> = {},
57
+ ): FiberHandle {
58
+ return this.provide(ConfigSectionContribution, {
59
+ domain,
60
+ schema: schema as ConfigSchema<unknown>,
61
+ options: options as RegisterSectionOptions<unknown>,
62
+ });
63
+ }
64
+
65
+ contributeService<T>(
66
+ scope: LifecycleScope,
67
+ id: ServiceIdentifier<T>,
68
+ ctor: ServiceClassRecipe,
69
+ opts?: FiberProvideOptions,
70
+ ): FiberHandle {
71
+ this.provide(FeatureServiceContribution, { scope, id });
72
+ return this.provide(ScopeUnits(scope), {
73
+ name: `${this.name}:${String(id)}`,
74
+ apply(fiber: Fiber): void {
75
+ fiber.provide(id, ctor, opts);
76
+ },
77
+ });
78
+ }
79
+
80
+ contributeAgentService<T>(
81
+ id: ServiceIdentifier<T>,
82
+ ctor: ServiceClassRecipe,
83
+ opts?: FiberProvideOptions,
84
+ ): FiberHandle {
85
+ return this.contributeService(LifecycleScope.Agent, id, ctor, opts);
86
+ }
87
+
88
+ contributeTool<T extends AnyAgentTool>(
89
+ id: ServiceIdentifier<T>,
90
+ ctor: AgentToolCtor<T>,
91
+ options: AgentToolContributionOptions,
92
+ ): void {
93
+ this.contributeService(LifecycleScope.Agent, id, ctor, {
94
+ activation: ScopeActivation.OnDemand,
95
+ });
96
+ this.provide(AgentToolContribution, { id, ctor, options });
97
+ }
98
+
99
+ contributeCommand(contribution: CommandContributionPayload): FiberHandle {
100
+ return this.provide(CommandContribution, contribution);
101
+ }
102
+
103
+ contributeProfiles(
104
+ profiles: readonly AgentProfile[],
105
+ opts?: { readonly sourceId?: string; readonly priority?: number },
106
+ ): FiberHandle {
107
+ return this.provide(AgentProfileContribution, {
108
+ sourceId: opts?.sourceId ?? `feature:${this.name}`,
109
+ priority: opts?.priority ?? AGENT_PROFILE_SOURCE_PRIORITY.builtin,
110
+ contribution: { profiles },
111
+ });
112
+ }
113
+
114
+ onDispose(fn: () => void): void {
115
+ this._register(toDisposable(fn));
116
+ }
117
+ }
packages/agent-core-v2/src/features/featureAssembly.ts ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
2
+
3
+ export interface IFeatureAssemblyService {
4
+ readonly _serviceBrand: undefined;
5
+ }
6
+
7
+ export const IFeatureAssemblyService: ServiceIdentifier<IFeatureAssemblyService> =
8
+ createDecorator<IFeatureAssemblyService>('featureAssemblyService');
packages/agent-core-v2/src/features/featureAssemblyService.ts ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { IFeatureManager } from '#/app/feature/featureManager';
2
+ import { LifecycleScope } from '#/app/scopes';
3
+ import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
4
+ import { Service } from '#/_base/di/service';
5
+
6
+ import { IFeatureAssemblyService } from './featureAssembly';
7
+ import { getFeatureRecipes } from './featureRegistry';
8
+
9
+ export class FeatureAssemblyService extends Service implements IFeatureAssemblyService {
10
+ declare readonly _serviceBrand: undefined;
11
+
12
+ constructor(@IFeatureManager featureManager: IFeatureManager) {
13
+ super();
14
+ for (const recipe of getFeatureRecipes()) {
15
+ featureManager.provideUnit(recipe);
16
+ }
17
+ }
18
+ }
19
+
20
+ registerScopedService(
21
+ LifecycleScope.App,
22
+ IFeatureAssemblyService,
23
+ FeatureAssemblyService,
24
+ ScopeActivation.OnScopeCreated,
25
+ 'features',
26
+ );
packages/agent-core-v2/src/features/featureRegistry.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { ServiceClassRecipe } from '#/_base/di/fiber';
2
+
3
+ const _featureRecipes: ServiceClassRecipe[] = [];
4
+
5
+ export function registerFeature(recipe: ServiceClassRecipe): void {
6
+ _featureRecipes.push(recipe);
7
+ }
8
+
9
+ export function getFeatureRecipes(): readonly ServiceClassRecipe[] {
10
+ return _featureRecipes;
11
+ }
12
+
13
+ export function _clearFeatureRecipesForTests(): void {
14
+ _featureRecipes.length = 0;
15
+ }
packages/agent-core-v2/src/hooks.ts ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { toDisposable, type IDisposable } from "#/_base/di/lifecycle";
2
+ import { BugIndicatingError } from "#/errors";
3
+
4
+ export type Hooks<TEvents extends Record<string, unknown>> = {
5
+ readonly [K in keyof TEvents]: HookSlot<TEvents[K]>;
6
+ };
7
+
8
+ export interface HookSlot<TContext> {
9
+ register(
10
+ id: string,
11
+ handler: HookHandler<TContext>,
12
+ options?: HookRegisterOptions,
13
+ ): IDisposable;
14
+
15
+ delete(id: string): boolean;
16
+
17
+ run(context: TContext, terminal?: (context: TContext) => Promise<void>): Promise<void>;
18
+ }
19
+
20
+ export type HookHandler<TContext> = (
21
+ context: TContext,
22
+ next: (context?: TContext) => Promise<void>,
23
+ ) => void | Promise<void>;
24
+
25
+ export interface HookRegisterOptions {
26
+ before?: string;
27
+ after?: string;
28
+ }
29
+
30
+ interface HookEntry<TContext> {
31
+ readonly id: string;
32
+ readonly handler: HookHandler<TContext>;
33
+ }
34
+
35
+ export class OrderedHookSlot<TContext> implements HookSlot<TContext> {
36
+ private entries: HookEntry<TContext>[] = [];
37
+
38
+ register(
39
+ id: string,
40
+ handler: HookHandler<TContext>,
41
+ options: HookRegisterOptions = {},
42
+ ): IDisposable {
43
+ if (options.before !== undefined && options.after !== undefined) {
44
+ throw new BugIndicatingError('Hook registration cannot specify both before and after');
45
+ }
46
+
47
+ this.delete(id);
48
+ const entry = { id, handler };
49
+ const target = options.before ?? options.after;
50
+ if (target === undefined) {
51
+ this.entries.push(entry);
52
+ return this.toEntryDisposable(entry);
53
+ }
54
+
55
+ const targetIndex = this.entries.findIndex((item) => item.id === target);
56
+ if (targetIndex < 0) {
57
+ throw new BugIndicatingError(`Hook target "${target}" is not registered`);
58
+ }
59
+
60
+ const insertAt = options.before !== undefined ? targetIndex : targetIndex + 1;
61
+ this.entries.splice(insertAt, 0, entry);
62
+ return this.toEntryDisposable(entry);
63
+ }
64
+
65
+ delete(id: string): boolean {
66
+ const index = this.entries.findIndex((entry) => entry.id === id);
67
+ if (index < 0) return false;
68
+ this.entries.splice(index, 1);
69
+ return true;
70
+ }
71
+
72
+ asDisposable(id: string): IDisposable {
73
+ return toDisposable(() => {
74
+ this.delete(id);
75
+ });
76
+ }
77
+
78
+ private toEntryDisposable(entry: HookEntry<TContext>): IDisposable {
79
+ return toDisposable(() => {
80
+ const index = this.entries.indexOf(entry);
81
+ if (index < 0) return;
82
+ this.entries.splice(index, 1);
83
+ });
84
+ }
85
+
86
+ async run(
87
+ context: TContext,
88
+ terminal: (context: TContext) => Promise<void> = async () => {},
89
+ ): Promise<void> {
90
+ const entries = [...this.entries];
91
+ const dispatch = (index: number, ctx: TContext): ((override?: TContext) => Promise<void>) => {
92
+ return async (override?: TContext): Promise<void> => {
93
+ const current = override ?? ctx;
94
+ const entry = entries[index];
95
+ if (entry === undefined) {
96
+ await terminal(current);
97
+ return;
98
+ }
99
+ await entry.handler(current, dispatch(index + 1, current));
100
+ };
101
+ };
102
+ await dispatch(0, context)();
103
+ }
104
+ }
105
+
106
+ export function createHooks<TEvents extends Record<string, unknown>, TKeys extends keyof TEvents>(
107
+ keys: readonly TKeys[],
108
+ ): Hooks<TEvents> {
109
+ return Object.fromEntries(
110
+ keys.map((key) => [key, new OrderedHookSlot<TEvents[TKeys]>()]),
111
+ ) as unknown as Hooks<TEvents>;
112
+ }
packages/agent-core-v2/src/human/index.ts ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export * from './llm/message';
2
+ export * from './llm/model';
3
+ export * from './llm/capability';
4
+ export * from './models-dev/models-dev';
5
+ export * from './llm/errors';
6
+ export * from './llm/syntax-errors';
7
+ export * from './llm/thinking';
8
+ export * from './llm/response-format';
9
+ export * from './llm/finish-reason';
10
+ export * from './llm/usage';
11
+ export * from './plugin';
12
+ export * from './llm/protocol/format';
13
+ export * from './llm/protocol/base';
14
+ export * from './llm/protocol/connection';
15
+ export * from './llm/protocol/thinking';
16
+ export * from './llm/protocol/rewrite';
17
+ export * from './llm/protocol/patterns';
18
+ export * from './llm/media';
19
+ export * from './llm/requester/requester';
20
+ export * from './llm/empty-response';
21
+ export * from './llm/requester/actor';
22
+ export * from './llm/requester/recovery';
23
+ export * from './llm/requester/retry';
24
+ export * from './llm/requester/bases/openai/contract';
25
+ export * from './llm/requester/bases/openai/capability';
26
+ export * from './llm/requester/bases/openai/trait';
27
+ export * from './llm/requester/bases/openai/extra-params';
28
+ export * from './llm/requester/bases/openai/requester';
29
+ export * from './llm/requester/bases/openai-responses/contract';
30
+ export * from './llm/requester/bases/openai-responses/capability';
31
+ export * from './llm/requester/bases/openai-responses/trait';
32
+ export * from './llm/requester/bases/openai-responses/extra-params';
33
+ export * from './llm/requester/bases/openai-responses/requester';
34
+ export * from './llm/requester/bases/google-genai/contract';
35
+ export * from './llm/requester/bases/google-genai/capability';
36
+ export * from './llm/requester/bases/google-genai/trait';
37
+ export * from './llm/requester/bases/google-genai/extra-params';
38
+ export * from './llm/requester/bases/google-genai/requester';
39
+ export * from './llm/requester/bases/anthropic/contract';
40
+ export * from './llm/requester/bases/anthropic/capability';
41
+ export * from './llm/requester/bases/anthropic/trait';
42
+ export * from './llm/requester/bases/anthropic/extra-params';
43
+ export * from './llm/requester/bases/anthropic/profile';
44
+ export * from './llm/requester/bases/anthropic/requester';
45
+ export * from './llm/requester/bases/tool-call-id';
46
+ export * from './llm/requester/bases/tool-result-text';
47
+ export * from './llm/provider/definition';
48
+ export * from './llm/provider-catalog';
49
+ export * from './llm-kimi/provider';
50
+ export * from './llm-kimi/errors';
51
+ export * from './llm-kimi/files';
52
+ export * from './llm-kimi/media';
53
+ export * from './llm-kimi/schema';
54
+ export * from './llm-kimi/trait';
55
+ export * from './llm/provider/providers/standard';
56
+ export * from './credentials';
57
+ export * from './tool/executor';
58
+ export * from './tool/machine';
59
+ export * from './tool/wait-for';
60
+ export * from './tool/tool';
61
+ export * from './media/tool';
62
+ export * from './agent/errors';
63
+ export * from './agent/machine';
64
+ export * from './agent/wait-for';
65
+ export * from './agent/turn';
66
+ export * from './agent/context-usage';
67
+ export * from './agent/events';
68
+ export * from './agent/slices';
69
+ export * from './agent/historySchema';
70
+ export * from './persist/open';
71
+ export * from './eventStore/index';
72
+ export * from './persist/v2/migrate';
73
+ export * from './session/machine';
74
+ export * from './session/events';
75
+ export * from './session/slices';
76
+ export * from './session/stores';
77
+ export * from './compaction/controller';
78
+ export * from './compaction/errors';
79
+ export * from './compaction/shape';
80
+ export * from './compaction/summarize';
81
+ export * from './usage/usage';
82
+ export * from './usage/machine';
83
+ export * from './usage/plugin';
84
+ export * from './timing/plugin';
85
+ export * from './interaction/interaction';
86
+ export * from './interaction/machine';
87
+ export * from './interaction/facade';
88
+ export * from './kimi/trace';
89
+ export * from './todo/todoItem';
90
+ export * from './todo/slice';
91
+ export * from './todo/tool';
92
+ export * from './todo/plugin';
93
+ export * from './tool-select/state';
94
+ export * from './tool-select/tool';
95
+ export * from './tool-select/plugin';
96
+ export * from './tool-select/resolver';
97
+ export * from './store';
packages/agent-core-v2/src/human/package.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "type": "module",
3
+ "imports": {
4
+ "#/*": "./*.ts"
5
+ }
6
+ }
packages/agent-core-v2/src/human/plugin.ts ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { SystemMessage, UserMessage } from '#/llm/message';
2
+ import type { AgentEmitted } from '#/agent/machine';
3
+ import { createSystemEntry, createUserEntry, type SystemEntry, type UserEntry } from '#/agent/turn';
4
+ import type { ToolDefinition } from '#/tool/tool';
5
+
6
+ export interface AgentPluginTarget {
7
+ kind: 'agent';
8
+ on(type: AgentEmitted['type'], handler: (event: AgentEmitted) => void): unknown;
9
+ notify(message: UserMessage): void;
10
+ remind(key: string, message: UserMessage | SystemMessage): void;
11
+ }
12
+
13
+ export type PluginTarget = AgentPluginTarget;
14
+
15
+ export interface Plugin {
16
+ readonly name: string;
17
+ tools?(): readonly ToolDefinition[];
18
+ connect?(target: PluginTarget): void;
19
+ }
20
+
21
+ export function collectPluginTools(plugins: readonly Plugin[]): readonly ToolDefinition[] {
22
+ return plugins.flatMap((plugin) => plugin.tools?.() ?? []);
23
+ }
24
+
25
+ export interface AgentPluginSource {
26
+ on(type: AgentEmitted['type'], handler: (event: AgentEmitted) => void): unknown;
27
+ send(
28
+ event:
29
+ | { type: 'input.notify'; entry: UserEntry }
30
+ | { type: 'input.remind'; key: string; entry: SystemEntry | UserEntry },
31
+ ): void;
32
+ }
33
+
34
+ export function connectPlugins(actor: AgentPluginSource, plugins: readonly Plugin[]): void {
35
+ const target: AgentPluginTarget = {
36
+ kind: 'agent',
37
+ on: (type, handler) => {
38
+ actor.on(type, handler);
39
+ },
40
+ notify: (message) => {
41
+ actor.send({ type: 'input.notify', entry: { message } });
42
+ },
43
+ remind: (key, message) => {
44
+ actor.send({
45
+ type: 'input.remind',
46
+ key,
47
+ entry: message.role === 'system' ? createSystemEntry(message) : createUserEntry(message),
48
+ });
49
+ },
50
+ };
51
+ for (const plugin of plugins) {
52
+ plugin.connect?.(target);
53
+ }
54
+ }
packages/agent-core-v2/src/human/xstate2.ts ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createActor as createXStateActor } from 'xstate';
2
+ import type { Actor, ActorOptions, AnyActorLogic } from 'xstate';
3
+
4
+ import { isAbortError } from '#/llm/errors';
5
+ import { xstateInspectionCollector } from '#/xstateInspection';
6
+
7
+ export * from 'xstate';
8
+
9
+ export type RootActorErrorReporter = (err: unknown) => void;
10
+
11
+ let reportRootActorError: RootActorErrorReporter = () => {};
12
+
13
+ export function setRootActorErrorReporter(reporter: RootActorErrorReporter): void {
14
+ reportRootActorError = reporter;
15
+ }
16
+
17
+ function createActorWithInspect<TLogic extends AnyActorLogic>(
18
+ logic: TLogic,
19
+ options?: ActorOptions<TLogic>,
20
+ ): Actor<TLogic> {
21
+ const inspect = options?.inspect;
22
+ const actor = createXStateActor(logic, {
23
+ ...options,
24
+ inspect: (event) => {
25
+ xstateInspectionCollector.publish(event);
26
+ if (typeof inspect === 'function') {
27
+ inspect(event);
28
+ } else {
29
+ inspect?.next?.(event);
30
+ }
31
+ },
32
+ });
33
+ swallowRootAbortError(actor);
34
+ return actor;
35
+ }
36
+
37
+ function swallowRootAbortError(actor: Actor<AnyActorLogic>): void {
38
+ const internal = actor as unknown as { _reportError(err: unknown): void };
39
+ const reportError = internal._reportError.bind(actor);
40
+ internal._reportError = (err: unknown) => {
41
+ if (isAbortError(err)) {
42
+ reportRootActorError(err);
43
+ return;
44
+ }
45
+ reportError(err);
46
+ };
47
+ }
48
+
49
+ export const createActor = createActorWithInspect as typeof createXStateActor;
packages/agent-core-v2/src/human/xstateInspection.ts ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { InspectionEvent } from 'xstate';
2
+
3
+ export type XstateInspectionEventType = InspectionEvent['type'];
4
+
5
+ export interface XstateInspectionEnvelope {
6
+ readonly type: XstateInspectionEventType;
7
+ readonly timestamp: number;
8
+ readonly actorSessionId: string;
9
+ readonly actorId?: string;
10
+ readonly logicId?: string;
11
+ readonly eventType?: string;
12
+ readonly stateValue?: unknown;
13
+ readonly unhandled?: boolean;
14
+ }
15
+
16
+ export type XstateInspectionListener = (envelope: XstateInspectionEnvelope) => void;
17
+
18
+ export interface XstateInspectionCollector {
19
+ subscribe(listener: XstateInspectionListener): () => void;
20
+ publish(event: InspectionEvent): void;
21
+ }
22
+
23
+ function scalar(value: unknown): string | undefined {
24
+ return typeof value === 'string' ? value : undefined;
25
+ }
26
+
27
+ function toEnvelope(event: InspectionEvent, now: () => number): XstateInspectionEnvelope {
28
+ const actorRef = event.actorRef as { id?: unknown; logic?: unknown };
29
+ const logic = actorRef.logic as { id?: unknown } | undefined;
30
+ const snapshot = 'snapshot' in event ? (event.snapshot as { value?: unknown }) : undefined;
31
+ const unhandled =
32
+ event.type === '@xstate.microstep' &&
33
+ event._transitions.length === 0 &&
34
+ !event.event.type.startsWith('xstate.');
35
+ return {
36
+ type: event.type,
37
+ timestamp: now(),
38
+ actorSessionId: event.actorRef.sessionId,
39
+ actorId: scalar(actorRef.id),
40
+ logicId: scalar(logic?.id),
41
+ eventType:
42
+ 'event' in event
43
+ ? event.event.type
44
+ : event.type === '@xstate.action'
45
+ ? event.action.type
46
+ : undefined,
47
+ stateValue: snapshot?.value,
48
+ unhandled: unhandled || undefined,
49
+ };
50
+ }
51
+
52
+ export function createXstateInspectionCollector(input?: {
53
+ now?: () => number;
54
+ }): XstateInspectionCollector {
55
+ const now = input?.now ?? Date.now;
56
+ const listeners = new Set<XstateInspectionListener>();
57
+ return {
58
+ subscribe(listener) {
59
+ listeners.add(listener);
60
+ return () => {
61
+ listeners.delete(listener);
62
+ };
63
+ },
64
+ publish(event) {
65
+ if (listeners.size === 0) return;
66
+ const envelope = toEnvelope(event, now);
67
+ for (const listener of listeners) listener(envelope);
68
+ },
69
+ };
70
+ }
71
+
72
+ export const xstateInspectionCollector = createXstateInspectionCollector();
packages/agent-core-v2/src/index.ts ADDED
@@ -0,0 +1,758 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export * from '#/_base/di/descriptors';
2
+ export * from '#/_base/di/errors';
3
+ export * from '#/_base/di/graph';
4
+ export * from '#/_base/di/instantiation';
5
+ export * from '#/_base/di/instantiationService';
6
+ export * from '#/_base/di/lifecycle';
7
+ export * from '#/_base/di/scope';
8
+ export * from './app/scopes';
9
+ export * from '#/_base/di/serviceCollection';
10
+ export * from '#/_base/di/cascadeEngine';
11
+ export * from '#/_base/di/dependencyGraph';
12
+ export * from '#/_base/lifecycle/ledger';
13
+ export {
14
+ collection,
15
+ isCollectionToken,
16
+ type CollectionChange,
17
+ type CollectionRecord,
18
+ type CollectionToken,
19
+ type CollectionView,
20
+ } from '#/_base/di/collection';
21
+ export {
22
+ FiberProtocolError,
23
+ FiberState,
24
+ ScopeUnits,
25
+ ServiceRecipeError,
26
+ setFiberEventResolver,
27
+ type ConfigSchema,
28
+ type Fiber,
29
+ type FiberHandle,
30
+ type FiberProvideOptions,
31
+ type RecipeStatics,
32
+ type ServiceRecipe,
33
+ } from '#/_base/di/fiber';
34
+ export { Service } from '#/_base/di/service';
35
+ export * from './errors';
36
+ export * from './events';
37
+ export * from '#/runtime/runtime';
38
+ export * from '#/runtime/runtimeRegistry';
39
+ export * from '#/runtime/runtimeWorkspaceView';
40
+ export * from '#/runtime/runtimeProvider';
41
+ export * from '#/runtime/runtimeUnitHost';
42
+ export * from '#/runtime/localRuntime';
43
+ export * from '#/runtime/standaloneRuntime';
44
+ export * from '#/program/program';
45
+ export * from '#/workspace/workspaceInstance/workspaceInstance';
46
+ export * from '#/workspace/workspaceInstance/workspaceInstanceManager';
47
+ export * from '#/workspace/workspaceInstance/workspaceInstanceManagerService';
48
+ export * from '#/agent/runtimeBinding/runtimeBinding';
49
+ export * from '#/agent/runtimeBinding/runtimeBindingService';
50
+ export * from '#/agent/runtimeBinding/agentRuntime';
51
+ export * from '#/app/sessionManager/sessionManager';
52
+ export * from '#/app/sessionManager/sessionManagerService';
53
+
54
+ export * from '#/_base/log/log';
55
+ export * from '#/_base/log/logConfig';
56
+ export * from '#/_base/log/formatter';
57
+ export * from '#/_base/log/fileLog';
58
+ export * from '#/_base/log/logService';
59
+ export * from '#/wire/wire';
60
+ export * from '#/wire/wireService';
61
+ export * from '#/wire/journal';
62
+ export * from '#/wire/tree/index';
63
+ export * from '#/wire/record';
64
+ export * from '#/wire/migration/migration';
65
+ export * from '#/session/sessionLog/sessionLogService';
66
+ export * from '#/app/telemetry/telemetry';
67
+ export * from '#/app/telemetry/context';
68
+ export * from '#/app/telemetry/events';
69
+ export * from '#/app/telemetry/telemetryService';
70
+ export * from '#/app/telemetry/consoleAppender';
71
+ export * from '#/app/telemetry/cloudAppender';
72
+ export * from '#/app/bootstrap/bootstrap';
73
+ export * from '#/app/bootstrap/bootstrapService';
74
+ export * from '#/os/interface/hostClock';
75
+ export * from '#/os/interface/hostEnvironment';
76
+ export * from '#/os/interface/hostFileSystem';
77
+ export * from '#/os/interface/hostProcess';
78
+ export * from '#/os/interface/terminal';
79
+ export * from '#/os/interface/terminalErrors';
80
+ export * from '#/os/backends/node-local/hostClockService';
81
+ export * from '#/os/backends/node-local/hostEnvironmentService';
82
+ export * from '#/os/backends/node-local/hostFsService';
83
+ export * from '#/os/backends/node-local/hostProcessService';
84
+ export * from '#/os/backends/node-local/hostTerminalService';
85
+ export * from '#/agent/tools/os/bash/bash';
86
+ import '#/agent/tools/os/bash/bashTool';
87
+ export * from '#/agent/tools/os/glob/glob';
88
+ import '#/agent/tools/os/glob/globTool';
89
+ export * from '#/agent/tools/os/grep/grep';
90
+ import '#/agent/tools/os/grep/grepTool';
91
+ export * from '#/agent/tools/os/read/read';
92
+ import '#/agent/tools/os/read/readTool';
93
+ export * from '#/agent/tools/os/write/write';
94
+ import '#/agent/tools/os/write/writeTool';
95
+ export * from '#/os/interface/terminal';
96
+ export * from '#/os/interface/terminalErrors';
97
+ export * from '#/os/backends/node-local/hostTerminalService';
98
+ export * from '#/session/terminal/terminalService';
99
+ export * from '#/app/task/task';
100
+ import '#/app/task/taskService';
101
+ export { TaskService } from '#/app/task/taskService';
102
+ import '#/app/event/eventBusService';
103
+ import '#/app/event/eventService';
104
+ import '#/app/event/fiberEventResolver';
105
+ export { IEventBus } from '#/app/event/eventBus';
106
+ export { IEventService } from '#/app/event/event';
107
+ export * from '#/app/event/errors';
108
+ export * from '#/app/event/event2';
109
+ export * from '#/state/errors';
110
+ export * from '#/state/state';
111
+ export * from '#/state/stateContribution';
112
+ export * from '#/state/agentModel';
113
+ export * from '#/state/eventDispatcher';
114
+ import '#/state/eventDispatcherService';
115
+ export * from '#/_base/state/stateRegistry';
116
+ export * from '#/_base/contribution/registry';
117
+ export * from '#/app/state/appState';
118
+ import '#/app/state/appStateService';
119
+ export * from '#/workspace/state/workspaceState';
120
+ import '#/workspace/state/workspaceStateService';
121
+ export * from '#/session/state/sessionState';
122
+ import '#/session/state/sessionStateService';
123
+ export * from '#/agent/state/agentState';
124
+ import '#/agent/state/agentStateService';
125
+ export * from '#/llm-adapter/contract/capability';
126
+ export * from '#/llm-adapter/contract/errors';
127
+ export {
128
+ createAssistantMessage,
129
+ createToolMessage,
130
+ createUserMessage,
131
+ isToolDeclarationOnlyMessage,
132
+ mergeInPlace,
133
+ type Message,
134
+ } from '#/llm-adapter/contract/message';
135
+ export {
136
+ extractText,
137
+ getTextContent,
138
+ isContentPart,
139
+ isToolCall,
140
+ isToolCallPart,
141
+ type AudioURLPart,
142
+ type ContentPart,
143
+ type ImageURLPart,
144
+ type Role,
145
+ type StreamedMessagePart,
146
+ type TextPart,
147
+ type ThinkPart,
148
+ type ToolCall,
149
+ type ToolCallPart,
150
+ type VideoURLPart,
151
+ } from '#human/llm/message';
152
+ export type { ToolDescription as Tool } from '#human/llm/message';
153
+ export { addUsage, emptyUsage, grandTotal, inputTotal, type TokenUsage } from '#human/llm/usage';
154
+ export type { FinishReason } from '#human/llm/finish-reason';
155
+ export type {
156
+ JsonObjectResponseFormat,
157
+ JsonSchemaObject,
158
+ JsonSchemaResponseFormat,
159
+ ResponseFormat,
160
+ } from '#human/llm/response-format';
161
+ export type { ThinkingEffort, ThinkingRequestOptions } from '#human/llm/thinking';
162
+ export type { VideoUploadInput } from '#human/llm/media/upload';
163
+ export type { ToolCallIdPolicy } from '#human/llm/requester/requester';
164
+ export type { SamplingOptions } from '#/llm-adapter/model/model-requester';
165
+ export * from '#/llm-adapter/contract/request-trace';
166
+ export type { KimiThinkingConfig } from '#human/llm-kimi/trait';
167
+
168
+ export * from '#/app/sessionIndex/sessionIndex';
169
+ export * from '#/app/sessionIndex/sessionIndexService';
170
+ export * from '#/app/sessionIndex/sessionIndexMirrorService';
171
+ export * from '#/session/sessionMetadata/sessionMetadata';
172
+ export * from '#/session/sessionMetadata/sessionMetadataService';
173
+ export * from '#/session/sessionMetadata/promptMetadata';
174
+ export * from '#/session/sessionActivity/sessionActivity';
175
+ export * from '#/session/sessionActivity/sessionActivityService';
176
+ export * from '#/session/sessionActivity/sessionOutcomeMirror';
177
+ export * from '#/session/sessionActivity/sessionOutcomeMirrorService';
178
+ export * from '#/session/sessionTitle/agentTitlePromptSource';
179
+ import '#/session/sessionTitle/agentTitlePromptSourceService';
180
+ export * from '#/session/sessionTitle/sessionTitle';
181
+ export * from '#/session/sessionTitle/sessionTitleService';
182
+ export * from '#/session/sessionToolPolicy/sessionToolPolicy';
183
+ export * from '#/session/sessionToolPolicy/sessionToolPolicyService';
184
+ export * from '#/app/config/config';
185
+ export * from '#/app/config/configEvents';
186
+ export type { ConfigChangedEvent } from '#/app/config/configEvents';
187
+ export * from '#/app/config/configService';
188
+ export * from '#/app/config/configSectionContributions';
189
+ import '#/app/kosongConfig/configSection';
190
+ export * from '#/llm-adapter/provider/provider';
191
+ export * from '#/llm-adapter/provider/provider-service';
192
+ export * from '#/llm-adapter/provider/provider-definition';
193
+ export * from '#/llm-adapter/protocol/protocolAdapterRegistry';
194
+ import '#/features/skill/catalog/configSection';
195
+ import '#/app/agentIdentity/configSection';
196
+ export * from '#/app/agentIdentity/configSection';
197
+ export * from '#/app/agentIdentity/agentIdentity';
198
+ export * from '#/app/agentIdentity/agentIdentityService';
199
+ import '#/llm-adapter/protocol/errors';
200
+ export * from '#/llm-adapter/protocol/errors';
201
+ export * from '#/llm-adapter/protocol/protocol';
202
+ export * from '#/llm-adapter/protocol/protocol-base';
203
+ import '#/app/kosongConfig/envOverlay';
204
+ export * from '#/llm-adapter/model/completion-budget';
205
+ export * from '#/llm-adapter/model/host-request-headers';
206
+ export * from '#/llm-adapter/model/model';
207
+ export * from '#/llm-adapter/model/model.types';
208
+ export * from '#/llm-adapter/model/model-service';
209
+ export * from '#/llm-adapter/model/thinking';
210
+ export * from '#/llm-adapter/model/catalog';
211
+ export * from '#/llm-adapter/model/catalog-service';
212
+ export * from '#/llm-adapter/model/model-requester';
213
+ import '#/llm-adapter/model/errors';
214
+ export {
215
+ MODEL_CATALOG_SECTION,
216
+ ModelCatalogConfigSchema,
217
+ type ModelCatalogConfig,
218
+ } from '#/app/kosongConfig/configSection';
219
+ export * from '#/app/kosongConfig/kosongConfig';
220
+ export * from '#/app/kosongConfig/kosongConfigService';
221
+ export * from '#/llm-adapter/model/model-oauth';
222
+ export * from '#/app/kosongConfig/oauthTokenAdapter';
223
+ export * from '#/app/kosongConfig/hostRequestHeadersAdapter';
224
+ export * from '#/app/kosongConfig/discovery';
225
+ export * from '#/app/kosongConfig/discoveryService';
226
+ export * from '#/app/kosongConfig/errors';
227
+ export * from '#/app/kosongConfig/modelsDevImport';
228
+ export * from '#/app/kosongConfig/modelsDevImportService';
229
+ export * from '#/app/kosongConfig/modelsDevUpstream';
230
+ export * from '#/app/kosongConfig/modelsDev';
231
+ export * from '#/app/agentProfileCatalog/agentProfileCatalog';
232
+ export * from '#/app/agentProfileCatalog/agentProfileContribution';
233
+ export * from '#/app/agentProfileCatalog/agentProfileRegistry';
234
+ export * from '#/app/agentProfileCatalog/agentProfileRegistryService';
235
+ export * from '#/app/agentProfileCatalog/builtinAgentProfileLoader';
236
+ export * from '#/app/agentProfileCatalog/builtinAgentProfileLoaderService';
237
+ export * from '#/app/agentProfileCatalog/profile-shared';
238
+ export * from '#/app/agentProfileCatalog/promptPrefix';
239
+ export {
240
+ registerAgentProfile,
241
+ getAgentProfileContributions,
242
+ _clearAgentProfileContributionsForTests,
243
+ } from '#/app/agentProfileCatalog/contribution';
244
+ export * from '#/workspace/workspaceAgentProfileLoader/configSection';
245
+ export { parseAgentFileText } from '#/workspace/workspaceAgentProfileLoader/internal/agentFile';
246
+ export { resolveAgentPath } from '#/workspace/workspaceAgentProfileLoader/internal/paths';
247
+ export * from '#/workspace/workspaceAgentProfileLoader/userAgentProfileLoader';
248
+ export * from '#/workspace/workspaceAgentProfileLoader/userAgentProfileLoaderService';
249
+ export * from '#/app/plugin/types';
250
+ export * from '#/app/plugin/commands';
251
+ export * from '#/app/plugin/manifest';
252
+ export * from '#/app/plugin/store';
253
+ export * from '#/app/plugin/source';
254
+ export * from '#/app/plugin/github-resolver';
255
+ export * from '#/app/plugin/archive';
256
+ export * from '#/app/plugin/manager';
257
+ export * from '#/app/plugin/marketplace';
258
+ export * from '#/app/plugin/plugin';
259
+ export * from '#/app/plugin/pluginEvents';
260
+ export * from '#/app/plugin/pluginService';
261
+ export * from '#/app/capability/capability';
262
+ export * from '#/app/capability/capabilityEvents';
263
+ export * from '#/app/capability/capabilityService';
264
+ export * from '#/app/capability/errors';
265
+ export * from '#/app/capability/types';
266
+ export * from '#/app/feature/featureManager';
267
+ export * from '#/app/feature/featureServiceContribution';
268
+ import '#/app/feature/featureManagerService';
269
+ export * from '#/features/feature';
270
+ export * from '#/features/featureAssembly';
271
+ export * from '#/features/featureRegistry';
272
+ import '#/features/featureAssemblyService';
273
+ export * from '#/agent/command/agentCommand';
274
+ export * from '#/agent/command/commandContribution';
275
+ import '#/agent/command/agentCommandService';
276
+ export * from '#/debug/index';
277
+ export * from '#/workspace/workspaceAgentProfileLoader/pluginAgentProfileLoader';
278
+ export * from '#/workspace/workspaceAgentProfileLoader/pluginAgentProfileLoaderService';
279
+
280
+ export type { SkillSource } from '#/features/skill/catalog/types';
281
+ export * from '#/features/skill/tools/skill';
282
+ export * from '#/features/skill/skill';
283
+ export * from '#/features/skill/skillService';
284
+ import '#/features/skill/skillFeature';
285
+ export * from '#/features/skill/catalog/types';
286
+ export * from '#/features/skill/catalog/configSection';
287
+ export * from '#/features/skill/catalog/parser';
288
+ export * from '#/features/skill/catalog/registry';
289
+ export * from '#/features/skill/catalog/errors';
290
+ export * from '#/features/skill/catalog/skillDiscovery';
291
+ export * from '#/features/skill/catalog/inMemorySkillDiscovery';
292
+ export * from '#/features/skill/catalog/skillSource';
293
+ export * from '#/features/skill/catalog/skillRoots';
294
+ export * from '#/features/skill/catalog/builtin/builtin';
295
+ export * from '#/features/skill/catalog/builtinSkillSource';
296
+ export * from '#/features/skill/catalog/userFileSkillSource';
297
+ export * from '#/features/skill/session/skillCatalog';
298
+ export * from '#/features/skill/session/skillCatalogData';
299
+ export * from '#/features/skill/session/skillCatalogService';
300
+ export * from '#/session/sessionAgentProfileCatalog/sessionAgentProfileCatalog';
301
+ export * from '#/session/sessionAgentProfileCatalog/agentProfileCatalogSeed';
302
+ export * from '#/session/sessionAgentProfileCatalog/sessionAgentProfileCatalogService';
303
+ export * from '#/session/sessionInstructions/instructionsProvider';
304
+ export * from '#/session/workspaceInfo/workspaceInfo';
305
+ export * from '#/workspace/workspaceDirs/workspaceDirs';
306
+ export * from '#/workspace/workspaceDirs/workspaceDirsService';
307
+ export * from '#/features/skill/workspace/workspaceSkillCatalog';
308
+ export * from '#/features/skill/workspace/workspaceSkillCatalogService';
309
+ export * from '#/features/skill/workspace/extraFileSkillSource';
310
+ export * from '#/features/skill/workspace/explicitFileSkillSource';
311
+ export * from '#/features/skill/workspace/rootFileSkillSource';
312
+ export * from '#/features/skill/workspace/pluginSkillSource';
313
+ export * from '#/workspace/workspaceAgentProfileLoader/workspaceAgentProfileLoader';
314
+ export * from '#/workspace/workspaceAgentProfileLoader/workspaceAgentProfileLoaderService';
315
+ export * from '#/workspace/workspaceAgentProfileLoader/extraAgentProfileLoader';
316
+ export * from '#/workspace/workspaceAgentProfileLoader/extraAgentProfileLoaderService';
317
+ export * from '#/workspace/workspaceAgentProfileLoader/explicitAgentProfileLoader';
318
+ export * from '#/workspace/workspaceAgentProfileLoader/explicitAgentProfileLoaderService';
319
+ export * from '#/workspace/workspaceInstructions/workspaceInstructions';
320
+ export * from '#/workspace/workspaceInstructions/workspaceInstructionsService';
321
+ export * from '#/agent/permissionGate/permissionGate';
322
+ export * from '#/agent/permissionGate/permissionGateService';
323
+ export * from '#/agent/toolApproval/toolApproval';
324
+ export * from '#/agent/toolApproval/toolApprovalService';
325
+ import '#/app/flag/flag';
326
+ import '#/app/flag/flagRegistry';
327
+ import '#/app/flag/flagRegistryService';
328
+ import '#/app/flag/flagService';
329
+ export * from '#/app/flag/flagRegistry';
330
+ export * from '#/app/flag/flagRegistryService';
331
+ export * from '#/app/flag/flag';
332
+ export * from '#/app/flag/flagService';
333
+
334
+ export * from '#/agent/modeMutex/modeMutex';
335
+ import '#/agent/modeMutex/modeMutexService';
336
+ export * from '#/features/btw/btw';
337
+ export * from '#/features/btw/btwService';
338
+ import '#/features/btw/btwFeature';
339
+ import '#/features/plan/profile/plan';
340
+ export * from '#/features/plan/tools/enter-plan-mode/enter-plan-mode';
341
+ import '#/features/plan/tools/enter-plan-mode/enterPlanModeTool';
342
+ export * from '#/features/plan/tools/exit-plan-mode/exit-plan-mode';
343
+ import '#/features/plan/tools/exit-plan-mode/exitPlanModeTool';
344
+ export * from '#/features/plan/configSection';
345
+ export * from '#/features/plan/plan';
346
+ export * from '#/features/plan/planOps';
347
+ export * from '#/features/plan/planService';
348
+ import '#/features/dateChange/dateChangeFeature';
349
+ import '#/features/plan/planFeature';
350
+ export * from '#/features/fileHistory/fileHistory';
351
+ export * from '#/features/fileHistory/fileHistoryOps';
352
+ export * from '#/features/fileHistory/fileHistoryService';
353
+ import '#/features/fileHistory/fileHistoryFeature';
354
+ export * from '#/features/externalHooks/configSection';
355
+ export * from '#/features/externalHooks/app/externalHooksRunner';
356
+ export * from '#/features/externalHooks/app/externalHooksRunnerService';
357
+ export * from '#/features/externalHooks/session/sessionExternalHooks';
358
+ export * from '#/features/externalHooks/session/sessionExternalHooksService';
359
+ export * from '#/features/externalHooks/agent/agentExternalHooks';
360
+ export * from '#/features/externalHooks/agent/agentExternalHooksService';
361
+ import '#/features/externalHooks/externalHooksFeature';
362
+ export * from '#/features/debugEvents/debugEvents';
363
+ export * from '#/features/debugEvents/debugEventsService';
364
+ import '#/features/debugEvents/debugEventsFeature';
365
+ export * from '#/features/swarm/configSection';
366
+ export * from '#/features/swarm/agent/swarm';
367
+ export * from '#/features/swarm/agent/swarmService';
368
+ export * from '#/features/swarm/session/sessionSwarm';
369
+ export * from '#/features/swarm/session/sessionSwarmService';
370
+ export * from '#/features/swarm/tools/agent-swarm/agent-swarm';
371
+ import '#/features/swarm/tools/agent-swarm/agentSwarmTool';
372
+ import '#/features/swarm/swarmFeature';
373
+ export * from '#/features/goal/tools/create-goal/create-goal';
374
+ import '#/features/goal/tools/create-goal/createGoalTool';
375
+ export * from '#/features/goal/tools/get-goal/get-goal';
376
+ import '#/features/goal/tools/get-goal/getGoalTool';
377
+ export * from '#/features/goal/tools/set-goal-budget/set-goal-budget';
378
+ import '#/features/goal/tools/set-goal-budget/setGoalBudgetTool';
379
+ export * from '#/features/goal/tools/update-goal/update-goal';
380
+ import '#/features/goal/tools/update-goal/updateGoalTool';
381
+ export * from '#/features/goal/goalDeadlineScheduler';
382
+ export * from '#/features/goal/goal';
383
+ export * from '#/features/goal/goalService';
384
+ export * from '#/features/goal/goalOps';
385
+ export * from '#/features/goal/types';
386
+ import '#/features/goal/goalFeature';
387
+ export * from '#/features/tower/flag';
388
+ export * from '#/features/tower/tower';
389
+ export * from '#/features/tower/towerFeature';
390
+ export * from '#/features/tower/towerService';
391
+ export * from '#/features/tower/towerRateLimit';
392
+ export * from '#/features/tower/towerRateLimitService';
393
+ export * from '#/features/tower/tools/init/init';
394
+ export * from '#/features/tower/tools/plan/plan';
395
+ export * from '#/features/tower/tools/spawn/spawn';
396
+ export * from '#/features/tower/tools/merge/merge';
397
+ export * from '#/features/tower/tools/teardown/teardown';
398
+ export * from '#/features/tower/tools/send/send';
399
+ export * from '#/features/tower/tools/inbox/inbox';
400
+ export * from '#/features/tower/tools/finding/finding';
401
+ export * from '#/features/tower/tools/review/review';
402
+ export * from '#/features/tower/tools/mission/mission';
403
+ export * from '#/features/tower/tools/status/status';
404
+ import '#/features/tower/flag';
405
+ import '#/features/tower/towerFeature';
406
+ export * from '#/agent/usage/usage';
407
+ export * from '#/agent/usage/cacheProbe';
408
+ export * from '#/agent/usage/cacheProbeService';
409
+ export * from '#/session/usage/sessionUsage';
410
+ export * from '#/session/usage/usageAgentModel';
411
+ export * from '#/session/usage/sessionUsageService';
412
+ import '#/features/usage/usageFeature';
413
+ export * from '#/agent/toolDedupe/toolDedupe';
414
+ export * from '#/agent/toolDedupe/toolDedupeService';
415
+ export * from '#/agent/agentsMdReminder/agentsMdReminder';
416
+ export * from '#/agent/agentsMdReminder/agentsMdReminderService';
417
+ import '#/agent/toolSelect/flag';
418
+ export * from '#/agent/tools/select-tools/select-tools';
419
+ import '#/agent/tools/select-tools/selectToolsTool';
420
+ export * from '#/agent/toolSelect/dynamicTools';
421
+ export * from '#/agent/toolSelect/toolSelect';
422
+ export * from '#/agent/toolSelect/toolSelectService';
423
+ export * from '#/agent/toolSelect/toolSelectAnnouncements';
424
+ export * from '#/agent/toolSelect/toolSelectAnnouncementsService';
425
+ export * from '#/agent/toolSelect/toolSelectSchemas';
426
+ export * from '#/agent/toolSelect/toolSelectSchemasService';
427
+ import '#/agent/toolPolicy/configSection';
428
+ export * from '#/agent/toolPolicy/configSection';
429
+ export * from '#/agent/toolPolicy/evaluate';
430
+ export * from '#/agent/toolPolicy/toolPolicy';
431
+ export * from '#/agent/toolPolicy/toolPolicyService';
432
+
433
+ import '#/agent/task/configSection';
434
+ export {
435
+ resolveAgentTaskConfig,
436
+ resolvePrintBackgroundMode,
437
+ type AgentTaskConfig,
438
+ type PrintBackgroundMode,
439
+ } from '#/agent/task/configSection';
440
+ export * from '#/agent/task/printDefaults';
441
+ export * from '#/agent/tools/task/task-list/task-list';
442
+ import '#/agent/tools/task/task-list/taskListTool';
443
+ export * from '#/agent/tools/task/task-output/task-output';
444
+ import '#/agent/tools/task/task-output/taskOutputTool';
445
+ export * from '#/agent/tools/task/task-stop/task-stop';
446
+ import '#/agent/tools/task/task-stop/taskStopTool';
447
+ export * from '#/agent/tools/task/task-wait/task-wait';
448
+ import '#/agent/tools/task/task-wait/taskWaitTool';
449
+ export * from '#/agent/task/task';
450
+ export * from '#/agent/task/taskOps';
451
+ export * from '#/agent/task/taskService';
452
+ import '#/features/cron/configSection';
453
+ export * from '#/features/cron/cronTask';
454
+ export * from '#/features/cron/configSection';
455
+ export * from '#/features/cron/cronService';
456
+ export * from '#/features/cron/cronOps';
457
+ export type { CronFiredEvent } from '#/features/cron/cronOps';
458
+ import '#/features/cron/cronFeature';
459
+ export * from '#/features/cron/tools/cron-create/cron-create';
460
+ export * from '#/features/cron/tools/cron-list/cron-list';
461
+ export * from '#/features/cron/tools/cron-delete/cron-delete';
462
+
463
+ import '#/session/agentLifecycle/profile/profiles';
464
+ export * from '#/session/agentLifecycle/agentLifecycle';
465
+ export * from '#/session/agentLifecycle/agentLifecycleService';
466
+ export * from '#/session/agentLifecycle/mainAgent';
467
+ export * from '#/session/mcp/sessionMcpHandle';
468
+ import '#/app/mcpConfig/configSection';
469
+ export {
470
+ MCP_SECTION,
471
+ McpSectionSchema,
472
+ type McpSection,
473
+ } from '#/app/mcpConfig/configSection';
474
+ export * from '#/app/mcpConfig/oauthStore';
475
+ export { IMcpConfigStore } from '#/app/mcpConfig/configStore';
476
+ import '#/app/mcpConfig/configStore';
477
+ export { IMcpOAuthService } from '#/app/mcpConfig/oauthService';
478
+ import '#/app/mcpConfig/oauthService';
479
+ export * from '#/app/mcpRegistry/mcpRegistry';
480
+ import '#/app/mcpRegistry/mcpRegistryService';
481
+ export * from '#/app/mcpManagement/mcpManagement';
482
+ import '#/app/mcpManagement/mcpManagementService';
483
+ export * from '#/workspace/workspaceMcpConfig/workspaceMcpConfig';
484
+ export * from '#/workspace/workspaceMcpConfig/workspaceMcpConfigService';
485
+ export * from '#/workspace/workspaceMcp/workspaceMcp';
486
+ export * from '#/workspace/workspaceMcp/workspaceMcpService';
487
+ export * from '#/session/subagent/subagent';
488
+ export * from '#/session/subagent/subagentService';
489
+ export * from '#/session/subagent/spawn';
490
+ import '#/session/subagent/flag';
491
+ export * from '#/session/subagent/subagentModelsValidation';
492
+ import '#/session/subagent/subagentModelsValidationService';
493
+ export * from '#/agent/tools/agent/subagent-task';
494
+ export { AGENT_RUN_PROMPT_ORIGIN } from '#/session/subagent/runAgentTurn';
495
+ export * from '#/session/subagent/mirrorAgentRun';
496
+ export * from '#/session/subagent/subagentScopeCache';
497
+ import '#/session/subagent/subagentScopeCacheService';
498
+ import '#/session/subagent/configSection';
499
+ export * from '#/agent/tools/agent/agent';
500
+ import '#/agent/tools/agent/agentTool';
501
+ export * from '#/app/sessionManager/sessionLookup';
502
+ export * from '#/workspace/workspaceContext/workspaceContext';
503
+ export * from '#/workspace/sessionLifecycle/sessionLifecycle';
504
+ export * from '#/workspace/sessionLifecycle/sessionLifecycleEvents';
505
+ export * from '#/workspace/sessionLifecycle/sessionLifecycleService';
506
+ export * from '#/workspace/sessionLifecycle/coldSessionArchive';
507
+ export * from '#/workspace/sessionLifecycle/internal/addressing';
508
+ import '#/app/sessionExport/errors';
509
+ export * from '#/app/sessionExport/sessionExport';
510
+ export * from '#/app/sessionExport/sessionExportService';
511
+ export * from '#/app/sessionExport/manifest';
512
+ export * from '#/app/sessionExport/wire-scan';
513
+ export * from '#/app/sessionExport/zip';
514
+ export * from '#/app/sessionLegacy/sessionLegacy';
515
+ export * from '#/app/sessionLegacy/sessionLegacyService';
516
+ export * from '#/human/interaction/interaction';
517
+ export * from '#/human/interaction/facade';
518
+ export * from '#/agent/interaction/interactionOps';
519
+ export * from '#/session/sessionContext/sessionContext';
520
+
521
+ export * from '#/agent/interaction/question';
522
+ export {
523
+ type ApprovalDecision,
524
+ type ApprovalRequest as SessionApprovalRequest,
525
+ type ApprovalResponse as SessionApprovalResponse,
526
+ } from '#/agent/interaction/approval';
527
+ export * from '#/agent/tools/ask-user-question/ask-user-question';
528
+ import '#/agent/tools/ask-user-question/askUserQuestionTool';
529
+ export * from '#/app/gateway/gateway';
530
+ export * from '#/app/gateway/gatewayService';
531
+
532
+ export * from '#/session/workspaceContext/workspaceContext';
533
+ export * from '#/session/workspaceContext/workspaceContextService';
534
+ export * from '#/app/projectLocalConfig/projectLocalConfig';
535
+ export * from '#/app/workspace/workspace';
536
+ export * from '#/app/workspace/workspaceService';
537
+ export * from '#/app/workspace/workspaceAlias';
538
+ export * from '#/app/workspace/workspaceEvents';
539
+ export * from '#/app/workspace/workspacePersistence';
540
+ export * from '#/app/workspace/fileWorkspacePersistence';
541
+ export * from '#/app/workspaceAliases/workspaceAliases';
542
+ import '#/app/workspaceAliases/workspaceAliasesService';
543
+ export * from '#/app/workspaceSessions/workspaceSessions';
544
+ import '#/app/workspaceSessions/workspaceSessionsService';
545
+ import '#/app/git/gitService';
546
+ export * from '#/app/bashParser/bashParser';
547
+ import '#/app/bashParser/bashParserService';
548
+ export * from '#/workspace/workspaceFs/internal/errors';
549
+ export * from '#/workspace/workspaceFs/fs';
550
+ export * from '#/workspace/workspaceFs/fsService';
551
+ export * from '#/session/agentLifecycle/profile/gitContext';
552
+ export * from '#/workspace/workspaceFs/internal/rgLocator';
553
+ export * from '#/workspace/workspaceFs/internal/runRg';
554
+ export * from '#/workspace/workspaceGit/workspaceGit';
555
+ export * from '#/workspace/workspaceGit/workspaceGitService';
556
+ export * from '#/session/sessionToolPolicyGate/sessionToolPolicyGate';
557
+ export * from '#/session/sessionToolPolicyGate/sessionToolPolicyGateService';
558
+ export * from '#/workspace/workspaceTrust/workspaceTrust';
559
+ export * from '#/workspace/workspaceTrust/workspaceTrustService';
560
+ export * from '#/app/hostFolderBrowser/hostFolderBrowser';
561
+ export * from '#/app/hostFolderBrowser/hostFolderBrowserService';
562
+ export * from '#/persistence/interface/storage';
563
+ export * from '#/persistence/interface/appendLogStore';
564
+ export * from '#/persistence/interface/atomicDocumentStore';
565
+ export * from '#/persistence/interface/queryStore';
566
+ export * from '#/persistence/interface/blobStore';
567
+ export * from '#/persistence/backends/node-fs/fileStorageService';
568
+ export * from '#/persistence/backends/node-fs/appendLogStore';
569
+ export * from '#/persistence/backends/node-fs/atomicDocumentStore';
570
+ export * from '#/persistence/backends/node-fs/blobStoreService';
571
+ export * from '#/persistence/backends/node-fs/projectLocalConfigService';
572
+ export * from '#/persistence/configSection';
573
+ import '#/persistence/configSection';
574
+ export * from '#/persistence/backends/minidb/miniDbQueryStore';
575
+ export * from '#/persistence/backends/memory/inMemoryStorageService';
576
+ export * from '#/agent/tools/web-search/web-search';
577
+ import '#/agent/tools/web-search/webSearchTool';
578
+ export * from '#/app/auth/auth';
579
+ export * from '#/app/auth/authService';
580
+ export * from '#/app/auth/configSection';
581
+ export * from '#/app/auth/webSearch/webSearch';
582
+ export * from '#/app/auth/webSearch/webSearchService';
583
+ export * from '#/app/auth/webSearch/providers/moonshot-web-search';
584
+ export * from '#/app/authLegacy/authLegacy';
585
+ export * from '#/app/authLegacy/authLegacyService';
586
+ export * from '#/app/file/fileService';
587
+ export * from '#/app/file/fileServiceImpl';
588
+ export {
589
+ buildImageCompressionCaption,
590
+ compressBase64ForModel,
591
+ compressImageForModel,
592
+ gateImageFormatParts,
593
+ IMAGE_BYTE_BUDGET,
594
+ MAX_IMAGE_DECODE_BYTES,
595
+ MAX_IMAGE_EDGE_PX,
596
+ READ_IMAGE_BYTE_BUDGET,
597
+ resolveMaxImageEdgePx,
598
+ resolveReadImageByteBudget,
599
+ } from '#/agent/media/image-compress';
600
+ export { providerImagePolicy, type ProviderImagePolicy } from '#human/llm/media/image-formats';
601
+ export {
602
+ buildImageConversionGuidance,
603
+ buildUnsupportedImageNotice,
604
+ decodeBase64Prefix,
605
+ isModelAcceptedImageMime,
606
+ normalizeImageMime,
607
+ parseImageDataUrl,
608
+ resolveEffectiveImageMime,
609
+ unsupportedImageMimeFromUrl,
610
+ } from '#/agent/media/image-format-policy';
611
+ export {
612
+ persistOriginalImage,
613
+ sessionMediaOriginalsDir,
614
+ } from '#/agent/media/image-originals';
615
+ export * from '#/app/edit/fileEdit';
616
+ export * from '#/app/edit/fileEditService';
617
+ export * from '#/app/edit/editService';
618
+ export * from '#/app/edit/textModel';
619
+ export * from '#/agent/tools/edit/edit';
620
+ import '#/agent/tools/edit/editTool';
621
+ export * from '#/agent/tools/fetch-url/fetch-url';
622
+ import '#/agent/tools/fetch-url/fetchUrlTool';
623
+ export * from '#/app/web/web';
624
+ export * from '#/app/web/webService';
625
+ export * from '#/app/web/providers/local-fetch-url';
626
+ export * from '#/app/web/providers/moonshot-fetch-url';
627
+
628
+ export * from '#/agent/blob/agentBlobService';
629
+ export * from '#/agent/blob/agentBlobServiceImpl';
630
+ export * from '#/agent/contextMemory/contextMemory';
631
+ export * from '#/agent/contextMemory/contextMemoryService';
632
+ export * from '#/agent/contextMemory/contextOps';
633
+ export * from '#/agent/contextMemory/compactionHandoff';
634
+ export * from '#/agent/contextMemory/conversationUndoParticipants';
635
+ export * from '#/agent/contextMemory/conversationTime';
636
+ export * from '#/agent/contextMemory/loopEventFold';
637
+ export * from '#/agent/contextMemory/messageId';
638
+ export * from '#/agent/contextMemory/contextTranscript';
639
+ export * from '#/agent/contextMemory/types';
640
+ export * from '#/features/reminder/reminderService';
641
+ export * from '#/features/reminder/systemReminder';
642
+ export * from '#/features/reminder/types';
643
+ import '#/features/reminder/reminderFeature';
644
+ export * from '#/features/dateChange/dateChange';
645
+ export * from '#/features/dateChange/dateChangeService';
646
+ export * from '#/agent/contextProjector/contextProjector';
647
+ export * from '#/agent/contextProjector/contextProjectorService';
648
+ export * from '#/agent/contextProjector/mediaProjection';
649
+ export * from '#/agent/tokenCounting/tokenCounting';
650
+ export * from '#/agent/tokenCounting/tokenCountingOps';
651
+ export * from '#/session/tokenCounting/sessionTokenCounting';
652
+ export * from '#/session/tokenCounting/tokenCountingAgentModel';
653
+ export * from '#/session/tokenCounting/sessionTokenCountingService';
654
+ import '#/features/tokenCounting/tokenCountingFeature';
655
+ export * from '#/agent/plugin/agentPlugin';
656
+ export * from '#/agent/plugin/agentPluginOps';
657
+ export * from '#/agent/plugin/agentPluginService';
658
+ export * from '#/agent/fullCompaction/strategy';
659
+ export * from '#/agent/fullCompaction/fullCompaction';
660
+ export * from '#/agent/fullCompaction/fullCompactionService';
661
+ export * from '#/agent/fullCompaction/compactionOps';
662
+ export * from '#/agent/fullCompaction/types';
663
+ export * from '#/agent/fullCompaction/contextRecovery';
664
+ export * from '#/agent/fullCompaction/compactionInstruction';
665
+ export * from '#/agent/llmRequester/llmRequester';
666
+ export * from '#/agent/llmRequester/llmRequesterService';
667
+ export * from '#/agent/llmRequester/llmRequestOps';
668
+ export * from '#/_base/utils/promise';
669
+ export * from '#/_base/utils/retry';
670
+ export * from '#/_base/utils/timer';
671
+ import '#/agent/loop/configSection';
672
+ export * from '#/agent/loop/loop';
673
+ export * from '#/agent/loop/loopService';
674
+ export * from '#/agent/loop/promptChannel';
675
+ export * from '#/agent/interruptionReminder/interruptionReminder';
676
+ export * from '#/agent/interruptionReminder/interruptionReminderService';
677
+ export * from '#/agent/interruptionReminder/interruptionReminderOps';
678
+ export * from '#/agent/mcp/mcp';
679
+ export * from '#/agent/mcp/mcpService';
680
+ export * from '#/agent/mcp/mcpDiscoveryOps';
681
+ export * from '#/mcpCore/config-schema';
682
+ export * from '#/agent/media/mediaTools';
683
+ export * from '#/agent/media/mediaToolsRegistrar';
684
+ export * from '#/agent/media/registerMediaTools';
685
+ export {
686
+ buildDaemonFileUrl,
687
+ buildMediaPathTag,
688
+ daemonFileRefFromPart,
689
+ mediaExtensionForMime,
690
+ matchSingleMediaPathTag,
691
+ parseDaemonFileUrl,
692
+ } from '#/agent/media/mediaRef';
693
+ export type { DaemonFileRef, MediaKind } from '#/agent/media/mediaRef';
694
+ export * from '#/agent/media/sessionMediaStore';
695
+ import '#/agent/media/sessionMediaStoreService';
696
+ export * from '#/agent/media/kimiFileUrl';
697
+ export * from '#/agent/media/videoUpload';
698
+ export * from '#/agent/media/mediaResolver';
699
+ export * from '#/agent/media/mediaResolverService';
700
+ import '#/agent/media/configSection';
701
+ export * from '#/agent/media/imageConfigBridge';
702
+ import '#/agent/permissionMode/configSection';
703
+ export * from '#/agent/permissionMode/permissionMode';
704
+ export * from '#/agent/permissionMode/permissionModeService';
705
+ export * from '#/agent/permissionPolicy/permissionPolicy';
706
+ export * from '#/agent/permissionPolicy/permissionPolicyService';
707
+ export * from '#/agent/permissionPolicy/types';
708
+ import '#/agent/permissionRules/configSection';
709
+ export * from '#/agent/permissionRules/permissionRules';
710
+ export * from '#/agent/permissionRules/matchesRule';
711
+ export * from '#/agent/permissionRules/permissionRulesService';
712
+ export * from '#/agent/pluginCommand/pluginCommand';
713
+ export * from '#/agent/pluginCommand/pluginCommandService';
714
+ export * from '#/agent/profile/profile';
715
+ export * from '#/agent/profile/profileService';
716
+ export * from '#/agent/profile/context';
717
+ export * from '#/agent/prompt/promptEvents';
718
+ export * from '#/agent/prompt/promptMetadataText';
719
+ export * from '#/agent/replayBuilder/types';
720
+ export * from '#/agent/replayBuilder/fold';
721
+ export { type SessionSummary } from '#/app/sessionIndex/sessionIndex';
722
+ export * from '#/agent/undo/undo';
723
+ export * from '#/agent/undo/undoService';
724
+ export * from '#/agent/shellCommand/shellCommand';
725
+ export * from '#/agent/shellCommand/shellCommandService';
726
+ export * from '#/agent/agentContext/agentContext';
727
+ export * from '#/agent/agentContext/agentSpace';
728
+ export * from '#/agent/scopeContext/scopeContext';
729
+ export * from '#/features/sessionInit/sessionInit';
730
+ export * from '#/features/sessionInit/sessionInitService';
731
+ export * from '#/features/sessionInit/profile/init';
732
+ import '#/features/sessionInit/sessionInitFeature';
733
+ export * from '#/features/todo/todoItem';
734
+ export * from '#/features/todo/todoListReminder';
735
+ export * from '#/features/todo/todoService';
736
+ export * from '#/features/todo/tools/todo-list/todo-list';
737
+ import '#/features/todo/todoFeature';
738
+ export * from '#/features/notify/flag';
739
+ export * from '#/features/notify/notifyUserAvailability';
740
+ export * from '#/features/notify/tools/notify-user/notify-user';
741
+ import '#/features/notify/notifyFeature';
742
+ export * from '#/tool/toolContract';
743
+ export * from '#/agent/toolExecutor/toolHooks';
744
+ export * from '#/agent/toolExecutor/toolExecutor';
745
+ export * from '#/agent/toolExecutor/toolExecutorService';
746
+ export * from '#/agent/toolResultTruncation/toolResultTruncation';
747
+ import '#/agent/toolResultTruncation/toolResultTruncationService';
748
+ import '#/agent/toolActivation/toolActivationService';
749
+ import '#/agent/toolRegistry/toolContribution';
750
+ import '#/agent/toolRegistry/toolRegistry';
751
+ import '#/agent/toolRegistry/toolRegistryService';
752
+ export { IAgentToolActivationService } from '#/agent/toolActivation/toolActivation';
753
+ export { IAgentToolRegistryService } from '#/agent/toolRegistry/toolRegistry';
754
+ export { registerAgentToolService, AgentToolContribution } from '#/agent/toolRegistry/toolContribution';
755
+ export type { AgentToolContributionOptions } from '#/agent/toolRegistry/toolContribution';
756
+ export * from '#/agent/userTool/userTool';
757
+ export * from '#/agent/userTool/userToolOps';
758
+ export * from '#/agent/userTool/userToolService';
packages/agent-core-v2/src/llm-adapter/record-diff.ts ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface RecordDiff {
2
+ readonly added: readonly string[];
3
+ readonly removed: readonly string[];
4
+ readonly changed: readonly string[];
5
+ }
6
+
7
+ export function isEmptyDiff(diff: RecordDiff): boolean {
8
+ return diff.added.length === 0 && diff.removed.length === 0 && diff.changed.length === 0;
9
+ }
10
+
11
+ export function diffRecords<T>(
12
+ previous: Readonly<Record<string, T>> | undefined,
13
+ current: Readonly<Record<string, T>> | undefined,
14
+ ): RecordDiff {
15
+ const prev = previous ?? {};
16
+ const curr = current ?? {};
17
+ const added: string[] = [];
18
+ const removed: string[] = [];
19
+ const changed: string[] = [];
20
+ for (const key of Object.keys(curr)) {
21
+ if (!(key in prev)) {
22
+ added.push(key);
23
+ } else if (!deepEqual(prev[key], curr[key])) {
24
+ changed.push(key);
25
+ }
26
+ }
27
+ for (const key of Object.keys(prev)) {
28
+ if (!(key in curr)) {
29
+ removed.push(key);
30
+ }
31
+ }
32
+ return { added, removed, changed };
33
+ }
34
+
35
+ export function deepEqual(a: unknown, b: unknown): boolean {
36
+ if (Object.is(a, b)) return true;
37
+ if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
38
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
39
+ const aKeys = Object.keys(a);
40
+ const bKeys = Object.keys(b);
41
+ if (aKeys.length !== bKeys.length) return false;
42
+ for (const key of aKeys) {
43
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
44
+ if (!deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])) {
45
+ return false;
46
+ }
47
+ }
48
+ return true;
49
+ }
packages/agent-core-v2/src/mcpCore/client-http.ts ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ErrorCodes, Error2 } from '#/errors';
2
+ import type { McpServerHttpConfig } from './config-schema';
3
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
4
+ import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
5
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
6
+
7
+ import {
8
+ buildRequestOptions,
9
+ KIMI_MCP_CLIENT_NAME,
10
+ KIMI_MCP_CLIENT_VERSION,
11
+ MCP_LIVENESS_PROBE_TIMEOUT_MS,
12
+ toMcpToolDefinition,
13
+ toMcpToolResult,
14
+ type UnexpectedCloseListener,
15
+ type UnexpectedCloseReason,
16
+ } from './client-shared';
17
+ import { buildMcpRemoteHeaders } from './client-remote';
18
+ import { createMcpOAuthFetch } from './oauth/provider';
19
+ import type { MCPClient, MCPToolDefinition, MCPToolResult } from './types';
20
+
21
+ export interface HttpMcpClientOptions {
22
+ readonly clientName?: string;
23
+ readonly clientVersion?: string;
24
+ readonly startupTimeoutMs?: number;
25
+ readonly toolCallTimeoutMs?: number;
26
+ readonly envLookup?: (name: string) => string | undefined;
27
+ readonly fetch?: typeof fetch;
28
+ readonly oauthProvider?: OAuthClientProvider;
29
+ }
30
+
31
+ export class HttpMcpClient implements MCPClient {
32
+ private readonly client: Client;
33
+ private readonly transport: StreamableHTTPClientTransport;
34
+ private readonly startupTimeoutMs?: number;
35
+ private readonly toolCallTimeoutMs?: number;
36
+ private started = false;
37
+ private closed = false;
38
+ private ready = false;
39
+ private hooksInstalled = false;
40
+ private unexpectedCloseListener: UnexpectedCloseListener | undefined;
41
+ private lastTransportError: Error | undefined;
42
+ private pendingUnexpectedClose: UnexpectedCloseReason | undefined;
43
+ private unexpectedCloseFired = false;
44
+
45
+ constructor(config: McpServerHttpConfig, options: HttpMcpClientOptions = {}) {
46
+ const envLookup = options.envLookup ?? ((name) => process.env[name]);
47
+ const headers = buildMcpHttpHeaders(config, envLookup);
48
+
49
+ this.transport = new StreamableHTTPClientTransport(new URL(config.url), {
50
+ requestInit: headers !== undefined ? { headers } : undefined,
51
+ fetch: createMcpOAuthFetch(options.oauthProvider, options.fetch),
52
+ authProvider: options.oauthProvider,
53
+ });
54
+ this.client = new Client({
55
+ name: options.clientName ?? KIMI_MCP_CLIENT_NAME,
56
+ version: options.clientVersion ?? KIMI_MCP_CLIENT_VERSION,
57
+ });
58
+ this.startupTimeoutMs = options.startupTimeoutMs;
59
+ this.toolCallTimeoutMs = options.toolCallTimeoutMs;
60
+ }
61
+
62
+ async connect(): Promise<void> {
63
+ if (this.closed) {
64
+ throw new Error2(ErrorCodes.MCP_STARTUP_FAILED, 'MCP HTTP client is closed');
65
+ }
66
+ if (this.started) return;
67
+ this.started = true;
68
+ this.installTransportHooks();
69
+ try {
70
+ await this.client.connect(
71
+ this.transport,
72
+ buildRequestOptions(this.startupTimeoutMs, undefined),
73
+ );
74
+ } catch (error) {
75
+ await this.closeStartedClient();
76
+ throw error;
77
+ }
78
+ if (this.closed) {
79
+ await this.closeStartedClient();
80
+ throw new Error2(ErrorCodes.MCP_STARTUP_FAILED, 'MCP HTTP client was closed during startup');
81
+ }
82
+ this.ready = true;
83
+ }
84
+
85
+ async close(): Promise<void> {
86
+ if (this.closed) return;
87
+ this.closed = true;
88
+ await this.closeStartedClient();
89
+ }
90
+
91
+ onUnexpectedClose(listener: UnexpectedCloseListener): void {
92
+ this.unexpectedCloseListener = listener;
93
+ const pending = this.pendingUnexpectedClose;
94
+ if (pending !== undefined) {
95
+ this.pendingUnexpectedClose = undefined;
96
+ listener(pending);
97
+ }
98
+ }
99
+
100
+ async listTools(): Promise<MCPToolDefinition[]> {
101
+ const result = await this.client.listTools(
102
+ undefined,
103
+ buildRequestOptions(this.startupTimeoutMs, undefined),
104
+ );
105
+ return result.tools.map(toMcpToolDefinition);
106
+ }
107
+
108
+ async callTool(
109
+ name: string,
110
+ args: Record<string, unknown>,
111
+ signal?: AbortSignal,
112
+ ): Promise<MCPToolResult> {
113
+ const requestOptions = buildRequestOptions(this.toolCallTimeoutMs, signal);
114
+ const result = await this.client.callTool({ name, arguments: args }, undefined, requestOptions);
115
+ return toMcpToolResult(result);
116
+ }
117
+
118
+ async ping(signal?: AbortSignal): Promise<void> {
119
+ await this.client.ping(buildRequestOptions(MCP_LIVENESS_PROBE_TIMEOUT_MS, signal));
120
+ }
121
+
122
+ private async closeStartedClient(): Promise<void> {
123
+ if (!this.started) return;
124
+ this.started = false;
125
+ await this.client.close();
126
+ }
127
+
128
+ private installTransportHooks(): void {
129
+ if (this.hooksInstalled) return;
130
+ this.hooksInstalled = true;
131
+ this.client.onclose = () => {
132
+ if (this.closed) return;
133
+ if (!this.ready) return;
134
+ this.fireUnexpectedClose({ error: this.lastTransportError });
135
+ };
136
+ this.client.onerror = (error) => {
137
+ this.lastTransportError = error;
138
+ if (this.closed) return;
139
+ if (!this.ready) return;
140
+ if (isTerminalTransportError(error)) {
141
+ this.fireUnexpectedClose({ error });
142
+ }
143
+ };
144
+ }
145
+
146
+ private fireUnexpectedClose(reason: UnexpectedCloseReason): void {
147
+ if (this.unexpectedCloseFired) return;
148
+ this.unexpectedCloseFired = true;
149
+ const listener = this.unexpectedCloseListener;
150
+ if (listener !== undefined) {
151
+ listener(reason);
152
+ } else {
153
+ this.pendingUnexpectedClose = reason;
154
+ }
155
+ }
156
+ }
157
+
158
+ export function isTerminalTransportError(error: Error): boolean {
159
+ if (error.name === 'UnauthorizedError') return true;
160
+ if (/Maximum reconnection attempts/i.test(error.message)) return true;
161
+ return false;
162
+ }
163
+
164
+ export function buildMcpHttpHeaders(
165
+ config: McpServerHttpConfig,
166
+ envLookup: (name: string) => string | undefined,
167
+ ): Record<string, string> | undefined {
168
+ return buildMcpRemoteHeaders(config, envLookup);
169
+ }
packages/agent-core-v2/src/mcpCore/client-remote.ts ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { McpRemoteServerConfig, McpServerConfig } from './config-schema';
2
+ import { ErrorCodes, Error2 } from '#/errors';
3
+
4
+ export function buildMcpRemoteHeaders(
5
+ config: McpRemoteServerConfig,
6
+ envLookup: (name: string) => string | undefined,
7
+ ): Record<string, string> | undefined {
8
+ const headers: Record<string, string> = { ...config.headers };
9
+ if (config.bearerTokenEnvVar !== undefined) {
10
+ const token = envLookup(config.bearerTokenEnvVar);
11
+ if (token === undefined || token.length === 0) {
12
+ throw new Error2(
13
+ ErrorCodes.CONFIG_INVALID,
14
+ `MCP ${config.transport.toUpperCase()} bearer token env var "${config.bearerTokenEnvVar}" is not set or is empty`,
15
+ );
16
+ }
17
+ for (const key of Object.keys(headers)) {
18
+ if (key.toLowerCase() === 'authorization') {
19
+ delete headers[key];
20
+ }
21
+ }
22
+ headers['Authorization'] = `Bearer ${token}`;
23
+ }
24
+ return Object.keys(headers).length > 0 ? headers : undefined;
25
+ }
26
+
27
+ export function isRemoteMcpConfig(config: McpServerConfig): config is McpRemoteServerConfig {
28
+ return config.transport === 'http' || config.transport === 'sse';
29
+ }
packages/agent-core-v2/src/mcpCore/client-shared.ts ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getCoreVersion } from '#/_base/version';
2
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
3
+
4
+ import type { MCPClient, MCPToolDefinition, MCPToolResult } from './types';
5
+
6
+ export const KIMI_MCP_CLIENT_NAME = 'kimi-code';
7
+ export const KIMI_MCP_CLIENT_VERSION = getCoreVersion();
8
+
9
+ export interface UnexpectedCloseReason {
10
+ readonly error?: Error;
11
+ readonly stderr?: string;
12
+ }
13
+
14
+ export type UnexpectedCloseListener = (reason: UnexpectedCloseReason) => void;
15
+
16
+ export function isMcpConnectionClosedError(error: unknown): boolean {
17
+ return (
18
+ error instanceof Error &&
19
+ (error as Error & { readonly code?: unknown }).code === ErrorCode.ConnectionClosed
20
+ );
21
+ }
22
+
23
+ export function isMcpTransportFailure(error: unknown): boolean {
24
+ if (!(error instanceof Error)) return false;
25
+ if (isMcpConnectionClosedError(error)) return true;
26
+ return !(error instanceof McpError);
27
+ }
28
+
29
+ export const MCP_LIVENESS_PROBE_TIMEOUT_MS = 5_000;
30
+
31
+ export function isMcpMalformedResultError(error: unknown): boolean {
32
+ return error instanceof Error && error.name === 'ZodError';
33
+ }
34
+
35
+ export async function probeMcpLiveness(client: MCPClient, signal: AbortSignal): Promise<boolean> {
36
+ try {
37
+ await client.ping(signal);
38
+ return true;
39
+ } catch (error) {
40
+ if (isMcpConnectionClosedError(error)) return false;
41
+ if (isMcpMalformedResultError(error)) return true;
42
+ if (error instanceof McpError) {
43
+ return (error as Error & { readonly code?: unknown }).code !== ErrorCode.RequestTimeout;
44
+ }
45
+ return false;
46
+ }
47
+ }
48
+
49
+ export interface McpRequestOptions {
50
+ readonly timeout?: number;
51
+ readonly signal?: AbortSignal;
52
+ }
53
+
54
+ export function buildRequestOptions(
55
+ timeoutMs: number | undefined,
56
+ signal: AbortSignal | undefined,
57
+ ): McpRequestOptions | undefined {
58
+ if (timeoutMs === undefined && signal === undefined) return undefined;
59
+ return { timeout: timeoutMs, signal };
60
+ }
61
+
62
+ interface SdkListedTool {
63
+ readonly name: string;
64
+ readonly description?: string;
65
+ readonly inputSchema: Record<string, unknown>;
66
+ }
67
+
68
+ export function toMcpToolDefinition(tool: SdkListedTool): MCPToolDefinition {
69
+ return {
70
+ name: tool.name,
71
+ description: tool.description ?? '',
72
+ inputSchema: tool.inputSchema,
73
+ };
74
+ }
75
+
76
+ export function toMcpToolResult(result: unknown): MCPToolResult {
77
+ if (typeof result === 'object' && result !== null && 'content' in result) {
78
+ const typed = result as {
79
+ content: unknown;
80
+ isError?: unknown;
81
+ structuredContent?: unknown;
82
+ _meta?: unknown;
83
+ };
84
+ if (Array.isArray(typed.content)) {
85
+ return {
86
+ content: typed.content as MCPToolResult['content'],
87
+ isError: typed.isError === true,
88
+ structuredContent: typed.structuredContent,
89
+ _meta:
90
+ typeof typed._meta === 'object' && typed._meta !== null
91
+ ? (typed._meta as Record<string, unknown>)
92
+ : undefined,
93
+ };
94
+ }
95
+ }
96
+ if (typeof result === 'object' && result !== null && 'toolResult' in result) {
97
+ const legacy = (result as { toolResult: unknown }).toolResult;
98
+ return {
99
+ content: [
100
+ {
101
+ type: 'text',
102
+ text: typeof legacy === 'string' ? legacy : JSON.stringify(legacy),
103
+ },
104
+ ],
105
+ isError: false,
106
+ };
107
+ }
108
+ return { content: [], isError: false };
109
+ }
packages/agent-core-v2/src/mcpCore/client-sse.ts ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ErrorCodes, Error2 } from '#/errors';
2
+ import type { McpServerSseConfig } from './config-schema';
3
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
4
+ import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
5
+ import { SSEClientTransport, SseError } from '@modelcontextprotocol/sdk/client/sse.js';
6
+
7
+ import {
8
+ buildRequestOptions,
9
+ KIMI_MCP_CLIENT_NAME,
10
+ KIMI_MCP_CLIENT_VERSION,
11
+ MCP_LIVENESS_PROBE_TIMEOUT_MS,
12
+ toMcpToolDefinition,
13
+ toMcpToolResult,
14
+ type UnexpectedCloseListener,
15
+ type UnexpectedCloseReason,
16
+ } from './client-shared';
17
+ import { buildMcpRemoteHeaders } from './client-remote';
18
+ import { createMcpOAuthFetch } from './oauth/provider';
19
+ import type { MCPClient, MCPToolDefinition, MCPToolResult } from './types';
20
+
21
+ export interface SseMcpClientOptions {
22
+ readonly clientName?: string;
23
+ readonly clientVersion?: string;
24
+ readonly startupTimeoutMs?: number;
25
+ readonly toolCallTimeoutMs?: number;
26
+ readonly envLookup?: (name: string) => string | undefined;
27
+ readonly fetch?: typeof fetch;
28
+ readonly oauthProvider?: OAuthClientProvider;
29
+ }
30
+
31
+ export class SseMcpClient implements MCPClient {
32
+ private readonly client: Client;
33
+ private readonly transport: SSEClientTransport;
34
+ private readonly startupTimeoutMs?: number;
35
+ private readonly toolCallTimeoutMs?: number;
36
+ private started = false;
37
+ private closed = false;
38
+ private ready = false;
39
+ private hooksInstalled = false;
40
+ private unexpectedCloseListener: UnexpectedCloseListener | undefined;
41
+ private lastTransportError: Error | undefined;
42
+ private pendingUnexpectedClose: UnexpectedCloseReason | undefined;
43
+ private unexpectedCloseFired = false;
44
+
45
+ constructor(config: McpServerSseConfig, options: SseMcpClientOptions = {}) {
46
+ const envLookup = options.envLookup ?? ((name) => process.env[name]);
47
+ const headers = buildMcpRemoteHeaders(config, envLookup);
48
+
49
+ this.transport = new SSEClientTransport(new URL(config.url), {
50
+ requestInit: headers !== undefined ? { headers } : undefined,
51
+ fetch: createMcpOAuthFetch(options.oauthProvider, options.fetch),
52
+ authProvider: options.oauthProvider,
53
+ });
54
+ this.client = new Client({
55
+ name: options.clientName ?? KIMI_MCP_CLIENT_NAME,
56
+ version: options.clientVersion ?? KIMI_MCP_CLIENT_VERSION,
57
+ });
58
+ this.startupTimeoutMs = options.startupTimeoutMs;
59
+ this.toolCallTimeoutMs = options.toolCallTimeoutMs;
60
+ }
61
+
62
+ async connect(): Promise<void> {
63
+ if (this.closed) {
64
+ throw new Error2(ErrorCodes.MCP_STARTUP_FAILED, 'MCP SSE client is closed');
65
+ }
66
+ if (this.started) return;
67
+ this.started = true;
68
+ this.installTransportHooks();
69
+ try {
70
+ await this.client.connect(
71
+ this.transport,
72
+ buildRequestOptions(this.startupTimeoutMs, undefined),
73
+ );
74
+ } catch (error) {
75
+ await this.closeStartedClient();
76
+ throw error;
77
+ }
78
+ if (this.closed) {
79
+ await this.closeStartedClient();
80
+ throw new Error2(ErrorCodes.MCP_STARTUP_FAILED, 'MCP SSE client was closed during startup');
81
+ }
82
+ this.ready = true;
83
+ }
84
+
85
+ async close(): Promise<void> {
86
+ if (this.closed) return;
87
+ this.closed = true;
88
+ await this.closeStartedClient();
89
+ }
90
+
91
+ onUnexpectedClose(listener: UnexpectedCloseListener): void {
92
+ this.unexpectedCloseListener = listener;
93
+ const pending = this.pendingUnexpectedClose;
94
+ if (pending !== undefined) {
95
+ this.pendingUnexpectedClose = undefined;
96
+ listener(pending);
97
+ }
98
+ }
99
+
100
+ async listTools(): Promise<MCPToolDefinition[]> {
101
+ const result = await this.client.listTools(
102
+ undefined,
103
+ buildRequestOptions(this.startupTimeoutMs, undefined),
104
+ );
105
+ return result.tools.map(toMcpToolDefinition);
106
+ }
107
+
108
+ async callTool(
109
+ name: string,
110
+ args: Record<string, unknown>,
111
+ signal?: AbortSignal,
112
+ ): Promise<MCPToolResult> {
113
+ const requestOptions = buildRequestOptions(this.toolCallTimeoutMs, signal);
114
+ const result = await this.client.callTool({ name, arguments: args }, undefined, requestOptions);
115
+ return toMcpToolResult(result);
116
+ }
117
+
118
+ async ping(signal?: AbortSignal): Promise<void> {
119
+ await this.client.ping(buildRequestOptions(MCP_LIVENESS_PROBE_TIMEOUT_MS, signal));
120
+ }
121
+
122
+ private async closeStartedClient(): Promise<void> {
123
+ if (!this.started) return;
124
+ this.started = false;
125
+ await this.client.close();
126
+ }
127
+
128
+ private installTransportHooks(): void {
129
+ if (this.hooksInstalled) return;
130
+ this.hooksInstalled = true;
131
+ this.client.onclose = () => {
132
+ if (this.closed) return;
133
+ if (!this.ready) return;
134
+ this.fireUnexpectedClose({ error: this.lastTransportError });
135
+ };
136
+ this.client.onerror = (error) => {
137
+ this.lastTransportError = error;
138
+ if (this.closed) return;
139
+ if (!this.ready) return;
140
+ if (isTerminalSseTransportError(error)) {
141
+ this.fireUnexpectedClose({ error });
142
+ }
143
+ };
144
+ }
145
+
146
+ private fireUnexpectedClose(reason: UnexpectedCloseReason): void {
147
+ if (this.unexpectedCloseFired) return;
148
+ this.unexpectedCloseFired = true;
149
+ const listener = this.unexpectedCloseListener;
150
+ if (listener !== undefined) {
151
+ listener(reason);
152
+ } else {
153
+ this.pendingUnexpectedClose = reason;
154
+ }
155
+ }
156
+ }
157
+
158
+ export function isTerminalSseTransportError(error: Error): boolean {
159
+ if (error.name === 'UnauthorizedError') return true;
160
+ return error instanceof SseError && error.code !== undefined;
161
+ }
packages/agent-core-v2/src/mcpCore/client-stdio.ts ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { ReadBuffer, serializeMessage } from '@modelcontextprotocol/sdk/shared/stdio.js';
3
+ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
4
+ import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
5
+
6
+ import { ErrorCodes, Error2 } from '#/errors';
7
+ import type { IHostProcess } from '#/os/interface/hostProcess';
8
+ import type { IRuntimeResolver } from '#/workspace/workspaceInstance/workspaceInstanceManager';
9
+ import { proxyEnvForChild, reconcileChildNoProxy } from '#/_base/utils/proxy';
10
+
11
+ import {
12
+ buildRequestOptions,
13
+ KIMI_MCP_CLIENT_NAME,
14
+ KIMI_MCP_CLIENT_VERSION,
15
+ MCP_LIVENESS_PROBE_TIMEOUT_MS,
16
+ toMcpToolDefinition,
17
+ toMcpToolResult,
18
+ type UnexpectedCloseListener,
19
+ type UnexpectedCloseReason,
20
+ } from './client-shared';
21
+ import type { McpServerStdioConfig } from './config-schema';
22
+ import type { MCPClient, MCPToolDefinition, MCPToolResult } from './types';
23
+
24
+ export interface StdioMcpClientOptions {
25
+ readonly clientName?: string;
26
+ readonly clientVersion?: string;
27
+ readonly startupTimeoutMs?: number;
28
+ readonly toolCallTimeoutMs?: number;
29
+ readonly defaultCwd?: string;
30
+ readonly runtimeResolver: IRuntimeResolver;
31
+ readonly workspaceId: string;
32
+ readonly runtimeId: string;
33
+ }
34
+
35
+ const STDERR_BUFFER_CAPACITY = 4 * 1024;
36
+
37
+ export class StdioMcpClient implements MCPClient {
38
+ private readonly client: Client;
39
+ private readonly transport: RuntimeStdioTransport;
40
+ private readonly startupTimeoutMs?: number;
41
+ private readonly toolCallTimeoutMs?: number;
42
+ private readonly stderrBuffer = new BoundedTail(STDERR_BUFFER_CAPACITY);
43
+ private started = false;
44
+ private closed = false;
45
+ private ready = false;
46
+ private hooksInstalled = false;
47
+ private unexpectedCloseListener: UnexpectedCloseListener | undefined;
48
+ private lastTransportError: Error | undefined;
49
+ private pendingUnexpectedClose: UnexpectedCloseReason | undefined;
50
+
51
+ static readonly stderrBufferCapacity = STDERR_BUFFER_CAPACITY;
52
+
53
+ constructor(config: McpServerStdioConfig, options: StdioMcpClientOptions) {
54
+ if (config.executor !== undefined && config.executor !== 'local') {
55
+ throw new Error2(ErrorCodes.NOT_IMPLEMENTED, `MCP stdio executor '${config.executor}' is not yet implemented`);
56
+ }
57
+ this.transport = new RuntimeStdioTransport(config, options, this.stderrBuffer);
58
+ this.client = new Client({
59
+ name: options.clientName ?? KIMI_MCP_CLIENT_NAME,
60
+ version: options.clientVersion ?? KIMI_MCP_CLIENT_VERSION,
61
+ });
62
+ this.startupTimeoutMs = options.startupTimeoutMs;
63
+ this.toolCallTimeoutMs = options.toolCallTimeoutMs;
64
+ }
65
+
66
+ async connect(): Promise<void> {
67
+ if (this.closed) {
68
+ throw new Error2(ErrorCodes.MCP_STARTUP_FAILED, 'MCP stdio client is closed');
69
+ }
70
+ if (this.started) return;
71
+ this.started = true;
72
+ this.installTransportHooks();
73
+ try {
74
+ await this.client.connect(
75
+ this.transport,
76
+ buildRequestOptions(this.startupTimeoutMs, undefined),
77
+ );
78
+ } catch (error) {
79
+ await this.closeStartedClient();
80
+ throw error;
81
+ }
82
+ if (this.closed) {
83
+ await this.closeStartedClient();
84
+ throw new Error2(ErrorCodes.MCP_STARTUP_FAILED, 'MCP stdio client was closed during startup');
85
+ }
86
+ this.ready = true;
87
+ }
88
+
89
+ async close(): Promise<void> {
90
+ if (this.closed) return;
91
+ this.closed = true;
92
+ await this.closeStartedClient();
93
+ }
94
+
95
+ onUnexpectedClose(listener: UnexpectedCloseListener): void {
96
+ this.unexpectedCloseListener = listener;
97
+ const pending = this.pendingUnexpectedClose;
98
+ if (pending !== undefined) {
99
+ this.pendingUnexpectedClose = undefined;
100
+ listener(pending);
101
+ }
102
+ }
103
+
104
+ stderrSnapshot(): string {
105
+ return this.stderrBuffer.snapshot();
106
+ }
107
+
108
+ async listTools(): Promise<MCPToolDefinition[]> {
109
+ const result = await this.client.listTools(
110
+ undefined,
111
+ buildRequestOptions(this.startupTimeoutMs, undefined),
112
+ );
113
+ return result.tools.map(toMcpToolDefinition);
114
+ }
115
+
116
+ async callTool(
117
+ name: string,
118
+ args: Record<string, unknown>,
119
+ signal?: AbortSignal,
120
+ ): Promise<MCPToolResult> {
121
+ const requestOptions = buildRequestOptions(this.toolCallTimeoutMs, signal);
122
+ const result = await this.client.callTool({ name, arguments: args }, undefined, requestOptions);
123
+ return toMcpToolResult(result);
124
+ }
125
+
126
+ async ping(signal?: AbortSignal): Promise<void> {
127
+ await this.client.ping(buildRequestOptions(MCP_LIVENESS_PROBE_TIMEOUT_MS, signal));
128
+ }
129
+
130
+ private async closeStartedClient(): Promise<void> {
131
+ if (!this.started) return;
132
+ this.started = false;
133
+ await this.client.close();
134
+ }
135
+
136
+ private installTransportHooks(): void {
137
+ if (this.hooksInstalled) return;
138
+ this.hooksInstalled = true;
139
+ this.client.onclose = () => {
140
+ if (this.closed) return;
141
+ if (!this.ready) return;
142
+ const stderr = this.stderrBuffer.snapshot();
143
+ const reason: UnexpectedCloseReason = {
144
+ error: this.lastTransportError,
145
+ stderr: stderr.length > 0 ? stderr : undefined,
146
+ };
147
+ const listener = this.unexpectedCloseListener;
148
+ if (listener !== undefined) {
149
+ listener(reason);
150
+ } else {
151
+ this.pendingUnexpectedClose = reason;
152
+ }
153
+ };
154
+ this.client.onerror = (error) => {
155
+ this.lastTransportError = error;
156
+ };
157
+ }
158
+ }
159
+
160
+ class RuntimeStdioTransport implements Transport {
161
+ onclose?: () => void;
162
+ onerror?: (error: Error) => void;
163
+ onmessage?: <T extends JSONRPCMessage>(message: T) => void;
164
+ private readonly readBuffer = new ReadBuffer();
165
+ private process: IHostProcess | undefined;
166
+ private lease: ReturnType<IRuntimeResolver['acquire']> | undefined;
167
+ private started = false;
168
+ private closed = false;
169
+
170
+ constructor(
171
+ private readonly config: McpServerStdioConfig,
172
+ private readonly options: StdioMcpClientOptions,
173
+ private readonly stderr: BoundedTail,
174
+ ) {}
175
+
176
+ async start(): Promise<void> {
177
+ if (this.started) throw new Error('Runtime stdio transport is already started');
178
+ if (this.closed) throw new Error('Runtime stdio transport is closed');
179
+ this.started = true;
180
+ const lease = this.options.runtimeResolver.acquire(
181
+ { workspaceId: this.options.workspaceId, runtimeId: this.options.runtimeId },
182
+ ['process'],
183
+ );
184
+ this.lease = lease;
185
+ try {
186
+ const base = lease.runtime.path.resolve(this.options.defaultCwd ?? lease.runtime.environment.homeDir);
187
+ const cwd = this.config.cwd === undefined ? base : lease.runtime.path.resolve(base, this.config.cwd);
188
+ const process = lease.track(await lease.runtime.process!.spawn(
189
+ this.config.command,
190
+ this.config.args,
191
+ { cwd, env: mergeStdioEnv(this.config.env) },
192
+ ));
193
+ this.process = process;
194
+ lease.track(this);
195
+ process.stdin.on('error', (error: Error) => this.onerror?.(error));
196
+ process.stdout.on('data', (chunk: Buffer | string) => this.onData(chunk));
197
+ process.stdout.on('end', () => this.finish());
198
+ process.stdout.on('error', (error: Error) => this.onerror?.(error));
199
+ process.stderr.on('data', (chunk: Buffer | string) => {
200
+ this.stderr.push(typeof chunk === 'string' ? chunk : chunk.toString('utf8'));
201
+ });
202
+ process.stderr.on('error', (error: Error) => this.onerror?.(error));
203
+ void process.wait().then(
204
+ () => this.finish(),
205
+ (error: unknown) => {
206
+ this.onerror?.(error instanceof Error ? error : new Error(String(error)));
207
+ this.finish();
208
+ },
209
+ );
210
+ } catch (error) {
211
+ this.lease = undefined;
212
+ lease.dispose();
213
+ throw error;
214
+ }
215
+ }
216
+
217
+ async send(message: JSONRPCMessage): Promise<void> {
218
+ const process = this.process;
219
+ if (process === undefined || this.closed) throw new Error('Runtime stdio transport is not running');
220
+ const data = serializeMessage(message);
221
+ await new Promise<void>((resolve, reject) => {
222
+ process.stdin.write(data, (error) => {
223
+ if (error !== null && error !== undefined) reject(error);
224
+ else resolve();
225
+ });
226
+ });
227
+ }
228
+
229
+ dispose(): Promise<void> {
230
+ return this.close();
231
+ }
232
+
233
+ async close(): Promise<void> {
234
+ if (this.closed) return;
235
+ this.closed = true;
236
+ const process = this.process;
237
+ this.process = undefined;
238
+ if (process !== undefined) {
239
+ try {
240
+ await process.kill();
241
+ } catch {}
242
+ void process.dispose();
243
+ }
244
+ this.readBuffer.clear();
245
+ const lease = this.lease;
246
+ this.lease = undefined;
247
+ lease?.dispose();
248
+ this.onclose?.();
249
+ }
250
+
251
+ private onData(chunk: Buffer | string): void {
252
+ this.readBuffer.append(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
253
+ while (true) {
254
+ try {
255
+ const message = this.readBuffer.readMessage();
256
+ if (message === null) return;
257
+ this.onmessage?.(message);
258
+ } catch (error) {
259
+ this.onerror?.(error instanceof Error ? error : new Error(String(error)));
260
+ }
261
+ }
262
+ }
263
+
264
+ private finish(): void {
265
+ if (this.closed) return;
266
+ this.closed = true;
267
+ this.process = undefined;
268
+ this.readBuffer.clear();
269
+ const lease = this.lease;
270
+ this.lease = undefined;
271
+ lease?.dispose();
272
+ this.onclose?.();
273
+ }
274
+ }
275
+
276
+ class BoundedTail {
277
+ private buffer = '';
278
+ constructor(private readonly capacity: number) {}
279
+
280
+ push(chunk: string): void {
281
+ this.buffer += chunk;
282
+ if (this.buffer.length > this.capacity) {
283
+ this.buffer = this.buffer.slice(this.buffer.length - this.capacity);
284
+ }
285
+ }
286
+
287
+ snapshot(): string {
288
+ return this.buffer;
289
+ }
290
+ }
291
+
292
+ export function mergeStdioEnv(
293
+ configEnv?: Record<string, string>,
294
+ parentEnv: Readonly<Record<string, string | undefined>> = process.env,
295
+ ): Record<string, string> {
296
+ const merged: Record<string, string> = {};
297
+ for (const [key, value] of Object.entries(parentEnv)) {
298
+ if (value !== undefined) merged[key] = value;
299
+ }
300
+ if (configEnv !== undefined) Object.assign(merged, configEnv);
301
+ Object.assign(merged, proxyEnvForChild(merged));
302
+ reconcileChildNoProxy(merged, configEnv);
303
+ return merged;
304
+ }
packages/agent-core-v2/src/mcpCore/config-schema.ts ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { z } from 'zod';
2
+
3
+ const StringRecordSchema = z.record(z.string(), z.string());
4
+
5
+ export const MAX_MCP_TIMEOUT_MS = 2_147_483_647;
6
+ export const McpTimeoutMsSchema = z.number().int().min(1).max(MAX_MCP_TIMEOUT_MS);
7
+
8
+ const McpServerCommonFields = {
9
+ enabled: z.boolean().optional(),
10
+ deferred: z.boolean().optional(),
11
+ startupTimeoutMs: McpTimeoutMsSchema.optional(),
12
+ toolTimeoutMs: McpTimeoutMsSchema.optional(),
13
+ enabledTools: z.array(z.string()).optional(),
14
+ disabledTools: z.array(z.string()).optional(),
15
+ } as const;
16
+
17
+ export const McpServerStdioConfigSchema = z.object({
18
+ transport: z.literal('stdio'),
19
+ command: z.string().min(1),
20
+ args: z.array(z.string()).optional(),
21
+ env: StringRecordSchema.optional(),
22
+ cwd: z.string().optional(),
23
+ executor: z.enum(['local', 'kaos']).optional(),
24
+ runtime_id: z.string().min(1).optional(),
25
+ ...McpServerCommonFields,
26
+ });
27
+
28
+ export type McpServerStdioConfig = z.infer<typeof McpServerStdioConfigSchema>;
29
+
30
+ export const McpServerHttpConfigSchema = z.object({
31
+ transport: z.literal('http'),
32
+ url: z.string().url(),
33
+ headers: StringRecordSchema.optional(),
34
+ auth: z.literal('oauth').optional(),
35
+ bearerTokenEnvVar: z.string().min(1).optional(),
36
+ ...McpServerCommonFields,
37
+ });
38
+
39
+ export type McpServerHttpConfig = z.infer<typeof McpServerHttpConfigSchema>;
40
+
41
+ export const McpServerSseConfigSchema = z.object({
42
+ transport: z.literal('sse'),
43
+ url: z.string().url(),
44
+ headers: StringRecordSchema.optional(),
45
+ auth: z.literal('oauth').optional(),
46
+ bearerTokenEnvVar: z.string().min(1).optional(),
47
+ ...McpServerCommonFields,
48
+ });
49
+
50
+ export type McpServerSseConfig = z.infer<typeof McpServerSseConfigSchema>;
51
+ export type McpRemoteServerConfig = McpServerHttpConfig | McpServerSseConfig;
52
+
53
+ const McpServerConfigDiscriminatedSchema = z.discriminatedUnion('transport', [
54
+ McpServerStdioConfigSchema,
55
+ McpServerHttpConfigSchema,
56
+ McpServerSseConfigSchema,
57
+ ]);
58
+
59
+ export const McpServerConfigSchema = z.preprocess((raw) => {
60
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return raw;
61
+ const obj = raw as Record<string, unknown>;
62
+ if ('transport' in obj) return obj;
63
+ if (typeof obj['command'] === 'string') return { ...obj, transport: 'stdio' };
64
+ if (typeof obj['url'] === 'string') return { ...obj, transport: 'http' };
65
+ return obj;
66
+ }, McpServerConfigDiscriminatedSchema);
67
+
68
+ export type McpServerConfig = z.infer<typeof McpServerConfigSchema>;
packages/agent-core-v2/src/mcpCore/configView.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { McpServerConfig } from './config-schema';
2
+
3
+ export type McpServerConfigView =
4
+ | (Omit<Extract<McpServerConfig, { readonly transport: 'stdio' }>, 'env'> & {
5
+ readonly envKeys?: readonly string[];
6
+ })
7
+ | (Omit<Exclude<McpServerConfig, { readonly transport: 'stdio' }>, 'headers'> & {
8
+ readonly headerKeys?: readonly string[];
9
+ });
10
+
11
+ export function toMcpServerConfigView(config: McpServerConfig): McpServerConfigView {
12
+ if (config.transport === 'stdio') {
13
+ const { env, ...safe } = config;
14
+ return env === undefined ? safe : { ...safe, envKeys: Object.keys(env).toSorted() };
15
+ }
16
+ const { headers, ...safe } = config;
17
+ return headers === undefined ? safe : { ...safe, headerKeys: Object.keys(headers).toSorted() };
18
+ }
packages/agent-core-v2/src/mcpCore/connection-manager.ts ADDED
@@ -0,0 +1,603 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ErrorCodes, Error2 } from '#/errors';
2
+ import type { McpServerConfig } from './config-schema';
3
+ import type { ILogger as Logger } from '#/_base/log/log';
4
+ import type { ToolDescription as Tool } from '#human/llm/message';
5
+ import { HostProcessError, HostProcessErrorCode } from '#/os/interface/hostProcess';
6
+
7
+ import { abortable } from '#/_base/utils/abort';
8
+ import { HttpMcpClient } from './client-http';
9
+ import { isRemoteMcpConfig } from './client-remote';
10
+ import { SseMcpClient } from './client-sse';
11
+ import type { UnexpectedCloseReason } from './client-shared';
12
+ import { StdioMcpClient } from './client-stdio';
13
+ import type { McpOAuthService } from '#/mcpCore/oauth/service';
14
+ import { assertMcpInputSchema, type MCPClient, type MCPToolDefinition } from './types';
15
+
16
+ export type McpServerStatus = 'pending' | 'connected' | 'failed' | 'disabled' | 'needs-auth' | 'removed';
17
+
18
+ export interface McpServerEntry {
19
+ readonly name: string;
20
+ readonly transport: McpServerConfig['transport'];
21
+ readonly status: McpServerStatus;
22
+ readonly toolCount: number;
23
+ readonly error?: string;
24
+ }
25
+
26
+ interface InternalEntry {
27
+ readonly name: string;
28
+ readonly config: McpServerConfig;
29
+ attemptId: number;
30
+ status: McpServerStatus;
31
+ tools?: readonly Tool[];
32
+ rawTools?: readonly MCPToolDefinition[];
33
+ enabledNames?: ReadonlySet<string>;
34
+ error?: string;
35
+ client?: RuntimeMcpClient;
36
+ }
37
+
38
+ export type McpStatusListener = (entry: McpServerEntry) => void;
39
+
40
+ export interface McpConnectionView {
41
+ readonly oauthService: McpOAuthService | undefined;
42
+ list(): readonly McpServerEntry[];
43
+ get(name: string): McpServerEntry | undefined;
44
+ configOf(name: string): McpServerConfig | undefined;
45
+ resolved(
46
+ name: string,
47
+ ):
48
+ | {
49
+ client: MCPClient;
50
+ tools: readonly Tool[];
51
+ rawTools: readonly MCPToolDefinition[];
52
+ enabledNames: ReadonlySet<string>;
53
+ deferred: boolean;
54
+ }
55
+ | undefined;
56
+ getRemoteServerUrl(name: string): string | undefined;
57
+ reconnect(name: string): Promise<void>;
58
+ reconnectAndJoin(name: string): Promise<void>;
59
+ waitForInitialLoad(signal?: AbortSignal): Promise<void>;
60
+ initialLoadDurationMs(): number;
61
+ onStatusChange(listener: McpStatusListener): () => void;
62
+ }
63
+
64
+ const DEFAULT_STARTUP_TIMEOUT_MS = 30_000;
65
+
66
+ type RuntimeMcpClient = StdioMcpClient | HttpMcpClient | SseMcpClient;
67
+ const defaultLog: Logger = {
68
+ error: () => {},
69
+ warn: () => {},
70
+ info: () => {},
71
+ debug: () => {},
72
+ child: () => defaultLog,
73
+ };
74
+
75
+ export interface McpDefaultTimeouts {
76
+ readonly startupTimeoutMs?: number;
77
+ readonly toolTimeoutMs?: number;
78
+ }
79
+
80
+ export interface McpConnectionManagerOptions {
81
+ readonly envLookup?: (name: string) => string | undefined;
82
+ readonly stdioCwd?: string;
83
+ readonly runtimeResolver?: import('#/workspace/workspaceInstance/workspaceInstanceManager').IRuntimeResolver;
84
+ readonly workspaceId?: string;
85
+ readonly runtimeId?: string;
86
+ readonly requireStdioRuntimeId?: boolean;
87
+ readonly oauthService?: McpOAuthService;
88
+ readonly log?: Logger;
89
+ readonly resolveDefaultTimeouts?: () => McpDefaultTimeouts;
90
+ readonly resolveClientName?: () => string | undefined;
91
+ }
92
+
93
+ export class McpConnectionManager implements McpConnectionView {
94
+ private readonly entries = new Map<string, InternalEntry>();
95
+ private readonly listeners = new Set<McpStatusListener>();
96
+ private readonly inFlightReconnects = new Map<string, Promise<void>>();
97
+ private initialLoad: Promise<void> = Promise.resolve();
98
+ private initialLoadAttemptId = 0;
99
+ private initialLoadStartedAt: number | undefined;
100
+ private initialLoadFinishedAt: number | undefined;
101
+
102
+ readonly oauthService: McpOAuthService | undefined;
103
+ private readonly log: Logger;
104
+
105
+ constructor(private readonly options: McpConnectionManagerOptions = {}) {
106
+ this.oauthService = options.oauthService;
107
+ this.log = options.log ?? defaultLog;
108
+ }
109
+
110
+ getRemoteServerUrl(name: string): string | undefined {
111
+ const entry = this.entries.get(name);
112
+ if (entry === undefined) return undefined;
113
+ if (!isRemoteMcpConfig(entry.config)) return undefined;
114
+ return entry.config.url;
115
+ }
116
+
117
+ getHttpServerUrl(name: string): string | undefined {
118
+ return this.getRemoteServerUrl(name);
119
+ }
120
+
121
+ onStatusChange(listener: McpStatusListener): () => void {
122
+ this.listeners.add(listener);
123
+ return () => {
124
+ this.listeners.delete(listener);
125
+ };
126
+ }
127
+
128
+ list(): readonly McpServerEntry[] {
129
+ return Array.from(this.entries.values(), toPublicEntry);
130
+ }
131
+
132
+ get(name: string): McpServerEntry | undefined {
133
+ const entry = this.entries.get(name);
134
+ return entry !== undefined ? toPublicEntry(entry) : undefined;
135
+ }
136
+
137
+ configOf(name: string): McpServerConfig | undefined {
138
+ return this.entries.get(name)?.config;
139
+ }
140
+
141
+ resolved(
142
+ name: string,
143
+ ):
144
+ | {
145
+ client: MCPClient;
146
+ tools: readonly Tool[];
147
+ rawTools: readonly MCPToolDefinition[];
148
+ enabledNames: ReadonlySet<string>;
149
+ deferred: boolean;
150
+ }
151
+ | undefined {
152
+ const entry = this.entries.get(name);
153
+ if (
154
+ entry?.status !== 'connected' ||
155
+ entry.tools === undefined ||
156
+ entry.rawTools === undefined ||
157
+ entry.client === undefined
158
+ ) {
159
+ return undefined;
160
+ }
161
+ return {
162
+ client: entry.client,
163
+ tools: entry.tools,
164
+ rawTools: entry.rawTools,
165
+ enabledNames: entry.enabledNames ?? new Set(entry.tools.map((t) => t.name)),
166
+ deferred: entry.config.deferred === true,
167
+ };
168
+ }
169
+
170
+ connectAll(configs: Record<string, McpServerConfig>): Promise<void> {
171
+ const attemptId = ++this.initialLoadAttemptId;
172
+ this.initialLoadStartedAt = Date.now();
173
+ this.initialLoadFinishedAt = undefined;
174
+ const initialLoad = this.connectAllNow(configs).finally(() => {
175
+ if (this.initialLoadAttemptId === attemptId) {
176
+ this.initialLoadFinishedAt = Date.now();
177
+ }
178
+ });
179
+ this.initialLoad = initialLoad;
180
+ return initialLoad;
181
+ }
182
+
183
+ async connect(name: string, config: McpServerConfig): Promise<void> {
184
+ const previous = this.entries.get(name);
185
+ if (previous !== undefined) {
186
+ if (
187
+ (previous.status === 'pending' || previous.status === 'connected') &&
188
+ mcpServerConfigsEqual(previous.config, config)
189
+ ) {
190
+ return;
191
+ }
192
+ await this.closeClient(previous);
193
+ }
194
+ const disabled = config.enabled === false;
195
+ const entry: InternalEntry = {
196
+ name,
197
+ config,
198
+ attemptId: 0,
199
+ status: disabled ? 'disabled' : 'pending',
200
+ };
201
+ this.entries.set(name, entry);
202
+ this.emit(entry);
203
+ if (!disabled) {
204
+ await this.connectOne(entry, this.beginConnectAttempt(entry));
205
+ }
206
+ }
207
+
208
+ async remove(name: string): Promise<boolean> {
209
+ const entry = this.entries.get(name);
210
+ if (entry === undefined) return false;
211
+ await this.closeClient(entry);
212
+ entry.status = 'disabled';
213
+ entry.tools = undefined;
214
+ entry.enabledNames = undefined;
215
+ entry.rawTools = undefined;
216
+ entry.error = undefined;
217
+ this.emit(entry);
218
+ this.entries.delete(name);
219
+ return true;
220
+ }
221
+
222
+ async markRemoved(name: string): Promise<boolean> {
223
+ const entry = this.entries.get(name);
224
+ if (entry === undefined) return false;
225
+ await this.closeClient(entry);
226
+ entry.status = 'removed';
227
+ entry.tools = undefined;
228
+ entry.enabledNames = undefined;
229
+ entry.rawTools = undefined;
230
+ entry.error = undefined;
231
+ this.emit(entry);
232
+ return true;
233
+ }
234
+
235
+ waitForInitialLoad(signal?: AbortSignal): Promise<void> {
236
+ signal?.throwIfAborted();
237
+ if (signal === undefined) return this.initialLoad;
238
+ return abortable(this.initialLoad, signal);
239
+ }
240
+
241
+ initialLoadDurationMs(): number {
242
+ if (this.initialLoadStartedAt === undefined) return 0;
243
+ const endedAt = this.initialLoadFinishedAt ?? Date.now();
244
+ return Math.max(0, endedAt - this.initialLoadStartedAt);
245
+ }
246
+
247
+ private async connectAllNow(configs: Record<string, McpServerConfig>): Promise<void> {
248
+ const tasks: Promise<unknown>[] = [];
249
+ for (const [name, config] of Object.entries(configs)) {
250
+ const disabled = config.enabled === false;
251
+ const entry: InternalEntry = {
252
+ name,
253
+ config,
254
+ attemptId: 0,
255
+ status: disabled ? 'disabled' : 'pending',
256
+ };
257
+ this.entries.set(name, entry);
258
+ this.emit(entry);
259
+ if (!disabled) {
260
+ tasks.push(this.connectOne(entry, this.beginConnectAttempt(entry)));
261
+ }
262
+ }
263
+ await Promise.allSettled(tasks);
264
+ }
265
+
266
+ async reconnect(name: string): Promise<void> {
267
+ const entry = this.entries.get(name);
268
+ if (entry === undefined || entry.status === 'removed') {
269
+ throw new Error2(ErrorCodes.MCP_SERVER_NOT_FOUND, `Unknown MCP server: ${name}`);
270
+ }
271
+ if (entry.config.enabled === false) {
272
+ throw new Error2(ErrorCodes.MCP_SERVER_DISABLED, `MCP server is disabled: ${name}`);
273
+ }
274
+ const attemptId = this.beginConnectAttempt(entry);
275
+ await this.closeClient(entry);
276
+ if (!this.isCurrent(entry, attemptId)) return;
277
+ entry.status = 'pending';
278
+ entry.tools = undefined;
279
+ entry.enabledNames = undefined;
280
+ entry.rawTools = undefined;
281
+ entry.error = undefined;
282
+ this.emit(entry);
283
+ await this.connectOne(entry, attemptId);
284
+ }
285
+
286
+ reconnectAndJoin(name: string): Promise<void> {
287
+ const existing = this.inFlightReconnects.get(name);
288
+ if (existing !== undefined) return existing;
289
+ const work = this.reconnect(name).finally(() => {
290
+ if (this.inFlightReconnects.get(name) === work) {
291
+ this.inFlightReconnects.delete(name);
292
+ }
293
+ });
294
+ this.inFlightReconnects.set(name, work);
295
+ return work;
296
+ }
297
+
298
+ async reconnectAfterCurrent(name: string): Promise<void> {
299
+ const existing = this.inFlightReconnects.get(name);
300
+ if (existing !== undefined) await existing.catch(() => undefined);
301
+ await this.reconnectAndJoin(name);
302
+ }
303
+
304
+ async shutdown(): Promise<void> {
305
+ const entries = Array.from(this.entries.values());
306
+ this.entries.clear();
307
+ const tasks = entries.map((entry) => this.closeClient(entry));
308
+ await Promise.allSettled(tasks);
309
+ }
310
+
311
+ private async connectOne(entry: InternalEntry, attemptId: number): Promise<void> {
312
+ const timeoutMs =
313
+ entry.config.startupTimeoutMs ??
314
+ this.options.resolveDefaultTimeouts?.().startupTimeoutMs ??
315
+ DEFAULT_STARTUP_TIMEOUT_MS;
316
+
317
+ let client: RuntimeMcpClient | undefined;
318
+ try {
319
+ const startupClient = await this.createClient(entry.config, entry.name, timeoutMs);
320
+ client = startupClient;
321
+ entry.client = startupClient;
322
+ const discovered = await withTimeout(
323
+ this.connectAndDiscoverTools(startupClient),
324
+ timeoutMs,
325
+ () => {
326
+ void this.closeRuntimeClient(startupClient);
327
+ },
328
+ );
329
+ if (!this.isCurrent(entry, attemptId)) {
330
+ await this.closeRuntimeClient(startupClient);
331
+ return;
332
+ }
333
+ entry.tools = discovered.tools;
334
+ entry.rawTools = discovered.rawTools;
335
+ entry.enabledNames = computeEnabledNames(entry.config, discovered.tools);
336
+ entry.status = 'connected';
337
+ this.watchForUnexpectedClose(entry, startupClient, attemptId);
338
+ } catch (error) {
339
+ if (!this.isCurrent(entry, attemptId)) {
340
+ if (client !== undefined) {
341
+ await this.closeRuntimeClient(client);
342
+ }
343
+ return;
344
+ }
345
+ if (this.shouldMarkNeedsAuth(entry, error)) {
346
+ entry.status = 'needs-auth';
347
+ entry.error = `${entry.name} requires OAuth — run /mcp-config login ${entry.name}`;
348
+ } else {
349
+ entry.status = 'failed';
350
+ entry.error = formatStartupError(error, client);
351
+ }
352
+ entry.tools = undefined;
353
+ entry.enabledNames = undefined;
354
+ entry.rawTools = undefined;
355
+ await this.closeClient(entry);
356
+ }
357
+ if (!this.isCurrent(entry, attemptId)) return;
358
+ this.emit(entry);
359
+ }
360
+
361
+ private watchForUnexpectedClose(
362
+ entry: InternalEntry,
363
+ client: RuntimeMcpClient,
364
+ attemptId: number,
365
+ ): void {
366
+ client.onUnexpectedClose((reason) => {
367
+ if (!this.isCurrent(entry, attemptId)) return;
368
+ if (entry.client !== client) return;
369
+ entry.status = 'failed';
370
+ entry.error = formatUnexpectedCloseError(entry.name, reason);
371
+ entry.tools = undefined;
372
+ entry.enabledNames = undefined;
373
+ entry.rawTools = undefined;
374
+ entry.client = undefined;
375
+ void this.closeRuntimeClient(client);
376
+ this.emit(entry);
377
+ });
378
+ }
379
+
380
+ private beginConnectAttempt(entry: InternalEntry): number {
381
+ entry.attemptId += 1;
382
+ return entry.attemptId;
383
+ }
384
+
385
+ private async createClient(
386
+ config: McpServerConfig,
387
+ name: string,
388
+ startupTimeoutMs: number,
389
+ ): Promise<RuntimeMcpClient> {
390
+ const toolCallTimeoutMs =
391
+ config.toolTimeoutMs ?? this.options.resolveDefaultTimeouts?.().toolTimeoutMs;
392
+ const clientName = this.options.resolveClientName?.();
393
+ if (config.transport === 'stdio') {
394
+ const runtimeResolver = this.options.runtimeResolver;
395
+ const workspaceId = this.options.workspaceId;
396
+ const runtimeId = config.runtime_id ?? this.options.runtimeId;
397
+ if (runtimeResolver === undefined || workspaceId === undefined || runtimeId === undefined || (this.options.requireStdioRuntimeId === true && config.runtime_id === undefined)) {
398
+ throw new Error('MCP stdio requires runtime_id and runtime binding');
399
+ }
400
+ return new StdioMcpClient(config, {
401
+ startupTimeoutMs,
402
+ toolCallTimeoutMs,
403
+ defaultCwd: this.options.stdioCwd,
404
+ clientName,
405
+ runtimeResolver,
406
+ workspaceId,
407
+ runtimeId,
408
+ });
409
+ }
410
+ if (config.transport === 'sse') {
411
+ return new SseMcpClient(config, {
412
+ startupTimeoutMs,
413
+ toolCallTimeoutMs,
414
+ envLookup: this.options.envLookup,
415
+ oauthProvider: await this.resolveOAuthProvider(config, name),
416
+ clientName,
417
+ });
418
+ }
419
+ return new HttpMcpClient(config, {
420
+ startupTimeoutMs,
421
+ toolCallTimeoutMs,
422
+ envLookup: this.options.envLookup,
423
+ oauthProvider: await this.resolveOAuthProvider(config, name),
424
+ clientName,
425
+ });
426
+ }
427
+
428
+ private async resolveOAuthProvider(
429
+ config: McpServerConfig,
430
+ name: string,
431
+ ): Promise<ReturnType<McpOAuthService['getProvider']> | undefined> {
432
+ const oauthService = this.oauthService;
433
+ if (oauthService === undefined) return undefined;
434
+ if (!isRemoteMcpConfig(config)) return undefined;
435
+ if (config.bearerTokenEnvVar !== undefined) return undefined;
436
+ if (!(await oauthService.hasTokens(name, config.url))) return undefined;
437
+ return oauthService.getProvider(name, config.url);
438
+ }
439
+
440
+ private shouldMarkNeedsAuth(entry: InternalEntry, error: unknown): boolean {
441
+ if (this.oauthService === undefined) return false;
442
+ if (!isRemoteMcpConfig(entry.config)) return false;
443
+ if (entry.config.bearerTokenEnvVar !== undefined) return false;
444
+ if (entry.config.headers !== undefined && entry.config.auth !== 'oauth') return false;
445
+ return isUnauthorizedLikeError(error);
446
+ }
447
+
448
+ private async connectAndDiscoverTools(
449
+ client: RuntimeMcpClient,
450
+ ): Promise<{ tools: Tool[]; rawTools: MCPToolDefinition[] }> {
451
+ await client.connect();
452
+ const mcpTools = await client.listTools();
453
+ return {
454
+ rawTools: mcpTools,
455
+ tools: mcpTools.map((mcpTool) => ({
456
+ name: mcpTool.name,
457
+ description: mcpTool.description,
458
+ parameters: assertMcpInputSchema(mcpTool.name, mcpTool.inputSchema),
459
+ })),
460
+ };
461
+ }
462
+
463
+ private async closeClient(entry: InternalEntry): Promise<void> {
464
+ if (entry.client === undefined) return;
465
+ const client = entry.client;
466
+ entry.client = undefined;
467
+ await this.closeRuntimeClient(client);
468
+ }
469
+
470
+ private async closeRuntimeClient(client: RuntimeMcpClient): Promise<void> {
471
+ try {
472
+ await client.close();
473
+ } catch {
474
+ }
475
+ }
476
+
477
+ private isCurrent(entry: InternalEntry, attemptId: number): boolean {
478
+ return this.entries.get(entry.name) === entry && entry.attemptId === attemptId;
479
+ }
480
+
481
+ private emit(entry: InternalEntry): void {
482
+ const view = toPublicEntry(entry);
483
+ if (view.status === 'failed' || view.status === 'needs-auth') {
484
+ this.log.error('mcp server unavailable', {
485
+ server: view.name,
486
+ transport: view.transport,
487
+ status: view.status,
488
+ reason: view.error,
489
+ });
490
+ }
491
+ for (const listener of this.listeners) {
492
+ try {
493
+ listener(view);
494
+ } catch {
495
+ }
496
+ }
497
+ }
498
+ }
499
+
500
+ function toPublicEntry(entry: InternalEntry): McpServerEntry {
501
+ return {
502
+ name: entry.name,
503
+ transport: entry.config.transport,
504
+ status: entry.status,
505
+ toolCount:
506
+ entry.status === 'connected' && entry.enabledNames !== undefined
507
+ ? entry.enabledNames.size
508
+ : 0,
509
+ error: entry.error,
510
+ };
511
+ }
512
+
513
+ function computeEnabledNames(config: McpServerConfig, tools: readonly Tool[]): Set<string> {
514
+ const all = tools.map((t) => t.name);
515
+ const enabledFilter =
516
+ config.enabledTools !== undefined ? new Set(config.enabledTools) : undefined;
517
+ const disabledFilter =
518
+ config.disabledTools !== undefined ? new Set(config.disabledTools) : undefined;
519
+ const allowed = new Set<string>();
520
+ for (const name of all) {
521
+ if (enabledFilter !== undefined && !enabledFilter.has(name)) continue;
522
+ if (disabledFilter !== undefined && disabledFilter.has(name)) continue;
523
+ allowed.add(name);
524
+ }
525
+ return allowed;
526
+ }
527
+
528
+ function isUnauthorizedLikeError(error: unknown): boolean {
529
+ if (!(error instanceof Error)) return false;
530
+ if (error.name === 'UnauthorizedError') return true;
531
+ const code = (error as { code?: unknown }).code;
532
+ if (typeof code === 'number' && code === 401) return true;
533
+ if (typeof code === 'string' && code === '401') return true;
534
+ return /\b401\b/.test(error.message) || /unauthorized/i.test(error.message);
535
+ }
536
+
537
+ function formatStartupError(error: unknown, client: RuntimeMcpClient | undefined): string {
538
+ const source = error instanceof HostProcessError &&
539
+ error.code === HostProcessErrorCode.SpawnFailed &&
540
+ error.cause instanceof Error
541
+ ? error.cause
542
+ : error;
543
+ const base = source instanceof Error ? source.message : String(source);
544
+ const tail = stderrTail(client);
545
+ if (tail === undefined) return base;
546
+ return `${base}\nstderr: ${tail}`;
547
+ }
548
+
549
+ function formatUnexpectedCloseError(name: string, reason: UnexpectedCloseReason): string {
550
+ const parts = [`MCP server "${name}" closed unexpectedly`];
551
+ if (reason.error !== undefined) {
552
+ parts.push(reason.error.message);
553
+ }
554
+ if (reason.stderr !== undefined && reason.stderr.length > 0) {
555
+ parts.push(`stderr: ${reason.stderr.trimEnd()}`);
556
+ }
557
+ return parts.join('\n');
558
+ }
559
+
560
+ function stderrTail(client: RuntimeMcpClient | undefined): string | undefined {
561
+ if (client === undefined) return undefined;
562
+ if (!(client instanceof StdioMcpClient)) return undefined;
563
+ const snapshot = client.stderrSnapshot();
564
+ if (snapshot.length === 0) return undefined;
565
+ return snapshot.trimEnd();
566
+ }
567
+
568
+ export function mcpServerConfigsEqual(a: McpServerConfig, b: McpServerConfig): boolean {
569
+ return stableConfigJson(a) === stableConfigJson(b);
570
+ }
571
+
572
+ function stableConfigJson(value: unknown): string {
573
+ if (Array.isArray(value)) {
574
+ return `[${value.map(stableConfigJson).join(',')}]`;
575
+ }
576
+ if (typeof value === 'object' && value !== null) {
577
+ const entries = Object.entries(value)
578
+ .filter(([, entryValue]) => entryValue !== undefined)
579
+ .map(([key, entryValue]) => `${JSON.stringify(key)}:${stableConfigJson(entryValue)}`)
580
+ .toSorted();
581
+ return `{${entries.join(',')}}`;
582
+ }
583
+ return JSON.stringify(value) ?? 'undefined';
584
+ }
585
+
586
+ async function withTimeout<T>(
587
+ promise: Promise<T>,
588
+ timeoutMs: number,
589
+ onTimeout?: () => void,
590
+ ): Promise<T> {
591
+ let timer: NodeJS.Timeout | undefined;
592
+ try {
593
+ return await new Promise<T>((resolve, reject) => {
594
+ timer = setTimeout(() => {
595
+ onTimeout?.();
596
+ reject(new Error2(ErrorCodes.MCP_STARTUP_FAILED, `Timed out after ${timeoutMs}ms`));
597
+ }, timeoutMs);
598
+ promise.then(resolve, reject);
599
+ });
600
+ } finally {
601
+ if (timer !== undefined) clearTimeout(timer);
602
+ }
603
+ }
packages/agent-core-v2/src/mcpCore/errors.ts ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { registerErrorDomain, type ErrorDomain } from '#/_base/errors/codes';
2
+
3
+ export const McpErrors = {
4
+ codes: {
5
+ MCP_SERVER_NOT_FOUND: 'mcp.server_not_found',
6
+ MCP_SERVER_DISABLED: 'mcp.server_disabled',
7
+ MCP_STARTUP_FAILED: 'mcp.startup_failed',
8
+ MCP_TOOL_NAME_COLLISION: 'mcp.tool_name_collision',
9
+ MCP_OAUTH_FAILED: 'mcp.oauth_failed',
10
+ },
11
+ } as const satisfies ErrorDomain;
12
+
13
+ registerErrorDomain(McpErrors);
packages/agent-core-v2/src/mcpCore/tool-naming.ts ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const MCP_NAME_PREFIX = 'mcp__';
2
+ const MCP_NAME_SEPARATOR = '__';
3
+
4
+ const MAX_QUALIFIED_LENGTH = 64;
5
+
6
+ export function sanitizeMcpNamePart(part: string): string {
7
+ return part.replaceAll(/[^a-zA-Z0-9_-]/g, '_').replaceAll(/_+/g, '_');
8
+ }
9
+
10
+ export function qualifyMcpToolName(serverName: string, toolName: string): string {
11
+ const full = `${MCP_NAME_PREFIX}${sanitizeMcpNamePart(serverName)}${MCP_NAME_SEPARATOR}${sanitizeMcpNamePart(toolName)}`;
12
+ if (full.length <= MAX_QUALIFIED_LENGTH) return full;
13
+
14
+ const hash = stableHash8(full);
15
+ const head = full.slice(0, MAX_QUALIFIED_LENGTH - hash.length - 1);
16
+ return `${head}_${hash}`;
17
+ }
18
+
19
+ function stableHash8(input: string): string {
20
+ let hash = 0x811c9dc5;
21
+ for (let i = 0; i < input.length; i++) {
22
+ hash ^= input.codePointAt(i)!;
23
+ hash = Math.trunc(Math.imul(hash, 0x01000193));
24
+ }
25
+ return hash.toString(16).padStart(8, '0');
26
+ }
packages/agent-core-v2/src/mcpCore/types.ts ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ErrorCodes, Error2 } from '#/errors';
2
+
3
+ export interface MCPEmbeddedResourceContents {
4
+ uri: string;
5
+ mimeType?: string;
6
+ text?: string;
7
+ blob?: string;
8
+ [key: string]: unknown;
9
+ }
10
+
11
+ export interface MCPContentBlock {
12
+ type: string;
13
+ text?: string;
14
+ data?: string;
15
+ mimeType?: string;
16
+ uri?: string;
17
+ resource?: MCPEmbeddedResourceContents;
18
+ [key: string]: unknown;
19
+ }
20
+
21
+ export interface MCPToolResult {
22
+ content: MCPContentBlock[];
23
+ isError: boolean;
24
+ structuredContent?: unknown;
25
+ _meta?: Record<string, unknown>;
26
+ }
27
+
28
+ export interface MCPToolDefinition {
29
+ name: string;
30
+ description: string;
31
+ inputSchema: unknown;
32
+ }
33
+
34
+ export interface MCPClient {
35
+ listTools(): Promise<MCPToolDefinition[]>;
36
+ callTool(
37
+ name: string,
38
+ args: Record<string, unknown>,
39
+ signal?: AbortSignal,
40
+ ): Promise<MCPToolResult>;
41
+ ping(signal?: AbortSignal): Promise<void>;
42
+ }
43
+
44
+ export function assertMcpInputSchema(
45
+ toolName: string,
46
+ inputSchema: unknown,
47
+ ): Record<string, unknown> {
48
+ if (typeof inputSchema === 'object' && inputSchema !== null && !Array.isArray(inputSchema)) {
49
+ return inputSchema as Record<string, unknown>;
50
+ }
51
+ throw new Error2(
52
+ ErrorCodes.MCP_STARTUP_FAILED,
53
+ `Invalid inputSchema for MCP tool "${toolName}": schema must be a JSON object`,
54
+ );
55
+ }