kfc-code-cli 0.0.1-alpha.13 → 0.0.1-alpha.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.mjs +2142 -1827
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -183,75 +183,894 @@ var ContextStateBrokenError = class extends Error {
183
183
  }
184
184
  };
185
185
  //#endregion
186
- //#region ../../packages/kimi-core/src/storage/journal/writer.ts
187
- /**
188
- * Record types the compaction path itself is allowed to write while the
189
- * lifecycle gate is in `compacting` state (§5.1.7 / v2 L2663-L2690).
190
- *
191
- * Background (Slice 6 audit M02): every non-compaction write is drained
192
- * during `compacting` so the in-flight compaction is not racing against
193
- * new Soul output. But compaction still needs to persist its own
194
- * `CompactionRecord` via `context.resetToSummary()`, which goes through
195
- * the same `JournalWriter.append()` entry point. Blanket-rejecting all
196
- * writes in `compacting` would deadlock compaction against itself, so
197
- * this whitelist names the record types that are considered
198
- * "compaction's own writes" and allowed through the gate.
199
- *
200
- * Keep this list narrow: only record types that are *only* emitted from
201
- * inside `TurnManager.executeCompaction` (Phase 2; previously
202
- * `runCompaction`) belong here. Any future compaction-path record
203
- * must be added explicitly.
204
- */
205
- const COMPACTION_OWN_WRITE_TYPES = new Set(["compaction", "session_initialized"]);
186
+ //#region ../../packages/kimi-core/src/storage/schema/records.ts
206
187
  /**
207
- * Record types that must be durable on disk before `append()` resolves
208
- * (§4.5.4 force-flush kinds). Recovery-critical boundary markers live
209
- * here: their absence at replay time breaks the contracts §9.x relies on.
210
- *
211
- * Declared as `ReadonlySet<string>` so we can include future union-member
212
- * strings (e.g. `subagent_completed` / `subagent_failed` from the
213
- * subagent slice) without forcing an out-of-slice edit to `WireRecord`.
188
+ * Fallback producer identity used when the host hasn't supplied one.
189
+ * Version marker makes unset state easy to spot in field reports.
214
190
  */
215
- const FORCE_FLUSH_KINDS = new Set([
216
- "approval_response",
217
- "turn_end",
218
- "subagent_completed",
219
- "subagent_failed",
220
- "session_initialized",
221
- "context_cleared",
222
- "tools_changed"
191
+ const DEFAULT_PRODUCER = {
192
+ kind: "typescript",
193
+ name: "@moonshot-ai/core",
194
+ version: "0.0.0-unset"
195
+ };
196
+ const agentTypeEnum = z.enum([
197
+ "main",
198
+ "sub",
199
+ "independent"
223
200
  ]);
224
- const DEFAULT_PROTOCOL_VERSION = "2.1";
201
+ const _rawWireProducerSchema = z.object({
202
+ kind: z.enum(["python", "typescript"]),
203
+ name: z.string(),
204
+ version: z.string()
205
+ });
206
+ const WireFileMetadataSchema = z.object({
207
+ type: z.literal("metadata"),
208
+ protocol_version: z.string(),
209
+ created_at: z.number(),
210
+ kimi_version: z.string().optional(),
211
+ producer: _rawWireProducerSchema.optional()
212
+ });
213
+ const _rawTurnBeginRecordSchema = z.object({
214
+ type: z.literal("turn_begin"),
215
+ seq: z.number(),
216
+ time: z.number(),
217
+ turn_id: z.string(),
218
+ agent_type: agentTypeEnum,
219
+ user_input: z.string().optional(),
220
+ input_kind: z.enum(["user", "system_trigger"]),
221
+ trigger_source: z.string().optional()
222
+ });
223
+ const _rawTurnEndRecordSchema = z.object({
224
+ type: z.literal("turn_end"),
225
+ seq: z.number(),
226
+ time: z.number(),
227
+ turn_id: z.string(),
228
+ agent_type: agentTypeEnum,
229
+ success: z.boolean(),
230
+ reason: z.enum([
231
+ "done",
232
+ "cancelled",
233
+ "error",
234
+ "interrupted"
235
+ ]),
236
+ usage: z.object({
237
+ input_tokens: z.number(),
238
+ output_tokens: z.number(),
239
+ cache_read_tokens: z.number().optional(),
240
+ cache_write_tokens: z.number().optional(),
241
+ cost_usd: z.number().optional()
242
+ }).optional(),
243
+ synthetic: z.boolean().optional()
244
+ });
245
+ const _userInputPartSchema = z.discriminatedUnion("type", [
246
+ z.object({
247
+ type: z.literal("text"),
248
+ text: z.string()
249
+ }),
250
+ z.object({
251
+ type: z.literal("image_url"),
252
+ image_url: z.object({ url: z.string() })
253
+ }),
254
+ z.object({
255
+ type: z.literal("video_url"),
256
+ video_url: z.object({ url: z.string() })
257
+ })
258
+ ]);
259
+ const _rawUserMessageRecordSchema = z.object({
260
+ type: z.literal("user_message"),
261
+ seq: z.number(),
262
+ time: z.number(),
263
+ turn_id: z.string(),
264
+ content: z.union([z.string(), z.array(_userInputPartSchema).readonly()]),
265
+ uuid: z.string().optional()
266
+ });
267
+ const _rawToolResultRecordSchema = z.object({
268
+ type: z.literal("tool_result"),
269
+ seq: z.number(),
270
+ time: z.number(),
271
+ turn_id: z.string(),
272
+ tool_call_id: z.string(),
273
+ output: z.unknown(),
274
+ is_error: z.boolean().optional(),
275
+ synthetic: z.boolean().optional(),
276
+ uuid: z.string().optional(),
277
+ parent_uuid: z.string().optional()
278
+ });
279
+ const _rawCompactionRecordSchema = z.object({
280
+ type: z.literal("compaction"),
281
+ seq: z.number(),
282
+ time: z.number(),
283
+ summary: z.string(),
284
+ compacted_range: z.object({
285
+ from_turn: z.number(),
286
+ to_turn: z.number(),
287
+ message_count: z.number()
288
+ }),
289
+ pre_compact_tokens: z.number(),
290
+ post_compact_tokens: z.number(),
291
+ trigger: z.enum(["auto", "manual"]),
292
+ archive_file: z.string().optional(),
293
+ uuid: z.string().optional()
294
+ });
295
+ const _rawSystemPromptChangedRecordSchema = z.object({
296
+ type: z.literal("system_prompt_changed"),
297
+ seq: z.number(),
298
+ time: z.number(),
299
+ new_prompt: z.string()
300
+ });
301
+ const _rawToolsChangedRecordSchema = z.object({
302
+ type: z.literal("tools_changed"),
303
+ seq: z.number(),
304
+ time: z.number(),
305
+ operation: z.enum([
306
+ "register",
307
+ "remove",
308
+ "set_active"
309
+ ]),
310
+ tools: z.array(z.string())
311
+ });
312
+ const _rawSystemReminderRecordSchema = z.object({
313
+ type: z.literal("system_reminder"),
314
+ seq: z.number(),
315
+ time: z.number(),
316
+ content: z.string(),
317
+ consumed_at_turn: z.number().optional()
318
+ });
319
+ const _rawNotificationRecordSchema = z.object({
320
+ type: z.literal("notification"),
321
+ seq: z.number(),
322
+ time: z.number(),
323
+ data: z.object({
324
+ id: z.string(),
325
+ category: z.enum([
326
+ "task",
327
+ "agent",
328
+ "system",
329
+ "team"
330
+ ]),
331
+ type: z.string(),
332
+ source_kind: z.string(),
333
+ source_id: z.string(),
334
+ title: z.string(),
335
+ body: z.string(),
336
+ severity: z.enum([
337
+ "info",
338
+ "success",
339
+ "warning",
340
+ "error"
341
+ ]),
342
+ payload: z.record(z.string(), z.unknown()).optional(),
343
+ targets: z.array(z.enum([
344
+ "llm",
345
+ "wire",
346
+ "shell"
347
+ ])),
348
+ dedupe_key: z.string().optional(),
349
+ delivered_at: z.object({
350
+ llm: z.number().optional(),
351
+ wire: z.number().optional(),
352
+ shell: z.number().optional()
353
+ }).optional(),
354
+ envelope_id: z.string().optional()
355
+ })
356
+ });
357
+ const _rawToolDeniedRecordSchema = z.object({
358
+ type: z.literal("tool_denied"),
359
+ seq: z.number(),
360
+ time: z.number(),
361
+ turn_id: z.string(),
362
+ step: z.number(),
363
+ data: z.object({
364
+ tool_call_id: z.string(),
365
+ tool_name: z.string(),
366
+ rule_id: z.string(),
367
+ reason: z.string()
368
+ })
369
+ });
370
+ const _rawStepBeginRecordSchema = z.object({
371
+ type: z.literal("step_begin"),
372
+ seq: z.number(),
373
+ time: z.number(),
374
+ uuid: z.string(),
375
+ turn_id: z.string(),
376
+ step: z.number()
377
+ });
378
+ const _rawStepEndRecordSchema = z.object({
379
+ type: z.literal("step_end"),
380
+ seq: z.number(),
381
+ time: z.number(),
382
+ uuid: z.string(),
383
+ turn_id: z.string(),
384
+ step: z.number(),
385
+ usage: z.object({
386
+ input_tokens: z.number(),
387
+ output_tokens: z.number(),
388
+ cache_read_tokens: z.number().optional(),
389
+ cache_write_tokens: z.number().optional()
390
+ }).optional(),
391
+ finish_reason: z.string().optional()
392
+ });
393
+ const _contentPartBodySchema = z.discriminatedUnion("kind", [z.object({
394
+ kind: z.literal("text"),
395
+ text: z.string()
396
+ }), z.object({
397
+ kind: z.literal("think"),
398
+ think: z.string(),
399
+ encrypted: z.string().optional()
400
+ })]);
401
+ const _rawContentPartRecordSchema = z.object({
402
+ type: z.literal("content_part"),
403
+ seq: z.number(),
404
+ time: z.number(),
405
+ uuid: z.string(),
406
+ turn_id: z.string(),
407
+ step: z.number(),
408
+ step_uuid: z.string(),
409
+ role: z.literal("assistant"),
410
+ part: _contentPartBodySchema
411
+ });
412
+ const _rawToolCallRecordSchema = z.object({
413
+ type: z.literal("tool_call"),
414
+ seq: z.number(),
415
+ time: z.number(),
416
+ uuid: z.string(),
417
+ turn_id: z.string(),
418
+ step: z.number(),
419
+ step_uuid: z.string(),
420
+ data: z.object({
421
+ tool_call_id: z.string(),
422
+ tool_name: z.string(),
423
+ args: z.unknown(),
424
+ activity_description: z.string().optional(),
425
+ user_facing_name: z.string().optional(),
426
+ input_display: z.unknown().optional()
427
+ })
428
+ });
225
429
  /**
226
- * Chains work onto a single promise so all callers are serialised in the
227
- * order `run` was invoked, regardless of when each task resolves.
430
+ * Storage persists the approval display as an opaque blob. Write-trust
431
+ * / read-passthrough: the concrete `ToolInputDisplay` union lives in
432
+ * `display/` and is the responsibility of the host layer to validate
433
+ * at render time.
228
434
  */
229
- var AsyncSerialQueue = class {
230
- tail = Promise.resolve();
231
- run(task) {
232
- const next = this.tail.then(task, task);
233
- this.tail = next.catch(() => {});
234
- return next;
235
- }
435
+ const ApprovalDisplaySchema = z.unknown();
436
+ const ApprovalSourceSchema = z.discriminatedUnion("kind", [
437
+ z.object({
438
+ kind: z.literal("soul"),
439
+ agent_id: z.string()
440
+ }),
441
+ z.object({
442
+ kind: z.literal("subagent"),
443
+ agent_id: z.string(),
444
+ subagent_type: z.string().optional()
445
+ }),
446
+ z.object({
447
+ kind: z.literal("turn"),
448
+ turn_id: z.string()
449
+ }),
450
+ z.object({
451
+ kind: z.literal("session"),
452
+ session_id: z.string()
453
+ }),
454
+ z.object({
455
+ kind: z.literal("mcp"),
456
+ server_id: z.string(),
457
+ reason: z.enum([
458
+ "elicitation",
459
+ "auth",
460
+ "tool_call"
461
+ ])
462
+ })
463
+ ]);
464
+ const skillInvocationTriggerEnum = z.enum([
465
+ "user-slash",
466
+ "claude-proactive",
467
+ "nested-skill"
468
+ ]);
469
+ const _rawSkillInvokedRecordSchema = z.object({
470
+ type: z.literal("skill_invoked"),
471
+ seq: z.number(),
472
+ time: z.number(),
473
+ turn_id: z.string(),
474
+ agent_type: agentTypeEnum.optional(),
475
+ data: z.object({
476
+ skill_name: z.string(),
477
+ execution_mode: z.enum(["inline", "fork"]),
478
+ original_input: z.string(),
479
+ sub_agent_id: z.string().optional(),
480
+ invocation_trigger: skillInvocationTriggerEnum.optional(),
481
+ query_depth: z.number().optional()
482
+ })
483
+ });
484
+ const _rawSkillCompletedRecordSchema = z.object({
485
+ type: z.literal("skill_completed"),
486
+ seq: z.number(),
487
+ time: z.number(),
488
+ turn_id: z.string(),
489
+ agent_type: agentTypeEnum.optional(),
490
+ data: z.object({
491
+ skill_name: z.string(),
492
+ execution_mode: z.enum(["inline", "fork"]),
493
+ success: z.boolean(),
494
+ error: z.string().optional(),
495
+ sub_agent_id: z.string().optional(),
496
+ invocation_trigger: skillInvocationTriggerEnum.optional(),
497
+ query_depth: z.number().optional()
498
+ })
499
+ });
500
+ const _rawApprovalRequestRecordSchema = z.object({
501
+ type: z.literal("approval_request"),
502
+ seq: z.number(),
503
+ time: z.number(),
504
+ turn_id: z.string(),
505
+ step: z.number(),
506
+ data: z.object({
507
+ request_id: z.string(),
508
+ tool_call_id: z.string(),
509
+ tool_name: z.string(),
510
+ action: z.string(),
511
+ display: ApprovalDisplaySchema,
512
+ source: ApprovalSourceSchema
513
+ })
514
+ });
515
+ const _rawApprovalResponseRecordSchema = z.object({
516
+ type: z.literal("approval_response"),
517
+ seq: z.number(),
518
+ time: z.number(),
519
+ turn_id: z.string(),
520
+ step: z.number(),
521
+ data: z.object({
522
+ request_id: z.string(),
523
+ response: z.enum([
524
+ "approved",
525
+ "rejected",
526
+ "cancelled"
527
+ ]),
528
+ feedback: z.string().optional(),
529
+ selected_label: z.string().optional(),
530
+ synthetic: z.boolean().optional()
531
+ })
532
+ });
533
+ const _rawTeamMailRecordSchema = z.object({
534
+ type: z.literal("team_mail"),
535
+ seq: z.number(),
536
+ time: z.number(),
537
+ data: z.object({
538
+ mail_id: z.string(),
539
+ reply_to: z.string().optional(),
540
+ from_agent: z.string(),
541
+ to_agent: z.string(),
542
+ content: z.string(),
543
+ summary: z.string().optional()
544
+ })
545
+ });
546
+ const _rawTokenUsageSchema = z.object({
547
+ input: z.number().int().nonnegative(),
548
+ output: z.number().int().nonnegative(),
549
+ cache_read: z.number().int().nonnegative().optional(),
550
+ cache_write: z.number().int().nonnegative().optional()
551
+ });
552
+ const _rawSubagentSpawnedRecordSchema = z.object({
553
+ type: z.literal("subagent_spawned"),
554
+ seq: z.number(),
555
+ time: z.number(),
556
+ uuid: z.string().optional(),
557
+ data: z.object({
558
+ agent_id: z.string(),
559
+ agent_name: z.string().optional(),
560
+ parent_tool_call_id: z.string(),
561
+ parent_agent_id: z.string().optional(),
562
+ parent_tool_call_uuid: z.string().optional(),
563
+ run_in_background: z.boolean()
564
+ })
565
+ });
566
+ const _rawSubagentCompletedRecordSchema = z.object({
567
+ type: z.literal("subagent_completed"),
568
+ seq: z.number(),
569
+ time: z.number(),
570
+ uuid: z.string().optional(),
571
+ parent_uuid: z.string().optional(),
572
+ data: z.object({
573
+ agent_id: z.string(),
574
+ parent_tool_call_id: z.string(),
575
+ result_summary: z.string(),
576
+ usage: _rawTokenUsageSchema.optional()
577
+ })
578
+ });
579
+ const _rawSubagentFailedRecordSchema = z.object({
580
+ type: z.literal("subagent_failed"),
581
+ seq: z.number(),
582
+ time: z.number(),
583
+ uuid: z.string().optional(),
584
+ parent_uuid: z.string().optional(),
585
+ data: z.object({
586
+ agent_id: z.string(),
587
+ parent_tool_call_id: z.string(),
588
+ error: z.string()
589
+ })
590
+ });
591
+ const _rawOwnershipChangedRecordSchema = z.object({
592
+ type: z.literal("ownership_changed"),
593
+ seq: z.number(),
594
+ time: z.number(),
595
+ old_owner: z.string().nullable(),
596
+ new_owner: z.string()
597
+ });
598
+ const _rawContextEditRecordSchema = z.object({
599
+ type: z.literal("context_edit"),
600
+ seq: z.number(),
601
+ time: z.number(),
602
+ operation: z.enum([
603
+ "edit_message",
604
+ "delete_message",
605
+ "rewind",
606
+ "insert_message",
607
+ "replace_message"
608
+ ]),
609
+ target_seq: z.number().optional(),
610
+ to_turn: z.number().optional(),
611
+ after_seq: z.number().optional(),
612
+ new_content: z.string().optional(),
613
+ new_role: z.enum([
614
+ "user",
615
+ "assistant",
616
+ "system"
617
+ ]).optional(),
618
+ cascade: z.boolean().optional()
619
+ });
620
+ const _rawContextClearedRecordSchema = z.object({
621
+ type: z.literal("context_cleared"),
622
+ seq: z.number(),
623
+ time: z.number()
624
+ });
625
+ const _sessionInitializedCommonShape = {
626
+ type: z.literal("session_initialized"),
627
+ seq: z.number(),
628
+ time: z.number(),
629
+ system_prompt: z.string(),
630
+ active_tools: z.array(z.string()),
631
+ model: z.string().optional(),
632
+ permission_mode: z.enum([
633
+ "default",
634
+ "acceptEdits",
635
+ "bypassPermissions"
636
+ ]).optional(),
637
+ plan_mode: z.boolean().optional(),
638
+ workspace_dir: z.string().optional(),
639
+ thinking_level: z.string().optional()
236
640
  };
237
- /** Real fs-backed JournalWriter. */
238
- var WiredJournalWriter = class {
239
- filePath;
240
- lifecycle;
241
- producer;
242
- protocolVersion;
243
- kimiVersion;
244
- now;
245
- queue = new AsyncSerialQueue();
246
- seq = 0;
247
- metadataWritten = false;
248
- /**
249
- * Tracks whether the parent directory entry for `filePath` has been
250
- * fsynced. Under POSIX semantics, `fh.sync()` flushes file *contents* but
251
- * does not guarantee the directory entry for a freshly created file has
252
- * been durably committed — a crash between the first append and the next
253
- * parent-directory fsync can leave the file's contents on disk with no
254
- * visible dirent. We fsync the parent directory once, after the first
641
+ const _rawSessionInitializedMainSchema = z.object({
642
+ ..._sessionInitializedCommonShape,
643
+ agent_type: z.literal("main"),
644
+ session_id: z.string()
645
+ });
646
+ const _rawSessionInitializedSubSchema = z.object({
647
+ ..._sessionInitializedCommonShape,
648
+ agent_type: z.literal("sub"),
649
+ agent_id: z.string(),
650
+ agent_name: z.string().optional(),
651
+ parent_session_id: z.string(),
652
+ parent_agent_id: z.string().optional(),
653
+ parent_tool_call_id: z.string(),
654
+ run_in_background: z.boolean()
655
+ });
656
+ const _rawSessionInitializedIndependentSchema = z.object({
657
+ ..._sessionInitializedCommonShape,
658
+ agent_type: z.literal("independent"),
659
+ agent_id: z.string(),
660
+ agent_name: z.string().optional()
661
+ });
662
+ const _rawSessionInitializedRecordSchema = z.discriminatedUnion("agent_type", [
663
+ _rawSessionInitializedMainSchema,
664
+ _rawSessionInitializedSubSchema,
665
+ _rawSessionInitializedIndependentSchema
666
+ ]);
667
+ const SessionInitializedRecordSchema = _rawSessionInitializedRecordSchema;
668
+ const WireRecordSchema = z.discriminatedUnion("type", [
669
+ _rawTurnBeginRecordSchema,
670
+ _rawTurnEndRecordSchema,
671
+ _rawUserMessageRecordSchema,
672
+ _rawToolResultRecordSchema,
673
+ _rawCompactionRecordSchema,
674
+ _rawSystemPromptChangedRecordSchema,
675
+ _rawToolsChangedRecordSchema,
676
+ _rawSystemReminderRecordSchema,
677
+ _rawNotificationRecordSchema,
678
+ _rawToolDeniedRecordSchema,
679
+ _rawStepBeginRecordSchema,
680
+ _rawStepEndRecordSchema,
681
+ _rawContentPartRecordSchema,
682
+ _rawToolCallRecordSchema,
683
+ _rawSkillInvokedRecordSchema,
684
+ _rawSkillCompletedRecordSchema,
685
+ _rawApprovalRequestRecordSchema,
686
+ _rawApprovalResponseRecordSchema,
687
+ _rawTeamMailRecordSchema,
688
+ _rawSubagentSpawnedRecordSchema,
689
+ _rawSubagentCompletedRecordSchema,
690
+ _rawSubagentFailedRecordSchema,
691
+ _rawOwnershipChangedRecordSchema,
692
+ _rawContextEditRecordSchema,
693
+ _rawContextClearedRecordSchema,
694
+ _rawSessionInitializedRecordSchema
695
+ ]);
696
+ //#endregion
697
+ //#region ../../packages/kimi-core/src/storage/journal/reader.ts
698
+ async function defaultReadLines(path) {
699
+ const parts = (await readFile(path, "utf8")).split("\n");
700
+ if (parts.length > 0 && parts.at(-1) === "") parts.pop();
701
+ return parts;
702
+ }
703
+ /**
704
+ * Replay a wire.jsonl into an ordered list of valid WireRecords plus a
705
+ * health signal. Does not construct a ContextState — that concern lives in
706
+ * WiredContextState which drives its internal mirror from these records.
707
+ *
708
+ * Error policy (§4.1.1 / §8.4):
709
+ * - unknown record type at any line → skip + warn
710
+ * - JSON.parse failure on the LAST body line → skip + warn (tail truncation)
711
+ * - JSON.parse failure on any earlier line → health = 'broken'
712
+ * - major version higher than supportedMajor → throw IncompatibleVersionError
713
+ */
714
+ async function replayWire(path, options) {
715
+ const lines = await (options.readLines ?? defaultReadLines)(path);
716
+ if (lines.length === 0) throw new WireJournalCorruptError(`wire.jsonl is empty at ${path}`);
717
+ const firstLine = lines[0];
718
+ if (firstLine === void 0) throw new WireJournalCorruptError(`wire.jsonl is empty at ${path}`);
719
+ let meta;
720
+ try {
721
+ meta = WireFileMetadataSchema.parse(JSON.parse(firstLine));
722
+ } catch (error) {
723
+ throw new WireJournalCorruptError(`wire.jsonl metadata header (line 1) is invalid: ${String(error)}`);
724
+ }
725
+ const majorStr = meta.protocol_version.split(".")[0] ?? "0";
726
+ const major = Number.parseInt(majorStr, 10);
727
+ if (!Number.isFinite(major)) throw new IncompatibleVersionError(`wire.jsonl metadata.protocol_version "${meta.protocol_version}" is not a valid version string`);
728
+ if (major > options.supportedMajor) throw new IncompatibleVersionError(`wire.jsonl version ${meta.protocol_version} is not supported (max major: ${options.supportedMajor}). Please upgrade Kimi CLI.`);
729
+ if (meta.producer === void 0) throw new UnsupportedProducerError("legacy", "metadata-missing-producer");
730
+ if (meta.producer.kind !== "typescript") throw new UnsupportedProducerError(meta.producer.kind === "python" ? "python" : "unknown", "cross-producer-not-supported");
731
+ const producer = meta.producer;
732
+ const initLine = lines[1];
733
+ if (initLine === void 0) throw new MalformedWireError("session-initialized-missing", `wire.jsonl has no line 2 (session_initialized) at ${path}`);
734
+ let initRaw;
735
+ try {
736
+ initRaw = JSON.parse(initLine);
737
+ } catch (error) {
738
+ throw new MalformedWireError("session-initialized-missing", `wire.jsonl line 2 failed JSON.parse: ${String(error)}`);
739
+ }
740
+ const initTypeField = initRaw?.type;
741
+ if (initTypeField !== "session_initialized") throw new MalformedWireError("session-initialized-missing", `wire.jsonl line 2 has type="${String(initTypeField)}"; expected "session_initialized"`);
742
+ const initParsed = SessionInitializedRecordSchema.safeParse(initRaw);
743
+ if (!initParsed.success) throw new MalformedWireError("session-initialized-missing", `wire.jsonl line 2 failed zod parse: ${initParsed.error.message}`);
744
+ const sessionInitialized = initParsed.data;
745
+ const records = [];
746
+ const warnings = [];
747
+ const bodyLines = lines.slice(2);
748
+ for (const [i, line] of bodyLines.entries()) {
749
+ const physicalLineNo = i + 3;
750
+ const isLastLine = i === bodyLines.length - 1;
751
+ const snippet = line.slice(0, 100);
752
+ let raw;
753
+ try {
754
+ raw = JSON.parse(line);
755
+ } catch (error) {
756
+ if (isLastLine) {
757
+ warnings.push(`Tail line truncated at line ${physicalLineNo}, skipping: ${snippet}`);
758
+ continue;
759
+ }
760
+ const reason = `wire.jsonl mid-file corruption at line ${physicalLineNo}: ${String(error)}`;
761
+ return {
762
+ records,
763
+ protocolVersion: meta.protocol_version,
764
+ health: "broken",
765
+ brokenReason: reason,
766
+ warnings,
767
+ producer,
768
+ sessionInitialized
769
+ };
770
+ }
771
+ const typeField = raw?.type;
772
+ if (typeField === "session_initialized") throw new MalformedWireError("session-initialized-position-wrong", `wire.jsonl has a session_initialized record at line ${physicalLineNo}; only line 2 is permitted`);
773
+ if (typeof typeField !== "string" || !KNOWN_RECORD_TYPES.has(typeField)) {
774
+ if (typeof typeField === "string" && LEGACY_RECORD_TYPES.has(typeField)) warnings.push(`Skipping legacy record type "${typeField}" at line ${physicalLineNo} (migrated to state.json)`);
775
+ else warnings.push(`Skipping unrecognized record type "${String(typeField)}" at line ${physicalLineNo}: ${snippet}`);
776
+ continue;
777
+ }
778
+ const parsed = WireRecordSchema.safeParse(raw);
779
+ if (!parsed.success) {
780
+ if (isLastLine) {
781
+ warnings.push(`Tail record failed schema validation at line ${physicalLineNo}, skipping: ${snippet}`);
782
+ continue;
783
+ }
784
+ const reason = `wire.jsonl schema violation at line ${physicalLineNo}: ${parsed.error.message}`;
785
+ return {
786
+ records,
787
+ protocolVersion: meta.protocol_version,
788
+ health: "broken",
789
+ brokenReason: reason,
790
+ warnings,
791
+ producer,
792
+ sessionInitialized
793
+ };
794
+ }
795
+ records.push(parsed.data);
796
+ }
797
+ return {
798
+ records,
799
+ protocolVersion: meta.protocol_version,
800
+ health: "ok",
801
+ warnings,
802
+ producer,
803
+ sessionInitialized
804
+ };
805
+ }
806
+ const KNOWN_RECORD_TYPES = new Set([
807
+ "turn_begin",
808
+ "turn_end",
809
+ "user_message",
810
+ "tool_result",
811
+ "compaction",
812
+ "system_prompt_changed",
813
+ "tools_changed",
814
+ "system_reminder",
815
+ "notification",
816
+ "tool_denied",
817
+ "step_begin",
818
+ "step_end",
819
+ "content_part",
820
+ "tool_call",
821
+ "skill_invoked",
822
+ "skill_completed",
823
+ "approval_request",
824
+ "approval_response",
825
+ "team_mail",
826
+ "subagent_spawned",
827
+ "subagent_completed",
828
+ "subagent_failed",
829
+ "ownership_changed",
830
+ "context_edit",
831
+ "context_cleared"
832
+ ]);
833
+ /**
834
+ * Record types that earlier schema versions persisted to `wire.jsonl`
835
+ * but have since been migrated to `state.json` as the truth source.
836
+ *
837
+ * Sessions written by older binaries still carry rows of these types.
838
+ * Keeping them in a dedicated set lets the replay loop emit an
839
+ * informational "legacy" warning instead of the forward-compat
840
+ * "unrecognized" warning, so it is clear these rows are expected to be
841
+ * dropped rather than being an unknown future-version type.
842
+ */
843
+ const LEGACY_RECORD_TYPES = new Set([
844
+ "permission_mode_changed",
845
+ "model_changed",
846
+ "thinking_changed",
847
+ "plan_mode_changed",
848
+ "session_meta_changed"
849
+ ]);
850
+ //#endregion
851
+ //#region ../../packages/kimi-core/src/storage/journal/rotation.ts
852
+ /**
853
+ * Storage-layer compaction utilities — file rotation and cross-file replay
854
+ * (§4.7 / §4.1.1).
855
+ *
856
+ * File rotation is the physical counterpart to TurnManager's
857
+ * `executeCompaction` (Phase 2; previously Soul's `runCompaction`):
858
+ * 1. Rename `wire.jsonl` → `wire.N.jsonl` (frozen archive)
859
+ * 2. Create new `wire.jsonl` with metadata header + CompactionRecord
860
+ *
861
+ * Cross-file replay reads all wire files in a session directory
862
+ * (wire.N.jsonl → ... → wire.1.jsonl → wire.jsonl) and produces a
863
+ * unified record stream.
864
+ *
865
+ * Crash recovery detects "wire.jsonl missing but wire.N.jsonl exists"
866
+ * and rolls back the lowest-numbered archive to `wire.jsonl`.
867
+ */
868
+ const ARCHIVE_PATTERN = /^wire\.(\d+)\.jsonl$/;
869
+ function extractArchiveNumber(name) {
870
+ const captured = ARCHIVE_PATTERN.exec(name)?.[1];
871
+ return captured !== void 0 ? Number.parseInt(captured, 10) : 0;
872
+ }
873
+ /**
874
+ * Compute the next archive filename for a rotation:
875
+ * - No archives exist → `wire.1.jsonl`
876
+ * - Highest existing is `wire.3.jsonl` → `wire.4.jsonl`
877
+ */
878
+ function nextArchiveName(sessionDir, existingArchives) {
879
+ let maxN = 0;
880
+ for (const archive of existingArchives) {
881
+ const n = extractArchiveNumber(archive);
882
+ if (n > maxN) maxN = n;
883
+ }
884
+ return join(sessionDir, `wire.${maxN + 1}.jsonl`);
885
+ }
886
+ async function rotateJournal(sessionDir, options) {
887
+ const { producer, protocolVersion, ...deps } = options;
888
+ const version = protocolVersion ?? "2.1";
889
+ const currentPath = join(sessionDir, "wire.jsonl");
890
+ const archivePath = nextArchiveName(sessionDir, (await readdir(sessionDir)).filter((e) => ARCHIVE_PATTERN.test(e)));
891
+ const renameFn = deps.renameFn ?? rename;
892
+ const retryDelayMs = deps.retryDelayMs ?? 500;
893
+ try {
894
+ await renameFn(currentPath, archivePath);
895
+ } catch (error) {
896
+ const code = error.code;
897
+ if (process.platform === "win32" && code === "EPERM") {
898
+ await new Promise((resolve, reject) => {
899
+ const timer = setTimeout(resolve, retryDelayMs);
900
+ const abortSignal = deps.signal;
901
+ if (abortSignal !== void 0) {
902
+ const onAbort = () => {
903
+ clearTimeout(timer);
904
+ reject(abortSignal.reason ?? /* @__PURE__ */ new Error("aborted"));
905
+ };
906
+ if (abortSignal.aborted) onAbort();
907
+ else abortSignal.addEventListener("abort", onAbort, { once: true });
908
+ }
909
+ });
910
+ await renameFn(currentPath, archivePath);
911
+ } else throw error;
912
+ }
913
+ await writeFileAtomicDurable(currentPath, JSON.stringify({
914
+ type: "metadata",
915
+ protocol_version: version,
916
+ created_at: Date.now(),
917
+ producer,
918
+ kimi_version: producer.version
919
+ }) + "\n");
920
+ await syncDir(sessionDir);
921
+ return {
922
+ archivePath,
923
+ newCurrentPath: currentPath
924
+ };
925
+ }
926
+ /**
927
+ * Find the highest-numbered archive in a list of archive filenames.
928
+ * Returns the filename and its number, or `undefined` if the list is empty.
929
+ */
930
+ function findHighestArchive(archives) {
931
+ let highestN = 0;
932
+ let result;
933
+ for (const name of archives) {
934
+ const n = extractArchiveNumber(name);
935
+ if (n > highestN) {
936
+ highestN = n;
937
+ result = name;
938
+ }
939
+ }
940
+ return result !== void 0 ? {
941
+ name: result,
942
+ n: highestN
943
+ } : void 0;
944
+ }
945
+ /**
946
+ * Detect and recover from a crash between file rotation steps.
947
+ *
948
+ * Three scenarios are handled:
949
+ *
950
+ * 1. **wire.jsonl missing**: `wire.jsonl` was renamed to `wire.N.jsonl`
951
+ * but the process crashed before the new `wire.jsonl` was created.
952
+ * Recovery: roll back the highest-numbered archive to `wire.jsonl`.
953
+ *
954
+ * 2. **wire.jsonl is metadata-only** (Slice 3.3 / M04): the rename
955
+ * succeeded AND the new `wire.jsonl` was created with a metadata
956
+ * header, but the process crashed before `appendBoundary` ran. The
957
+ * new file has only the metadata line — no session_initialized, no
958
+ * compaction record. Recovery: remove the half-complete file and
959
+ * roll back the highest archive.
960
+ *
961
+ * 3. **wire.jsonl has metadata + session_initialized only** (Phase 23 /
962
+ * T7.7): `appendBoundary` copied `session_initialized` through as the
963
+ * second line, but the process crashed before the compaction record
964
+ * landed. Without the compaction record, the archived conversation is
965
+ * orphaned — replay of the new wire would show an empty post-boundary
966
+ * window and the archive would never be re-read. Recovery: same as
967
+ * case 2 — remove the half-complete file and restore the archive.
968
+ *
969
+ * Returns `true` if recovery was performed, `false` if no recovery was needed.
970
+ */
971
+ async function recoverRotation(sessionDir) {
972
+ const entries = await readdir(sessionDir);
973
+ const archives = entries.filter((e) => ARCHIVE_PATTERN.test(e));
974
+ if (!entries.includes("wire.jsonl")) {
975
+ const highest = findHighestArchive(archives);
976
+ if (highest === void 0) return false;
977
+ await rename(join(sessionDir, highest.name), join(sessionDir, "wire.jsonl"));
978
+ return true;
979
+ }
980
+ if (archives.length > 0) {
981
+ const currentPath = join(sessionDir, "wire.jsonl");
982
+ const lines = (await readFile(currentPath, "utf8")).trim().split("\n").filter((l) => l.length > 0);
983
+ if (lines.length === 1 || lines.length === 2) {
984
+ const parsedLines = lines.map((l) => {
985
+ try {
986
+ return JSON.parse(l);
987
+ } catch {
988
+ return null;
989
+ }
990
+ });
991
+ const isMetadata = parsedLines[0]?.["type"] === "metadata";
992
+ if (lines.length === 1 ? isMetadata : isMetadata && parsedLines[1]?.["type"] === "session_initialized") {
993
+ const highest = findHighestArchive(archives);
994
+ if (highest !== void 0) {
995
+ await unlink(currentPath);
996
+ await rename(join(sessionDir, highest.name), join(sessionDir, "wire.jsonl"));
997
+ return true;
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+ return false;
1003
+ }
1004
+ //#endregion
1005
+ //#region ../../packages/kimi-core/src/storage/journal/writer.ts
1006
+ /**
1007
+ * Record types the compaction path itself is allowed to write while the
1008
+ * lifecycle gate is in `compacting` state (§5.1.7 / v2 L2663-L2690).
1009
+ *
1010
+ * Background (Slice 6 audit M02): every non-compaction write is drained
1011
+ * during `compacting` so the in-flight compaction is not racing against
1012
+ * new Soul output. But compaction still needs to persist its own
1013
+ * `CompactionRecord` via `context.resetToSummary()`, which goes through
1014
+ * the same `JournalWriter.append()` entry point. Blanket-rejecting all
1015
+ * writes in `compacting` would deadlock compaction against itself, so
1016
+ * this whitelist names the record types that are considered
1017
+ * "compaction's own writes" and allowed through the gate.
1018
+ *
1019
+ * Keep this list narrow: only record types that are *only* emitted from
1020
+ * inside `TurnManager.executeCompaction` (Phase 2; previously
1021
+ * `runCompaction`) belong here. Any future compaction-path record
1022
+ * must be added explicitly.
1023
+ */
1024
+ const COMPACTION_OWN_WRITE_TYPES = new Set(["compaction", "session_initialized"]);
1025
+ /**
1026
+ * Record types that must be durable on disk before `append()` resolves
1027
+ * (§4.5.4 — force-flush kinds). Recovery-critical boundary markers live
1028
+ * here: their absence at replay time breaks the contracts §9.x relies on.
1029
+ *
1030
+ * Declared as `ReadonlySet<string>` so we can include future union-member
1031
+ * strings (e.g. `subagent_completed` / `subagent_failed` from the
1032
+ * subagent slice) without forcing an out-of-slice edit to `WireRecord`.
1033
+ */
1034
+ const FORCE_FLUSH_KINDS = new Set([
1035
+ "approval_response",
1036
+ "turn_end",
1037
+ "subagent_completed",
1038
+ "subagent_failed",
1039
+ "session_initialized",
1040
+ "context_cleared",
1041
+ "tools_changed"
1042
+ ]);
1043
+ const DEFAULT_PROTOCOL_VERSION = "2.1";
1044
+ /**
1045
+ * Chains work onto a single promise so all callers are serialised in the
1046
+ * order `run` was invoked, regardless of when each task resolves.
1047
+ */
1048
+ var AsyncSerialQueue = class {
1049
+ tail = Promise.resolve();
1050
+ run(task) {
1051
+ const next = this.tail.then(task, task);
1052
+ this.tail = next.catch(() => {});
1053
+ return next;
1054
+ }
1055
+ };
1056
+ /** Real fs-backed JournalWriter. */
1057
+ var WiredJournalWriter = class {
1058
+ filePath;
1059
+ lifecycle;
1060
+ producer;
1061
+ protocolVersion;
1062
+ kimiVersion;
1063
+ now;
1064
+ queue = new AsyncSerialQueue();
1065
+ seq = 0;
1066
+ metadataWritten = false;
1067
+ /**
1068
+ * Tracks whether the parent directory entry for `filePath` has been
1069
+ * fsynced. Under POSIX semantics, `fh.sync()` flushes file *contents* but
1070
+ * does not guarantee the directory entry for a freshly created file has
1071
+ * been durably committed — a crash between the first append and the next
1072
+ * parent-directory fsync can leave the file's contents on disk with no
1073
+ * visible dirent. We fsync the parent directory once, after the first
255
1074
  * successful write, and never again for the lifetime of this writer.
256
1075
  */
257
1076
  directorySynced = false;
@@ -296,6 +1115,32 @@ var WiredJournalWriter = class {
296
1115
  get pendingRecords() {
297
1116
  return this.pending;
298
1117
  }
1118
+ get sessionDir() {
1119
+ return dirname(this.filePath);
1120
+ }
1121
+ get rotateCapability() {
1122
+ return {
1123
+ sessionDir: this.sessionDir,
1124
+ rotate: async () => {
1125
+ await this.flush();
1126
+ const result = await rotateJournal(this.sessionDir, { producer: this.producer });
1127
+ this.resetForRotation();
1128
+ return result;
1129
+ },
1130
+ readSessionInitialized: async () => {
1131
+ const lines = (await readFile(this.filePath, "utf-8")).split("\n").filter((l) => l.length > 0);
1132
+ if (lines.length < 2) throw new Error(`readSessionInitialized: wire.jsonl at ${this.filePath} has fewer than 2 lines`);
1133
+ const parsed = JSON.parse(lines[1]);
1134
+ const result = SessionInitializedRecordSchema.safeParse(parsed);
1135
+ if (!result.success) throw new Error(`readSessionInitialized: wire.jsonl line 2 at ${this.filePath} is not a valid session_initialized record: ${result.error.message}`);
1136
+ return result.data;
1137
+ },
1138
+ appendBoundary: async (sessionInitialized) => {
1139
+ const { seq: _seq, time: _time, ...input } = sessionInitialized;
1140
+ await this.append(input);
1141
+ }
1142
+ };
1143
+ }
299
1144
  /**
300
1145
  * Reset writer state after a compaction rotation (Slice 3.3 / M04).
301
1146
  *
@@ -306,9 +1151,6 @@ var WiredJournalWriter = class {
306
1151
  * mark it as written. The new file's directory entry was durably
307
1152
  * committed by `rotateJournal`'s `syncDir`, so `directorySynced` is
308
1153
  * also set.
309
- *
310
- * Only the compaction path (via `WiredJournalCapability`) should call
311
- * this method — normal appends must never touch the seq counter.
312
1154
  */
313
1155
  resetForRotation() {
314
1156
  this.seq = 0;
@@ -453,7 +1295,7 @@ var WiredJournalWriter = class {
453
1295
  }
454
1296
  }
455
1297
  async ensureDir() {
456
- await mkdir(dirname(this.filePath), { recursive: true });
1298
+ await mkdir(this.sessionDir, { recursive: true });
457
1299
  }
458
1300
  async writeAndSync(line) {
459
1301
  const fh = await open(this.filePath, "a");
@@ -487,7 +1329,7 @@ var WiredJournalWriter = class {
487
1329
  }
488
1330
  /** Async parent-directory fsync. Tests may spy on this method by name. */
489
1331
  async syncParentDir() {
490
- await syncDir(dirname(this.filePath));
1332
+ await syncDir(this.sessionDir);
491
1333
  }
492
1334
  };
493
1335
  /** No-op writer used by InMemory state implementations. */
@@ -546,1492 +1388,673 @@ var ContextStateMemory = class {
546
1388
  get tokenCountWithPending() {
547
1389
  return this._tokenCountWithPending;
548
1390
  }
549
- get thinkingLevel() {
550
- return this._thinkingLevel;
551
- }
552
- get beforeStep() {
553
- return this._beforeStep;
554
- }
555
- setBeforeStepHook(fn) {
556
- this._beforeStep = fn;
557
- }
558
- getHistory() {
559
- return this.history;
560
- }
561
- drainSteerMessages() {
562
- const drained = this.steerBuffer;
563
- this.steerBuffer = [];
564
- return drained;
565
- }
566
- pushSteer(input) {
567
- this.steerBuffer.push({ ...input });
568
- }
569
- appendUserContent(content) {
570
- this.history.push({
571
- role: "user",
572
- content,
573
- toolCalls: []
574
- });
575
- }
576
- appendToolResult(toolCallId, content) {
577
- this.history.push({
578
- role: "tool",
579
- content,
580
- toolCalls: [],
581
- toolCallId
582
- });
583
- }
584
- openStep(uuid) {
585
- const message = {
586
- role: "assistant",
587
- content: [],
588
- toolCalls: []
589
- };
590
- this.history.push(message);
591
- this.openSteps.set(uuid, message);
592
- }
593
- closeStep(uuid, usage) {
594
- this.openSteps.delete(uuid);
595
- if (usage !== void 0) this._tokenCountWithPending = usage.input_tokens + usage.output_tokens;
596
- }
597
- getOpenStep(uuid) {
598
- return this.openSteps.get(uuid);
599
- }
600
- appendOpenStepContent(stepUuid, part) {
601
- this.openSteps.get(stepUuid)?.content.push(part);
602
- }
603
- appendOpenStepToolCall(stepUuid, toolCall) {
604
- this.openSteps.get(stepUuid)?.toolCalls.push(toolCall);
605
- }
606
- clearConversation() {
607
- this.history = [];
608
- this.openSteps.clear();
609
- this._tokenCountWithPending = 0;
610
- }
611
- setSystemPrompt(systemPrompt) {
612
- this._systemPrompt = systemPrompt;
613
- }
614
- setModel(model) {
615
- this._model = model;
616
- }
617
- setThinkingLevel(level) {
618
- this._thinkingLevel = level;
619
- }
620
- applyToolsChanged(operation, tools) {
621
- if (operation === "set_active") {
622
- this._activeTools = new Set(tools);
623
- return;
624
- }
625
- if (operation === "register") {
626
- for (const tool of tools) this._activeTools.add(tool);
627
- return;
628
- }
629
- for (const tool of tools) this._activeTools.delete(tool);
630
- }
631
- resetToSummary(summary) {
632
- this.history = [{
633
- role: "assistant",
634
- content: [{
635
- type: "text",
636
- text: summary.summary
637
- }],
638
- toolCalls: []
639
- }];
640
- this.openSteps.clear();
641
- this._tokenCountWithPending = summary.postCompactTokens;
642
- }
643
- };
644
- //#endregion
645
- //#region ../../packages/kimi-core/src/storage/state/message-mirror.ts
646
- function userInputToContentParts(input) {
647
- if (input.parts !== void 0 && input.parts.some((part) => part.type !== "text") && input.parts !== void 0) return input.parts.map(userInputPartToContentPart);
648
- return [{
649
- type: "text",
650
- text: input.parts !== void 0 ? input.parts.filter((part) => part.type === "text").map((part) => part.text).join("") : input.text
651
- }];
652
- }
653
- function toolResultOutputToContentParts(output) {
654
- if (isContentPartArray$1(output)) return output.map((part) => cloneContentPart$1(part));
655
- return [{
656
- type: "text",
657
- text: typeof output === "string" ? output : JSON.stringify(output)
658
- }];
659
- }
660
- function atomicPartToContentPart(part) {
661
- if (part.kind === "text") return {
662
- type: "text",
663
- text: part.text
664
- };
665
- const thinkPart = {
666
- type: "think",
667
- think: part.think
668
- };
669
- if (part.encrypted !== void 0) thinkPart.encrypted = part.encrypted;
670
- return thinkPart;
671
- }
672
- function sanitizeToolCallData(data) {
673
- return {
674
- tool_call_id: data.tool_call_id,
675
- tool_name: data.tool_name,
676
- args: data.args,
677
- ...data.activity_description !== void 0 ? { activity_description: data.activity_description } : {},
678
- user_facing_name: data.user_facing_name,
679
- input_display: data.input_display
680
- };
681
- }
682
- function toolCallDataToKosongToolCall(data) {
683
- return {
684
- type: "function",
685
- id: data.tool_call_id,
686
- function: {
687
- name: data.tool_name,
688
- arguments: data.args === void 0 ? null : JSON.stringify(data.args)
689
- }
690
- };
691
- }
692
- function userInputPartToContentPart(part) {
693
- if (part.type === "text") return {
694
- type: "text",
695
- text: part.text
696
- };
697
- if (part.type === "image_url") return {
698
- type: "image_url",
699
- imageUrl: part.image_url
700
- };
701
- return {
702
- type: "video_url",
703
- videoUrl: part.video_url
704
- };
705
- }
706
- function isContentPart$2(value) {
707
- if (typeof value !== "object" || value === null) return false;
708
- const part = value;
709
- return part.type === "text" || part.type === "think" || part.type === "image_url" || part.type === "audio_url" || part.type === "video_url";
710
- }
711
- function isContentPartArray$1(value) {
712
- return Array.isArray(value) && value.every((part) => isContentPart$2(part));
713
- }
714
- function cloneContentPart$1(part) {
715
- return { ...part };
716
- }
717
- //#endregion
718
- //#region ../../packages/kimi-core/src/storage/state/notification-xml.ts
719
- /**
720
- * Notification XML rendering — shared between ContextState and
721
- * ConversationProjector so both produce byte-identical chat-history
722
- * injection text.
723
- *
724
- * Output shape:
725
- * <notification id="..." category="..." type="..." source_kind="..." source_id="...">
726
- * Title: ...
727
- * Severity: ...
728
- * <body>
729
- * <task-notification> (only when source_kind === 'background_task' and tail_output is non-empty)
730
- * <truncated tail>
731
- * </task-notification>
732
- * </notification>
733
- *
734
- * The opening-tag names (`<notification ` / `<task-notification>`) are
735
- * load-bearing for the projector's `mergeAdjacentUserMessages` detector
736
- * — rename requires updating the detector too.
737
- */
738
- function renderNotificationXml(data) {
739
- const id = stringAttr(data["id"], "unknown");
740
- const category = stringAttr(data["category"], "unknown");
741
- const type = stringAttr(data["type"], "unknown");
742
- const sourceKind = stringAttr(data["source_kind"], "unknown");
743
- const sourceId = stringAttr(data["source_id"], "unknown");
744
- const title = typeof data["title"] === "string" ? data["title"] : "";
745
- const severity = typeof data["severity"] === "string" ? data["severity"] : "";
746
- const body = typeof data["body"] === "string" ? data["body"] : "";
747
- const lines = [`<notification id="${id}" category="${category}" type="${type}" source_kind="${sourceKind}" source_id="${sourceId}">`];
748
- if (title.length > 0) lines.push(`Title: ${title}`);
749
- if (severity.length > 0) lines.push(`Severity: ${severity}`);
750
- if (body.length > 0) lines.push(body);
751
- if (data["source_kind"] === "background_task") {
752
- const tailRaw = typeof data["tail_output"] === "string" ? data["tail_output"] : "";
753
- if (tailRaw.length > 0) {
754
- const truncated = truncateTailOutput(tailRaw, 20, 3e3);
755
- lines.push("<task-notification>");
756
- lines.push(truncated);
757
- lines.push("</task-notification>");
758
- }
759
- }
760
- lines.push("</notification>");
761
- return lines.join("\n");
762
- }
763
- /**
764
- * Truncate tail output to at most `maxLines` lines and `maxChars`
765
- * characters. Takes the *last* N lines, then trims from the front if
766
- * the character budget is exceeded.
767
- */
768
- function truncateTailOutput(raw, maxLines, maxChars) {
769
- const allLines = raw.split("\n");
770
- let result = (allLines.length > maxLines ? allLines.slice(-maxLines) : allLines).join("\n");
771
- if (result.length > maxChars) result = result.slice(-maxChars);
772
- return result;
773
- }
774
- function stringAttr(value, fallback) {
775
- if (typeof value !== "string" || value.length === 0) return fallback;
776
- return value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;");
777
- }
778
- //#endregion
779
- //#region ../../packages/kimi-core/src/storage/state/projector.ts
780
- /**
781
- * Pure read-side projector for provider-visible messages. It passes through
782
- * persisted history, merges adjacent user messages, filters incomplete
783
- * assistant placeholders, and injects ephemeral annotations without writing
784
- * anything back to the journal.
785
- *
786
- * The system prompt is not injected here; it is forwarded separately through
787
- * provider chat parameters to avoid duplicate system messages.
788
- */
789
- var DefaultConversationProjector = class {
790
- project(snapshot, ephemeralInjectionsOrOptions, options) {
791
- const ephemeralInjections = Array.isArray(ephemeralInjectionsOrOptions) ? ephemeralInjectionsOrOptions : [];
792
- const merged = mergeAdjacentUserMessages(snapshot.history.filter((m) => m.partial !== true && !(m.role === "assistant" && m.content.length === 0 && m.toolCalls.length === 0)));
793
- return [...ephemeralInjections.length === 0 ? [] : ephemeralInjections.map((injection) => renderInjection(injection)), ...merged];
794
- }
795
- };
796
- /**
797
- * Render an EphemeralInjection into a synthetic user message. System
798
- * reminders and pending notifications use XML wrappers so the model can
799
- * distinguish host annotations from genuine user text. `memory_recall`
800
- * stays as free text.
801
- *
802
- * The merge-guard logic downstream (`mergeAdjacentUserMessages`) uses
803
- * the `<notification ` / `<system-reminder>` opening tag to detect
804
- * these messages, so the exact tag names are load-bearing for
805
- * projector correctness — do not rename without also updating
806
- * `isInjectionUserMessage` below.
807
- */
808
- function renderInjection(injection) {
809
- return {
810
- role: "user",
811
- content: [{
812
- type: "text",
813
- text: renderInjectionText(injection)
814
- }],
815
- toolCalls: []
816
- };
817
- }
818
- function renderInjectionText(injection) {
819
- const { kind, content } = injection;
820
- if (kind === "pending_notification") {
821
- if (typeof content === "string") return `<notification>\n${content}\n</notification>`;
822
- return renderNotificationXml(content);
823
- }
824
- if (kind === "system_reminder") return `<system-reminder>\n${typeof content === "string" ? content : JSON.stringify(content)}\n</system-reminder>`;
825
- return typeof content === "string" ? content : JSON.stringify(content);
826
- }
827
- /**
828
- * Detect whether a user message was produced by the ephemeral injection
829
- * pipeline (system_reminder or notification XML tag). Such messages
830
- * must never be merged with an adjacent real user turn — doing so would
831
- * smear the injection's XML wrapper into the user's actual prompt and
832
- * confuse the LLM about where the system annotation ends.
833
- *
834
- */
835
- function isInjectionUserMessage(message) {
836
- if (message.role !== "user") return false;
837
- const trimmed = extractTextOnly(message).trimStart();
838
- if (trimmed.startsWith("<notification ")) return true;
839
- if (trimmed.startsWith("<system-reminder>")) return true;
840
- return false;
841
- }
842
- function mergeAdjacentUserMessages(history) {
843
- const out = [];
844
- for (const message of history) {
845
- const previous = out.at(-1);
846
- if (message.role === "user" && previous !== void 0 && previous.role === "user" && !isInjectionUserMessage(message) && !isInjectionUserMessage(previous)) {
847
- out[out.length - 1] = mergeTwoUserMessages(previous, message);
848
- continue;
849
- }
850
- out.push(cloneMessage(message));
851
- }
852
- return out;
853
- }
854
- function mergeTwoUserMessages(a, b) {
855
- const aText = extractTextOnly(a);
856
- const bText = extractTextOnly(b);
857
- const nonTextParts = [...a.content.filter((p) => p.type !== "text"), ...b.content.filter((p) => p.type !== "text")];
858
- return {
859
- role: "user",
860
- content: [{
861
- type: "text",
862
- text: `${aText}\n\n${bText}`
863
- }, ...nonTextParts],
864
- toolCalls: []
865
- };
866
- }
867
- function extractTextOnly(message) {
868
- return message.content.filter((p) => p.type === "text").map((p) => p.text).join("");
869
- }
870
- function cloneMessage(message) {
871
- return {
872
- role: message.role,
873
- name: message.name,
874
- content: message.content.map((p) => ({ ...p })),
875
- toolCalls: message.toolCalls.map((tc) => ({ ...tc })),
876
- toolCallId: message.toolCallId,
877
- partial: message.partial
878
- };
879
- }
880
- //#endregion
881
- //#region ../../packages/kimi-core/src/storage/state/record-builders.ts
882
- function buildUserMessageRecord(turnId, input) {
883
- return {
884
- type: "user_message",
885
- turn_id: turnId,
886
- content: input.parts !== void 0 ? input.parts : input.text
887
- };
888
- }
889
- function buildToolResultRecord(turnId, parentUuid, toolCallId, normalisedOutput, result) {
890
- return {
891
- type: "tool_result",
892
- turn_id: turnId,
893
- tool_call_id: toolCallId,
894
- output: normalisedOutput,
895
- parent_uuid: parentUuid,
896
- is_error: result.isError,
897
- synthetic: result.synthetic
898
- };
899
- }
900
- function buildStepBeginRecord(input) {
901
- return {
902
- type: "step_begin",
903
- uuid: input.uuid,
904
- turn_id: input.turnId,
905
- step: input.step
906
- };
907
- }
908
- function buildStepEndRecord(input) {
909
- return {
910
- type: "step_end",
911
- uuid: input.uuid,
912
- turn_id: input.turnId,
913
- step: input.step,
914
- usage: input.usage,
915
- finish_reason: input.finishReason
916
- };
917
- }
918
- function buildContentPartRecord(input) {
919
- return {
920
- type: "content_part",
921
- uuid: input.uuid,
922
- turn_id: input.turnId,
923
- step: input.step,
924
- step_uuid: input.stepUuid,
925
- role: "assistant",
926
- part: input.part.kind === "text" ? {
927
- kind: "text",
928
- text: input.part.text
929
- } : input.part.encrypted !== void 0 ? {
930
- kind: "think",
931
- think: input.part.think,
932
- encrypted: input.part.encrypted
933
- } : {
934
- kind: "think",
935
- think: input.part.think
936
- }
937
- };
938
- }
939
- function buildToolCallRecord(input, data) {
940
- return {
941
- type: "tool_call",
942
- uuid: input.uuid,
943
- turn_id: input.turnId,
944
- step: input.step,
945
- step_uuid: input.stepUuid,
946
- data
947
- };
948
- }
949
- function buildNotificationRecord(data) {
950
- return {
951
- type: "notification",
952
- data
953
- };
954
- }
955
- function buildSystemReminderRecord(data) {
956
- return {
957
- type: "system_reminder",
958
- content: data.content
959
- };
960
- }
961
- function buildClearRecord() {
962
- return { type: "context_cleared" };
963
- }
964
- function buildConfigChangeRecord(event) {
965
- switch (event.type) {
966
- case "system_prompt_changed": return {
967
- type: "system_prompt_changed",
968
- new_prompt: event.new_prompt
969
- };
970
- case "tools_changed": return {
971
- type: "tools_changed",
972
- operation: event.operation,
973
- tools: event.tools
974
- };
975
- }
976
- }
977
- function buildCompactionRecord(summary) {
978
- return {
979
- type: "compaction",
980
- summary: summary.summary,
981
- compacted_range: {
982
- from_turn: summary.compactedRange.fromTurn,
983
- to_turn: summary.compactedRange.toTurn,
984
- message_count: summary.compactedRange.messageCount
985
- },
986
- pre_compact_tokens: summary.preCompactTokens,
987
- post_compact_tokens: summary.postCompactTokens,
988
- trigger: summary.trigger,
989
- archive_file: summary.archiveFile
990
- };
991
- }
992
- //#endregion
993
- //#region ../../packages/kimi-core/src/storage/state/context-state.ts
994
- /**
995
- * Core ContextState logic. Both `WiredContextState` and
996
- * `InMemoryContextState` are thin wrappers that differ only in the journal
997
- * writer they supply and the constructor shape they expose.
998
- *
999
- * WAL-then-mirror atomicity:
1000
- * 1. build the WireRecord
1001
- * 2. await journalWriter.append(...)
1002
- * 3. on success only, update the in-memory projection
1003
- *
1004
- * If step 2 throws, the in-memory state is unchanged.
1005
- */
1006
- var BaseContextState = class {
1007
- journalWriter;
1008
- projector;
1009
- currentTurnId;
1010
- memory;
1011
- _broken = false;
1012
- _brokenError;
1013
- /**
1014
- * Marks all future write entry points as failed. Read methods remain
1015
- * callable so callers can still inspect the last valid in-memory state.
1016
- */
1017
- markBroken(error) {
1018
- if (this._broken) return;
1019
- this._broken = true;
1020
- this._brokenError = error;
1021
- }
1022
- assertNotBroken() {
1023
- if (this._broken) throw new ContextStateBrokenError("ContextState is broken due to a prior persist error", this._brokenError);
1024
- }
1025
- constructor(opts) {
1026
- this.journalWriter = opts.journalWriter;
1027
- this.projector = opts.projector ?? new DefaultConversationProjector();
1028
- this.currentTurnId = opts.currentTurnId;
1029
- this.memory = new ContextStateMemory(opts);
1030
- }
1031
- get model() {
1032
- return this.memory.model;
1033
- }
1034
- get systemPrompt() {
1035
- return this.memory.systemPrompt;
1036
- }
1037
- get activeTools() {
1038
- return this.memory.activeTools;
1039
- }
1040
- get tokenCountWithPending() {
1041
- return this.memory.tokenCountWithPending;
1042
- }
1043
- get thinkingLevel() {
1044
- return this.memory.thinkingLevel;
1045
- }
1046
- get beforeStep() {
1047
- return this.memory.beforeStep;
1048
- }
1049
- buildMessages() {
1050
- return this.projector.project({
1051
- history: this.memory.getHistory(),
1052
- systemPrompt: this.memory.systemPrompt,
1053
- model: this.memory.model,
1054
- activeTools: this.memory.activeTools
1055
- });
1056
- }
1057
- async applySteerMessages() {
1058
- const steers = this.memory.drainSteerMessages();
1059
- if (steers.length === 0) return false;
1060
- this.assertNotBroken();
1061
- for (const steer of steers) await this.appendUserMessage(steer);
1062
- return true;
1063
- }
1064
- pushSteer(input) {
1065
- this.memory.pushSteer(input);
1066
- }
1067
- setBeforeStepHook(fn) {
1068
- this.memory.setBeforeStepHook(fn);
1069
- }
1070
- getHistory() {
1071
- return this.memory.getHistory();
1072
- }
1073
- async appendUserMessage(input, turnIdOverride) {
1074
- this.assertNotBroken();
1075
- const turnId = turnIdOverride ?? this.currentTurnId();
1076
- await this.journalWriter.append(buildUserMessageRecord(turnId, input));
1077
- this.memory.appendUserContent(userInputToContentParts(input));
1078
- }
1079
- async appendToolResult(parentUuid, toolCallId, result) {
1080
- this.assertNotBroken();
1081
- const normalisedOutput = result.output === void 0 ? null : result.output;
1082
- const turnId = this.currentTurnId();
1083
- await this.journalWriter.append(buildToolResultRecord(turnId, parentUuid, toolCallId, normalisedOutput, result));
1084
- this.memory.appendToolResult(toolCallId, toolResultOutputToContentParts(normalisedOutput));
1085
- }
1086
- async appendStepBegin(input) {
1087
- this.assertNotBroken();
1088
- await this.journalWriter.append(buildStepBeginRecord(input));
1089
- this.memory.openStep(input.uuid);
1090
- }
1091
- async appendStepEnd(input) {
1092
- this.assertNotBroken();
1093
- await this.journalWriter.append(buildStepEndRecord(input));
1094
- this.memory.closeStep(input.uuid, input.usage);
1095
- }
1096
- async appendContentPart(input) {
1097
- this.assertNotBroken();
1098
- if (this.memory.getOpenStep(input.stepUuid) === void 0) throw new Error(`appendContentPart: unknown stepUuid '${input.stepUuid}' (no open step_begin)`);
1099
- await this.journalWriter.append(buildContentPartRecord(input));
1100
- this.memory.appendOpenStepContent(input.stepUuid, atomicPartToContentPart(input.part));
1101
- }
1102
- async appendToolCall(input) {
1103
- this.assertNotBroken();
1104
- if (this.memory.getOpenStep(input.stepUuid) === void 0) throw new Error(`appendToolCall: unknown stepUuid '${input.stepUuid}' (no open step_begin)`);
1105
- const data = sanitizeToolCallData(input.data);
1106
- await this.journalWriter.append(buildToolCallRecord(input, data));
1107
- this.memory.appendOpenStepToolCall(input.stepUuid, toolCallDataToKosongToolCall(input.data));
1108
- }
1109
- async appendNotification(data) {
1110
- this.assertNotBroken();
1111
- await this.journalWriter.append(buildNotificationRecord(data));
1112
- const text = renderNotificationXml(data);
1113
- this.memory.appendUserContent([{
1114
- type: "text",
1115
- text
1116
- }]);
1117
- }
1118
- async appendSystemReminder(data) {
1119
- this.assertNotBroken();
1120
- await this.journalWriter.append(buildSystemReminderRecord(data));
1121
- const text = `<system-reminder>\n${data.content}\n</system-reminder>`;
1122
- this.memory.appendUserContent([{
1123
- type: "text",
1124
- text
1125
- }]);
1126
- }
1127
- async clear() {
1128
- this.assertNotBroken();
1129
- await this.journalWriter.append(buildClearRecord());
1130
- this.memory.clearConversation();
1131
- }
1132
- async applyConfigChange(event) {
1133
- this.assertNotBroken();
1134
- switch (event.type) {
1135
- case "system_prompt_changed":
1136
- await this.journalWriter.append(buildConfigChangeRecord(event));
1137
- this.memory.setSystemPrompt(event.new_prompt);
1138
- return;
1139
- case "tools_changed":
1140
- await this.journalWriter.append(buildConfigChangeRecord(event));
1141
- this.memory.applyToolsChanged(event.operation, event.tools);
1142
- }
1143
- }
1144
- setModel(model) {
1145
- this.memory.setModel(model);
1146
- }
1147
- setThinkingLevel(level) {
1148
- this.memory.setThinkingLevel(level);
1149
- }
1150
- async resetToSummary(summary) {
1151
- this.assertNotBroken();
1152
- await this.journalWriter.append(buildCompactionRecord(summary));
1153
- this.memory.resetToSummary(summary);
1154
- }
1155
- };
1156
- var WiredContextState = class extends BaseContextState {};
1157
- var InMemoryContextState = class extends BaseContextState {
1158
- constructor(opts) {
1159
- super({
1160
- ...opts,
1161
- currentTurnId: opts.currentTurnId ?? (() => "embedded"),
1162
- journalWriter: new NoopJournalWriter()
1163
- });
1164
- }
1165
- };
1166
- //#endregion
1167
- //#region ../../packages/kimi-core/src/storage/state/session-journal.ts
1168
- /**
1169
- * Production `SessionJournal` implementation — delegates every append to the
1170
- * shared `JournalWriter`, which in turn serialises to wire.jsonl.
1171
- *
1172
- * Does not mutate any conversation projection state; management records do
1173
- * not change buildMessages().
1174
- */
1175
- var WiredSessionJournalImpl = class {
1176
- constructor(journalWriter) {
1177
- this.journalWriter = journalWriter;
1178
- }
1179
- async appendTurnBegin(data) {
1180
- await this.journalWriter.append(data);
1181
- }
1182
- async appendTurnEnd(data) {
1183
- await this.journalWriter.append(data);
1184
- }
1185
- async appendSkillInvoked(data) {
1186
- await this.journalWriter.append(data);
1187
- }
1188
- async appendSkillCompleted(data) {
1189
- await this.journalWriter.append(data);
1190
- }
1191
- async appendApprovalRequest(data) {
1192
- await this.journalWriter.append(data);
1193
- }
1194
- async appendApprovalResponse(data) {
1195
- await this.journalWriter.append(data);
1196
- }
1197
- async appendTeamMail(data) {
1198
- await this.journalWriter.append(data);
1199
- }
1200
- async appendToolDenied(data) {
1201
- await this.journalWriter.append(data);
1202
- }
1203
- async appendSubagentSpawned(data) {
1204
- await this.journalWriter.append(data);
1205
- }
1206
- async appendSubagentCompleted(data) {
1207
- await this.journalWriter.append(data);
1208
- }
1209
- async appendSubagentFailed(data) {
1210
- await this.journalWriter.append(data);
1211
- }
1212
- async appendOwnershipChanged(data) {
1213
- await this.journalWriter.append(data);
1214
- }
1215
- };
1216
- //#endregion
1217
- //#region ../../packages/kimi-core/src/storage/schema/records.ts
1218
- /**
1219
- * Fallback producer identity used when the host hasn't supplied one.
1220
- * Version marker makes unset state easy to spot in field reports.
1221
- */
1222
- const DEFAULT_PRODUCER = {
1223
- kind: "typescript",
1224
- name: "@moonshot-ai/core",
1225
- version: "0.0.0-unset"
1226
- };
1227
- const agentTypeEnum = z.enum([
1228
- "main",
1229
- "sub",
1230
- "independent"
1231
- ]);
1232
- const _rawWireProducerSchema = z.object({
1233
- kind: z.enum(["python", "typescript"]),
1234
- name: z.string(),
1235
- version: z.string()
1236
- });
1237
- const WireFileMetadataSchema = z.object({
1238
- type: z.literal("metadata"),
1239
- protocol_version: z.string(),
1240
- created_at: z.number(),
1241
- kimi_version: z.string().optional(),
1242
- producer: _rawWireProducerSchema.optional()
1243
- });
1244
- const _rawTurnBeginRecordSchema = z.object({
1245
- type: z.literal("turn_begin"),
1246
- seq: z.number(),
1247
- time: z.number(),
1248
- turn_id: z.string(),
1249
- agent_type: agentTypeEnum,
1250
- user_input: z.string().optional(),
1251
- input_kind: z.enum(["user", "system_trigger"]),
1252
- trigger_source: z.string().optional()
1253
- });
1254
- const _rawTurnEndRecordSchema = z.object({
1255
- type: z.literal("turn_end"),
1256
- seq: z.number(),
1257
- time: z.number(),
1258
- turn_id: z.string(),
1259
- agent_type: agentTypeEnum,
1260
- success: z.boolean(),
1261
- reason: z.enum([
1262
- "done",
1263
- "cancelled",
1264
- "error",
1265
- "interrupted"
1266
- ]),
1267
- usage: z.object({
1268
- input_tokens: z.number(),
1269
- output_tokens: z.number(),
1270
- cache_read_tokens: z.number().optional(),
1271
- cache_write_tokens: z.number().optional(),
1272
- cost_usd: z.number().optional()
1273
- }).optional(),
1274
- synthetic: z.boolean().optional()
1275
- });
1276
- const _userInputPartSchema = z.discriminatedUnion("type", [
1277
- z.object({
1278
- type: z.literal("text"),
1279
- text: z.string()
1280
- }),
1281
- z.object({
1282
- type: z.literal("image_url"),
1283
- image_url: z.object({ url: z.string() })
1284
- }),
1285
- z.object({
1286
- type: z.literal("video_url"),
1287
- video_url: z.object({ url: z.string() })
1288
- })
1289
- ]);
1290
- const _rawUserMessageRecordSchema = z.object({
1291
- type: z.literal("user_message"),
1292
- seq: z.number(),
1293
- time: z.number(),
1294
- turn_id: z.string(),
1295
- content: z.union([z.string(), z.array(_userInputPartSchema).readonly()]),
1296
- uuid: z.string().optional()
1297
- });
1298
- const _rawToolResultRecordSchema = z.object({
1299
- type: z.literal("tool_result"),
1300
- seq: z.number(),
1301
- time: z.number(),
1302
- turn_id: z.string(),
1303
- tool_call_id: z.string(),
1304
- output: z.unknown(),
1305
- is_error: z.boolean().optional(),
1306
- synthetic: z.boolean().optional(),
1307
- uuid: z.string().optional(),
1308
- parent_uuid: z.string().optional()
1309
- });
1310
- const _rawCompactionRecordSchema = z.object({
1311
- type: z.literal("compaction"),
1312
- seq: z.number(),
1313
- time: z.number(),
1314
- summary: z.string(),
1315
- compacted_range: z.object({
1316
- from_turn: z.number(),
1317
- to_turn: z.number(),
1318
- message_count: z.number()
1319
- }),
1320
- pre_compact_tokens: z.number(),
1321
- post_compact_tokens: z.number(),
1322
- trigger: z.enum(["auto", "manual"]),
1323
- archive_file: z.string().optional(),
1324
- uuid: z.string().optional()
1325
- });
1326
- const _rawSystemPromptChangedRecordSchema = z.object({
1327
- type: z.literal("system_prompt_changed"),
1328
- seq: z.number(),
1329
- time: z.number(),
1330
- new_prompt: z.string()
1331
- });
1332
- const _rawToolsChangedRecordSchema = z.object({
1333
- type: z.literal("tools_changed"),
1334
- seq: z.number(),
1335
- time: z.number(),
1336
- operation: z.enum([
1337
- "register",
1338
- "remove",
1339
- "set_active"
1340
- ]),
1341
- tools: z.array(z.string())
1342
- });
1343
- const _rawSystemReminderRecordSchema = z.object({
1344
- type: z.literal("system_reminder"),
1345
- seq: z.number(),
1346
- time: z.number(),
1347
- content: z.string(),
1348
- consumed_at_turn: z.number().optional()
1349
- });
1350
- const _rawNotificationRecordSchema = z.object({
1351
- type: z.literal("notification"),
1352
- seq: z.number(),
1353
- time: z.number(),
1354
- data: z.object({
1355
- id: z.string(),
1356
- category: z.enum([
1357
- "task",
1358
- "agent",
1359
- "system",
1360
- "team"
1361
- ]),
1362
- type: z.string(),
1363
- source_kind: z.string(),
1364
- source_id: z.string(),
1365
- title: z.string(),
1366
- body: z.string(),
1367
- severity: z.enum([
1368
- "info",
1369
- "success",
1370
- "warning",
1371
- "error"
1372
- ]),
1373
- payload: z.record(z.string(), z.unknown()).optional(),
1374
- targets: z.array(z.enum([
1375
- "llm",
1376
- "wire",
1377
- "shell"
1378
- ])),
1379
- dedupe_key: z.string().optional(),
1380
- delivered_at: z.object({
1381
- llm: z.number().optional(),
1382
- wire: z.number().optional(),
1383
- shell: z.number().optional()
1384
- }).optional(),
1385
- envelope_id: z.string().optional()
1386
- })
1387
- });
1388
- const _rawToolDeniedRecordSchema = z.object({
1389
- type: z.literal("tool_denied"),
1390
- seq: z.number(),
1391
- time: z.number(),
1392
- turn_id: z.string(),
1393
- step: z.number(),
1394
- data: z.object({
1395
- tool_call_id: z.string(),
1396
- tool_name: z.string(),
1397
- rule_id: z.string(),
1398
- reason: z.string()
1399
- })
1400
- });
1401
- const _rawStepBeginRecordSchema = z.object({
1402
- type: z.literal("step_begin"),
1403
- seq: z.number(),
1404
- time: z.number(),
1405
- uuid: z.string(),
1406
- turn_id: z.string(),
1407
- step: z.number()
1408
- });
1409
- const _rawStepEndRecordSchema = z.object({
1410
- type: z.literal("step_end"),
1411
- seq: z.number(),
1412
- time: z.number(),
1413
- uuid: z.string(),
1414
- turn_id: z.string(),
1415
- step: z.number(),
1416
- usage: z.object({
1417
- input_tokens: z.number(),
1418
- output_tokens: z.number(),
1419
- cache_read_tokens: z.number().optional(),
1420
- cache_write_tokens: z.number().optional()
1421
- }).optional(),
1422
- finish_reason: z.string().optional()
1423
- });
1424
- const _contentPartBodySchema = z.discriminatedUnion("kind", [z.object({
1425
- kind: z.literal("text"),
1426
- text: z.string()
1427
- }), z.object({
1428
- kind: z.literal("think"),
1429
- think: z.string(),
1430
- encrypted: z.string().optional()
1431
- })]);
1432
- const _rawContentPartRecordSchema = z.object({
1433
- type: z.literal("content_part"),
1434
- seq: z.number(),
1435
- time: z.number(),
1436
- uuid: z.string(),
1437
- turn_id: z.string(),
1438
- step: z.number(),
1439
- step_uuid: z.string(),
1440
- role: z.literal("assistant"),
1441
- part: _contentPartBodySchema
1442
- });
1443
- const _rawToolCallRecordSchema = z.object({
1444
- type: z.literal("tool_call"),
1445
- seq: z.number(),
1446
- time: z.number(),
1447
- uuid: z.string(),
1448
- turn_id: z.string(),
1449
- step: z.number(),
1450
- step_uuid: z.string(),
1451
- data: z.object({
1452
- tool_call_id: z.string(),
1453
- tool_name: z.string(),
1454
- args: z.unknown(),
1455
- activity_description: z.string().optional(),
1456
- user_facing_name: z.string().optional(),
1457
- input_display: z.unknown().optional()
1458
- })
1459
- });
1460
- /**
1461
- * Storage persists the approval display as an opaque blob. Write-trust
1462
- * / read-passthrough: the concrete `ToolInputDisplay` union lives in
1463
- * `display/` and is the responsibility of the host layer to validate
1464
- * at render time.
1465
- */
1466
- const ApprovalDisplaySchema = z.unknown();
1467
- const ApprovalSourceSchema = z.discriminatedUnion("kind", [
1468
- z.object({
1469
- kind: z.literal("soul"),
1470
- agent_id: z.string()
1471
- }),
1472
- z.object({
1473
- kind: z.literal("subagent"),
1474
- agent_id: z.string(),
1475
- subagent_type: z.string().optional()
1476
- }),
1477
- z.object({
1478
- kind: z.literal("turn"),
1479
- turn_id: z.string()
1480
- }),
1481
- z.object({
1482
- kind: z.literal("session"),
1483
- session_id: z.string()
1484
- }),
1485
- z.object({
1486
- kind: z.literal("mcp"),
1487
- server_id: z.string(),
1488
- reason: z.enum([
1489
- "elicitation",
1490
- "auth",
1491
- "tool_call"
1492
- ])
1493
- })
1494
- ]);
1495
- const skillInvocationTriggerEnum = z.enum([
1496
- "user-slash",
1497
- "claude-proactive",
1498
- "nested-skill"
1499
- ]);
1500
- const _rawSkillInvokedRecordSchema = z.object({
1501
- type: z.literal("skill_invoked"),
1502
- seq: z.number(),
1503
- time: z.number(),
1504
- turn_id: z.string(),
1505
- agent_type: agentTypeEnum.optional(),
1506
- data: z.object({
1507
- skill_name: z.string(),
1508
- execution_mode: z.enum(["inline", "fork"]),
1509
- original_input: z.string(),
1510
- sub_agent_id: z.string().optional(),
1511
- invocation_trigger: skillInvocationTriggerEnum.optional(),
1512
- query_depth: z.number().optional()
1513
- })
1514
- });
1515
- const _rawSkillCompletedRecordSchema = z.object({
1516
- type: z.literal("skill_completed"),
1517
- seq: z.number(),
1518
- time: z.number(),
1519
- turn_id: z.string(),
1520
- agent_type: agentTypeEnum.optional(),
1521
- data: z.object({
1522
- skill_name: z.string(),
1523
- execution_mode: z.enum(["inline", "fork"]),
1524
- success: z.boolean(),
1525
- error: z.string().optional(),
1526
- sub_agent_id: z.string().optional(),
1527
- invocation_trigger: skillInvocationTriggerEnum.optional(),
1528
- query_depth: z.number().optional()
1529
- })
1530
- });
1531
- const _rawApprovalRequestRecordSchema = z.object({
1532
- type: z.literal("approval_request"),
1533
- seq: z.number(),
1534
- time: z.number(),
1535
- turn_id: z.string(),
1536
- step: z.number(),
1537
- data: z.object({
1538
- request_id: z.string(),
1539
- tool_call_id: z.string(),
1540
- tool_name: z.string(),
1541
- action: z.string(),
1542
- display: ApprovalDisplaySchema,
1543
- source: ApprovalSourceSchema
1544
- })
1545
- });
1546
- const _rawApprovalResponseRecordSchema = z.object({
1547
- type: z.literal("approval_response"),
1548
- seq: z.number(),
1549
- time: z.number(),
1550
- turn_id: z.string(),
1551
- step: z.number(),
1552
- data: z.object({
1553
- request_id: z.string(),
1554
- response: z.enum([
1555
- "approved",
1556
- "rejected",
1557
- "cancelled"
1558
- ]),
1559
- feedback: z.string().optional(),
1560
- selected_label: z.string().optional(),
1561
- synthetic: z.boolean().optional()
1562
- })
1563
- });
1564
- const _rawTeamMailRecordSchema = z.object({
1565
- type: z.literal("team_mail"),
1566
- seq: z.number(),
1567
- time: z.number(),
1568
- data: z.object({
1569
- mail_id: z.string(),
1570
- reply_to: z.string().optional(),
1571
- from_agent: z.string(),
1572
- to_agent: z.string(),
1573
- content: z.string(),
1574
- summary: z.string().optional()
1575
- })
1576
- });
1577
- const _rawTokenUsageSchema = z.object({
1578
- input: z.number().int().nonnegative(),
1579
- output: z.number().int().nonnegative(),
1580
- cache_read: z.number().int().nonnegative().optional(),
1581
- cache_write: z.number().int().nonnegative().optional()
1582
- });
1583
- const _rawSubagentSpawnedRecordSchema = z.object({
1584
- type: z.literal("subagent_spawned"),
1585
- seq: z.number(),
1586
- time: z.number(),
1587
- uuid: z.string().optional(),
1588
- data: z.object({
1589
- agent_id: z.string(),
1590
- agent_name: z.string().optional(),
1591
- parent_tool_call_id: z.string(),
1592
- parent_agent_id: z.string().optional(),
1593
- parent_tool_call_uuid: z.string().optional(),
1594
- run_in_background: z.boolean()
1595
- })
1596
- });
1597
- const _rawSubagentCompletedRecordSchema = z.object({
1598
- type: z.literal("subagent_completed"),
1599
- seq: z.number(),
1600
- time: z.number(),
1601
- uuid: z.string().optional(),
1602
- parent_uuid: z.string().optional(),
1603
- data: z.object({
1604
- agent_id: z.string(),
1605
- parent_tool_call_id: z.string(),
1606
- result_summary: z.string(),
1607
- usage: _rawTokenUsageSchema.optional()
1608
- })
1609
- });
1610
- const _rawSubagentFailedRecordSchema = z.object({
1611
- type: z.literal("subagent_failed"),
1612
- seq: z.number(),
1613
- time: z.number(),
1614
- uuid: z.string().optional(),
1615
- parent_uuid: z.string().optional(),
1616
- data: z.object({
1617
- agent_id: z.string(),
1618
- parent_tool_call_id: z.string(),
1619
- error: z.string()
1620
- })
1621
- });
1622
- const _rawOwnershipChangedRecordSchema = z.object({
1623
- type: z.literal("ownership_changed"),
1624
- seq: z.number(),
1625
- time: z.number(),
1626
- old_owner: z.string().nullable(),
1627
- new_owner: z.string()
1628
- });
1629
- const _rawContextEditRecordSchema = z.object({
1630
- type: z.literal("context_edit"),
1631
- seq: z.number(),
1632
- time: z.number(),
1633
- operation: z.enum([
1634
- "edit_message",
1635
- "delete_message",
1636
- "rewind",
1637
- "insert_message",
1638
- "replace_message"
1639
- ]),
1640
- target_seq: z.number().optional(),
1641
- to_turn: z.number().optional(),
1642
- after_seq: z.number().optional(),
1643
- new_content: z.string().optional(),
1644
- new_role: z.enum([
1645
- "user",
1646
- "assistant",
1647
- "system"
1648
- ]).optional(),
1649
- cascade: z.boolean().optional()
1650
- });
1651
- const _rawContextClearedRecordSchema = z.object({
1652
- type: z.literal("context_cleared"),
1653
- seq: z.number(),
1654
- time: z.number()
1655
- });
1656
- const _sessionInitializedCommonShape = {
1657
- type: z.literal("session_initialized"),
1658
- seq: z.number(),
1659
- time: z.number(),
1660
- system_prompt: z.string(),
1661
- active_tools: z.array(z.string()),
1662
- model: z.string().optional(),
1663
- permission_mode: z.enum([
1664
- "default",
1665
- "acceptEdits",
1666
- "bypassPermissions"
1667
- ]).optional(),
1668
- plan_mode: z.boolean().optional(),
1669
- workspace_dir: z.string().optional(),
1670
- thinking_level: z.string().optional()
1671
- };
1672
- const _rawSessionInitializedMainSchema = z.object({
1673
- ..._sessionInitializedCommonShape,
1674
- agent_type: z.literal("main"),
1675
- session_id: z.string()
1676
- });
1677
- const _rawSessionInitializedSubSchema = z.object({
1678
- ..._sessionInitializedCommonShape,
1679
- agent_type: z.literal("sub"),
1680
- agent_id: z.string(),
1681
- agent_name: z.string().optional(),
1682
- parent_session_id: z.string(),
1683
- parent_agent_id: z.string().optional(),
1684
- parent_tool_call_id: z.string(),
1685
- run_in_background: z.boolean()
1686
- });
1687
- const _rawSessionInitializedIndependentSchema = z.object({
1688
- ..._sessionInitializedCommonShape,
1689
- agent_type: z.literal("independent"),
1690
- agent_id: z.string(),
1691
- agent_name: z.string().optional()
1692
- });
1693
- const _rawSessionInitializedRecordSchema = z.discriminatedUnion("agent_type", [
1694
- _rawSessionInitializedMainSchema,
1695
- _rawSessionInitializedSubSchema,
1696
- _rawSessionInitializedIndependentSchema
1697
- ]);
1698
- const SessionInitializedRecordSchema = _rawSessionInitializedRecordSchema;
1699
- const WireRecordSchema = z.discriminatedUnion("type", [
1700
- _rawTurnBeginRecordSchema,
1701
- _rawTurnEndRecordSchema,
1702
- _rawUserMessageRecordSchema,
1703
- _rawToolResultRecordSchema,
1704
- _rawCompactionRecordSchema,
1705
- _rawSystemPromptChangedRecordSchema,
1706
- _rawToolsChangedRecordSchema,
1707
- _rawSystemReminderRecordSchema,
1708
- _rawNotificationRecordSchema,
1709
- _rawToolDeniedRecordSchema,
1710
- _rawStepBeginRecordSchema,
1711
- _rawStepEndRecordSchema,
1712
- _rawContentPartRecordSchema,
1713
- _rawToolCallRecordSchema,
1714
- _rawSkillInvokedRecordSchema,
1715
- _rawSkillCompletedRecordSchema,
1716
- _rawApprovalRequestRecordSchema,
1717
- _rawApprovalResponseRecordSchema,
1718
- _rawTeamMailRecordSchema,
1719
- _rawSubagentSpawnedRecordSchema,
1720
- _rawSubagentCompletedRecordSchema,
1721
- _rawSubagentFailedRecordSchema,
1722
- _rawOwnershipChangedRecordSchema,
1723
- _rawContextEditRecordSchema,
1724
- _rawContextClearedRecordSchema,
1725
- _rawSessionInitializedRecordSchema
1726
- ]);
1727
- //#endregion
1728
- //#region ../../packages/kimi-core/src/storage/journal/reader.ts
1729
- async function defaultReadLines(path) {
1730
- const parts = (await readFile(path, "utf8")).split("\n");
1731
- if (parts.length > 0 && parts.at(-1) === "") parts.pop();
1732
- return parts;
1733
- }
1734
- /**
1735
- * Replay a wire.jsonl into an ordered list of valid WireRecords plus a
1736
- * health signal. Does not construct a ContextState — that concern lives in
1737
- * WiredContextState which drives its internal mirror from these records.
1738
- *
1739
- * Error policy (§4.1.1 / §8.4):
1740
- * - unknown record type at any line → skip + warn
1741
- * - JSON.parse failure on the LAST body line → skip + warn (tail truncation)
1742
- * - JSON.parse failure on any earlier line → health = 'broken'
1743
- * - major version higher than supportedMajor → throw IncompatibleVersionError
1744
- */
1745
- async function replayWire(path, options) {
1746
- const lines = await (options.readLines ?? defaultReadLines)(path);
1747
- if (lines.length === 0) throw new WireJournalCorruptError(`wire.jsonl is empty at ${path}`);
1748
- const firstLine = lines[0];
1749
- if (firstLine === void 0) throw new WireJournalCorruptError(`wire.jsonl is empty at ${path}`);
1750
- let meta;
1751
- try {
1752
- meta = WireFileMetadataSchema.parse(JSON.parse(firstLine));
1753
- } catch (error) {
1754
- throw new WireJournalCorruptError(`wire.jsonl metadata header (line 1) is invalid: ${String(error)}`);
1755
- }
1756
- const majorStr = meta.protocol_version.split(".")[0] ?? "0";
1757
- const major = Number.parseInt(majorStr, 10);
1758
- if (!Number.isFinite(major)) throw new IncompatibleVersionError(`wire.jsonl metadata.protocol_version "${meta.protocol_version}" is not a valid version string`);
1759
- if (major > options.supportedMajor) throw new IncompatibleVersionError(`wire.jsonl version ${meta.protocol_version} is not supported (max major: ${options.supportedMajor}). Please upgrade Kimi CLI.`);
1760
- if (meta.producer === void 0) throw new UnsupportedProducerError("legacy", "metadata-missing-producer");
1761
- if (meta.producer.kind !== "typescript") throw new UnsupportedProducerError(meta.producer.kind === "python" ? "python" : "unknown", "cross-producer-not-supported");
1762
- const producer = meta.producer;
1763
- const initLine = lines[1];
1764
- if (initLine === void 0) throw new MalformedWireError("session-initialized-missing", `wire.jsonl has no line 2 (session_initialized) at ${path}`);
1765
- let initRaw;
1766
- try {
1767
- initRaw = JSON.parse(initLine);
1768
- } catch (error) {
1769
- throw new MalformedWireError("session-initialized-missing", `wire.jsonl line 2 failed JSON.parse: ${String(error)}`);
1391
+ get thinkingLevel() {
1392
+ return this._thinkingLevel;
1770
1393
  }
1771
- const initTypeField = initRaw?.type;
1772
- if (initTypeField !== "session_initialized") throw new MalformedWireError("session-initialized-missing", `wire.jsonl line 2 has type="${String(initTypeField)}"; expected "session_initialized"`);
1773
- const initParsed = SessionInitializedRecordSchema.safeParse(initRaw);
1774
- if (!initParsed.success) throw new MalformedWireError("session-initialized-missing", `wire.jsonl line 2 failed zod parse: ${initParsed.error.message}`);
1775
- const sessionInitialized = initParsed.data;
1776
- const records = [];
1777
- const warnings = [];
1778
- const bodyLines = lines.slice(2);
1779
- for (const [i, line] of bodyLines.entries()) {
1780
- const physicalLineNo = i + 3;
1781
- const isLastLine = i === bodyLines.length - 1;
1782
- const snippet = line.slice(0, 100);
1783
- let raw;
1784
- try {
1785
- raw = JSON.parse(line);
1786
- } catch (error) {
1787
- if (isLastLine) {
1788
- warnings.push(`Tail line truncated at line ${physicalLineNo}, skipping: ${snippet}`);
1789
- continue;
1790
- }
1791
- const reason = `wire.jsonl mid-file corruption at line ${physicalLineNo}: ${String(error)}`;
1792
- return {
1793
- records,
1794
- protocolVersion: meta.protocol_version,
1795
- health: "broken",
1796
- brokenReason: reason,
1797
- warnings,
1798
- producer,
1799
- sessionInitialized
1800
- };
1801
- }
1802
- const typeField = raw?.type;
1803
- if (typeField === "session_initialized") throw new MalformedWireError("session-initialized-position-wrong", `wire.jsonl has a session_initialized record at line ${physicalLineNo}; only line 2 is permitted`);
1804
- if (typeof typeField !== "string" || !KNOWN_RECORD_TYPES.has(typeField)) {
1805
- if (typeof typeField === "string" && LEGACY_RECORD_TYPES.has(typeField)) warnings.push(`Skipping legacy record type "${typeField}" at line ${physicalLineNo} (migrated to state.json)`);
1806
- else warnings.push(`Skipping unrecognized record type "${String(typeField)}" at line ${physicalLineNo}: ${snippet}`);
1807
- continue;
1394
+ get beforeStep() {
1395
+ return this._beforeStep;
1396
+ }
1397
+ setBeforeStepHook(fn) {
1398
+ this._beforeStep = fn;
1399
+ }
1400
+ getHistory() {
1401
+ return this.history;
1402
+ }
1403
+ drainSteerMessages() {
1404
+ const drained = this.steerBuffer;
1405
+ this.steerBuffer = [];
1406
+ return drained;
1407
+ }
1408
+ pushSteer(input) {
1409
+ this.steerBuffer.push({ ...input });
1410
+ }
1411
+ appendUserContent(content) {
1412
+ this.history.push({
1413
+ role: "user",
1414
+ content,
1415
+ toolCalls: []
1416
+ });
1417
+ }
1418
+ appendToolResult(toolCallId, content) {
1419
+ this.history.push({
1420
+ role: "tool",
1421
+ content,
1422
+ toolCalls: [],
1423
+ toolCallId
1424
+ });
1425
+ }
1426
+ openStep(uuid) {
1427
+ const message = {
1428
+ role: "assistant",
1429
+ content: [],
1430
+ toolCalls: []
1431
+ };
1432
+ this.history.push(message);
1433
+ this.openSteps.set(uuid, message);
1434
+ }
1435
+ closeStep(uuid, usage) {
1436
+ this.openSteps.delete(uuid);
1437
+ if (usage !== void 0) this._tokenCountWithPending = usage.input_tokens + usage.output_tokens;
1438
+ }
1439
+ getOpenStep(uuid) {
1440
+ return this.openSteps.get(uuid);
1441
+ }
1442
+ appendOpenStepContent(stepUuid, part) {
1443
+ this.openSteps.get(stepUuid)?.content.push(part);
1444
+ }
1445
+ appendOpenStepToolCall(stepUuid, toolCall) {
1446
+ this.openSteps.get(stepUuid)?.toolCalls.push(toolCall);
1447
+ }
1448
+ clearConversation() {
1449
+ this.history = [];
1450
+ this.openSteps.clear();
1451
+ this._tokenCountWithPending = 0;
1452
+ }
1453
+ setSystemPrompt(systemPrompt) {
1454
+ this._systemPrompt = systemPrompt;
1455
+ }
1456
+ setModel(model) {
1457
+ this._model = model;
1458
+ }
1459
+ setThinkingLevel(level) {
1460
+ this._thinkingLevel = level;
1461
+ }
1462
+ applyToolsChanged(operation, tools) {
1463
+ if (operation === "set_active") {
1464
+ this._activeTools = new Set(tools);
1465
+ return;
1808
1466
  }
1809
- const parsed = WireRecordSchema.safeParse(raw);
1810
- if (!parsed.success) {
1811
- if (isLastLine) {
1812
- warnings.push(`Tail record failed schema validation at line ${physicalLineNo}, skipping: ${snippet}`);
1813
- continue;
1814
- }
1815
- const reason = `wire.jsonl schema violation at line ${physicalLineNo}: ${parsed.error.message}`;
1816
- return {
1817
- records,
1818
- protocolVersion: meta.protocol_version,
1819
- health: "broken",
1820
- brokenReason: reason,
1821
- warnings,
1822
- producer,
1823
- sessionInitialized
1824
- };
1467
+ if (operation === "register") {
1468
+ for (const tool of tools) this._activeTools.add(tool);
1469
+ return;
1825
1470
  }
1826
- records.push(parsed.data);
1471
+ for (const tool of tools) this._activeTools.delete(tool);
1472
+ }
1473
+ resetToSummary(summary) {
1474
+ this.history = [{
1475
+ role: "assistant",
1476
+ content: [{
1477
+ type: "text",
1478
+ text: summary.summary
1479
+ }],
1480
+ toolCalls: []
1481
+ }];
1482
+ this.openSteps.clear();
1483
+ this._tokenCountWithPending = summary.postCompactTokens;
1827
1484
  }
1485
+ };
1486
+ //#endregion
1487
+ //#region ../../packages/kimi-core/src/storage/state/message-mirror.ts
1488
+ function userInputToContentParts(input) {
1489
+ if (input.parts !== void 0 && input.parts.some((part) => part.type !== "text") && input.parts !== void 0) return input.parts.map(userInputPartToContentPart);
1490
+ return [{
1491
+ type: "text",
1492
+ text: input.parts !== void 0 ? input.parts.filter((part) => part.type === "text").map((part) => part.text).join("") : input.text
1493
+ }];
1494
+ }
1495
+ function toolResultOutputToContentParts(output) {
1496
+ if (isContentPartArray$1(output)) return output.map((part) => cloneContentPart$1(part));
1497
+ return [{
1498
+ type: "text",
1499
+ text: typeof output === "string" ? output : JSON.stringify(output)
1500
+ }];
1501
+ }
1502
+ function atomicPartToContentPart(part) {
1503
+ if (part.kind === "text") return {
1504
+ type: "text",
1505
+ text: part.text
1506
+ };
1507
+ const thinkPart = {
1508
+ type: "think",
1509
+ think: part.think
1510
+ };
1511
+ if (part.encrypted !== void 0) thinkPart.encrypted = part.encrypted;
1512
+ return thinkPart;
1513
+ }
1514
+ function sanitizeToolCallData(data) {
1828
1515
  return {
1829
- records,
1830
- protocolVersion: meta.protocol_version,
1831
- health: "ok",
1832
- warnings,
1833
- producer,
1834
- sessionInitialized
1516
+ tool_call_id: data.tool_call_id,
1517
+ tool_name: data.tool_name,
1518
+ args: data.args,
1519
+ ...data.activity_description !== void 0 ? { activity_description: data.activity_description } : {},
1520
+ user_facing_name: data.user_facing_name,
1521
+ input_display: data.input_display
1835
1522
  };
1836
1523
  }
1837
- const KNOWN_RECORD_TYPES = new Set([
1838
- "turn_begin",
1839
- "turn_end",
1840
- "user_message",
1841
- "tool_result",
1842
- "compaction",
1843
- "system_prompt_changed",
1844
- "tools_changed",
1845
- "system_reminder",
1846
- "notification",
1847
- "tool_denied",
1848
- "step_begin",
1849
- "step_end",
1850
- "content_part",
1851
- "tool_call",
1852
- "skill_invoked",
1853
- "skill_completed",
1854
- "approval_request",
1855
- "approval_response",
1856
- "team_mail",
1857
- "subagent_spawned",
1858
- "subagent_completed",
1859
- "subagent_failed",
1860
- "ownership_changed",
1861
- "context_edit",
1862
- "context_cleared"
1863
- ]);
1524
+ function toolCallDataToKosongToolCall(data) {
1525
+ return {
1526
+ type: "function",
1527
+ id: data.tool_call_id,
1528
+ function: {
1529
+ name: data.tool_name,
1530
+ arguments: data.args === void 0 ? null : JSON.stringify(data.args)
1531
+ }
1532
+ };
1533
+ }
1534
+ function userInputPartToContentPart(part) {
1535
+ if (part.type === "text") return {
1536
+ type: "text",
1537
+ text: part.text
1538
+ };
1539
+ if (part.type === "image_url") return {
1540
+ type: "image_url",
1541
+ imageUrl: part.image_url
1542
+ };
1543
+ return {
1544
+ type: "video_url",
1545
+ videoUrl: part.video_url
1546
+ };
1547
+ }
1548
+ function isContentPart$2(value) {
1549
+ if (typeof value !== "object" || value === null) return false;
1550
+ const part = value;
1551
+ return part.type === "text" || part.type === "think" || part.type === "image_url" || part.type === "audio_url" || part.type === "video_url";
1552
+ }
1553
+ function isContentPartArray$1(value) {
1554
+ return Array.isArray(value) && value.every((part) => isContentPart$2(part));
1555
+ }
1556
+ function cloneContentPart$1(part) {
1557
+ return { ...part };
1558
+ }
1559
+ //#endregion
1560
+ //#region ../../packages/kimi-core/src/storage/state/notification-xml.ts
1864
1561
  /**
1865
- * Record types that earlier schema versions persisted to `wire.jsonl`
1866
- * but have since been migrated to `state.json` as the truth source.
1562
+ * Notification XML rendering shared between ContextState and
1563
+ * ConversationProjector so both produce byte-identical chat-history
1564
+ * injection text.
1867
1565
  *
1868
- * Sessions written by older binaries still carry rows of these types.
1869
- * Keeping them in a dedicated set lets the replay loop emit an
1870
- * informational "legacy" warning instead of the forward-compat
1871
- * "unrecognized" warning, so it is clear these rows are expected to be
1872
- * dropped rather than being an unknown future-version type.
1566
+ * Output shape:
1567
+ * <notification id="..." category="..." type="..." source_kind="..." source_id="...">
1568
+ * Title: ...
1569
+ * Severity: ...
1570
+ * <body>
1571
+ * <task-notification> (only when source_kind === 'background_task' and tail_output is non-empty)
1572
+ * <truncated tail>
1573
+ * </task-notification>
1574
+ * </notification>
1575
+ *
1576
+ * The opening-tag names (`<notification ` / `<task-notification>`) are
1577
+ * load-bearing for the projector's `mergeAdjacentUserMessages` detector
1578
+ * — rename requires updating the detector too.
1873
1579
  */
1874
- const LEGACY_RECORD_TYPES = new Set([
1875
- "permission_mode_changed",
1876
- "model_changed",
1877
- "thinking_changed",
1878
- "plan_mode_changed",
1879
- "session_meta_changed"
1880
- ]);
1580
+ function renderNotificationXml(data) {
1581
+ const id = stringAttr(data["id"], "unknown");
1582
+ const category = stringAttr(data["category"], "unknown");
1583
+ const type = stringAttr(data["type"], "unknown");
1584
+ const sourceKind = stringAttr(data["source_kind"], "unknown");
1585
+ const sourceId = stringAttr(data["source_id"], "unknown");
1586
+ const title = typeof data["title"] === "string" ? data["title"] : "";
1587
+ const severity = typeof data["severity"] === "string" ? data["severity"] : "";
1588
+ const body = typeof data["body"] === "string" ? data["body"] : "";
1589
+ const lines = [`<notification id="${id}" category="${category}" type="${type}" source_kind="${sourceKind}" source_id="${sourceId}">`];
1590
+ if (title.length > 0) lines.push(`Title: ${title}`);
1591
+ if (severity.length > 0) lines.push(`Severity: ${severity}`);
1592
+ if (body.length > 0) lines.push(body);
1593
+ if (data["source_kind"] === "background_task") {
1594
+ const tailRaw = typeof data["tail_output"] === "string" ? data["tail_output"] : "";
1595
+ if (tailRaw.length > 0) {
1596
+ const truncated = truncateTailOutput(tailRaw, 20, 3e3);
1597
+ lines.push("<task-notification>");
1598
+ lines.push(truncated);
1599
+ lines.push("</task-notification>");
1600
+ }
1601
+ }
1602
+ lines.push("</notification>");
1603
+ return lines.join("\n");
1604
+ }
1605
+ /**
1606
+ * Truncate tail output to at most `maxLines` lines and `maxChars`
1607
+ * characters. Takes the *last* N lines, then trims from the front if
1608
+ * the character budget is exceeded.
1609
+ */
1610
+ function truncateTailOutput(raw, maxLines, maxChars) {
1611
+ const allLines = raw.split("\n");
1612
+ let result = (allLines.length > maxLines ? allLines.slice(-maxLines) : allLines).join("\n");
1613
+ if (result.length > maxChars) result = result.slice(-maxChars);
1614
+ return result;
1615
+ }
1616
+ function stringAttr(value, fallback) {
1617
+ if (typeof value !== "string" || value.length === 0) return fallback;
1618
+ return value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;");
1619
+ }
1881
1620
  //#endregion
1882
- //#region ../../packages/kimi-core/src/storage/journal/rotation.ts
1621
+ //#region ../../packages/kimi-core/src/storage/state/projector.ts
1883
1622
  /**
1884
- * Storage-layer compaction utilities file rotation and cross-file replay
1885
- * (§4.7 / §4.1.1).
1623
+ * Pure read-side projector for provider-visible messages. It passes through
1624
+ * persisted history, merges adjacent user messages, filters incomplete
1625
+ * assistant placeholders, and injects ephemeral annotations without writing
1626
+ * anything back to the journal.
1886
1627
  *
1887
- * File rotation is the physical counterpart to TurnManager's
1888
- * `executeCompaction` (Phase 2; previously Soul's `runCompaction`):
1889
- * 1. Rename `wire.jsonl` → `wire.N.jsonl` (frozen archive)
1890
- * 2. Create new `wire.jsonl` with metadata header + CompactionRecord
1628
+ * The system prompt is not injected here; it is forwarded separately through
1629
+ * provider chat parameters to avoid duplicate system messages.
1630
+ */
1631
+ var DefaultConversationProjector = class {
1632
+ project(snapshot, ephemeralInjectionsOrOptions, options) {
1633
+ const ephemeralInjections = Array.isArray(ephemeralInjectionsOrOptions) ? ephemeralInjectionsOrOptions : [];
1634
+ const merged = mergeAdjacentUserMessages(snapshot.history.filter((m) => m.partial !== true && !(m.role === "assistant" && m.content.length === 0 && m.toolCalls.length === 0)));
1635
+ return [...ephemeralInjections.length === 0 ? [] : ephemeralInjections.map((injection) => renderInjection(injection)), ...merged];
1636
+ }
1637
+ };
1638
+ /**
1639
+ * Render an EphemeralInjection into a synthetic user message. System
1640
+ * reminders and pending notifications use XML wrappers so the model can
1641
+ * distinguish host annotations from genuine user text. `memory_recall`
1642
+ * stays as free text.
1891
1643
  *
1892
- * Cross-file replay reads all wire files in a session directory
1893
- * (wire.N.jsonl ... wire.1.jsonl wire.jsonl) and produces a
1894
- * unified record stream.
1644
+ * The merge-guard logic downstream (`mergeAdjacentUserMessages`) uses
1645
+ * the `<notification ` / `<system-reminder>` opening tag to detect
1646
+ * these messages, so the exact tag names are load-bearing for
1647
+ * projector correctness — do not rename without also updating
1648
+ * `isInjectionUserMessage` below.
1649
+ */
1650
+ function renderInjection(injection) {
1651
+ return {
1652
+ role: "user",
1653
+ content: [{
1654
+ type: "text",
1655
+ text: renderInjectionText(injection)
1656
+ }],
1657
+ toolCalls: []
1658
+ };
1659
+ }
1660
+ function renderInjectionText(injection) {
1661
+ const { kind, content } = injection;
1662
+ if (kind === "pending_notification") {
1663
+ if (typeof content === "string") return `<notification>\n${content}\n</notification>`;
1664
+ return renderNotificationXml(content);
1665
+ }
1666
+ if (kind === "system_reminder") return `<system-reminder>\n${typeof content === "string" ? content : JSON.stringify(content)}\n</system-reminder>`;
1667
+ return typeof content === "string" ? content : JSON.stringify(content);
1668
+ }
1669
+ /**
1670
+ * Detect whether a user message was produced by the ephemeral injection
1671
+ * pipeline (system_reminder or notification XML tag). Such messages
1672
+ * must never be merged with an adjacent real user turn — doing so would
1673
+ * smear the injection's XML wrapper into the user's actual prompt and
1674
+ * confuse the LLM about where the system annotation ends.
1895
1675
  *
1896
- * Crash recovery detects "wire.jsonl missing but wire.N.jsonl exists"
1897
- * and rolls back the lowest-numbered archive to `wire.jsonl`.
1898
1676
  */
1899
- const ARCHIVE_PATTERN = /^wire\.(\d+)\.jsonl$/;
1900
- function extractArchiveNumber(name) {
1901
- const captured = ARCHIVE_PATTERN.exec(name)?.[1];
1902
- return captured !== void 0 ? Number.parseInt(captured, 10) : 0;
1677
+ function isInjectionUserMessage(message) {
1678
+ if (message.role !== "user") return false;
1679
+ const trimmed = extractTextOnly(message).trimStart();
1680
+ if (trimmed.startsWith("<notification ")) return true;
1681
+ if (trimmed.startsWith("<system-reminder>")) return true;
1682
+ return false;
1683
+ }
1684
+ function mergeAdjacentUserMessages(history) {
1685
+ const out = [];
1686
+ for (const message of history) {
1687
+ const previous = out.at(-1);
1688
+ if (message.role === "user" && previous !== void 0 && previous.role === "user" && !isInjectionUserMessage(message) && !isInjectionUserMessage(previous)) {
1689
+ out[out.length - 1] = mergeTwoUserMessages(previous, message);
1690
+ continue;
1691
+ }
1692
+ out.push(cloneMessage(message));
1693
+ }
1694
+ return out;
1695
+ }
1696
+ function mergeTwoUserMessages(a, b) {
1697
+ const aText = extractTextOnly(a);
1698
+ const bText = extractTextOnly(b);
1699
+ const nonTextParts = [...a.content.filter((p) => p.type !== "text"), ...b.content.filter((p) => p.type !== "text")];
1700
+ return {
1701
+ role: "user",
1702
+ content: [{
1703
+ type: "text",
1704
+ text: `${aText}\n\n${bText}`
1705
+ }, ...nonTextParts],
1706
+ toolCalls: []
1707
+ };
1708
+ }
1709
+ function extractTextOnly(message) {
1710
+ return message.content.filter((p) => p.type === "text").map((p) => p.text).join("");
1711
+ }
1712
+ function cloneMessage(message) {
1713
+ return {
1714
+ role: message.role,
1715
+ name: message.name,
1716
+ content: message.content.map((p) => ({ ...p })),
1717
+ toolCalls: message.toolCalls.map((tc) => ({ ...tc })),
1718
+ toolCallId: message.toolCallId,
1719
+ partial: message.partial
1720
+ };
1721
+ }
1722
+ //#endregion
1723
+ //#region ../../packages/kimi-core/src/storage/state/record-builders.ts
1724
+ function buildUserMessageRecord(turnId, input) {
1725
+ return {
1726
+ type: "user_message",
1727
+ turn_id: turnId,
1728
+ content: input.parts !== void 0 ? input.parts : input.text
1729
+ };
1730
+ }
1731
+ function buildToolResultRecord(turnId, parentUuid, toolCallId, normalisedOutput, result) {
1732
+ return {
1733
+ type: "tool_result",
1734
+ turn_id: turnId,
1735
+ tool_call_id: toolCallId,
1736
+ output: normalisedOutput,
1737
+ parent_uuid: parentUuid,
1738
+ is_error: result.isError,
1739
+ synthetic: result.synthetic
1740
+ };
1741
+ }
1742
+ function buildStepBeginRecord(input) {
1743
+ return {
1744
+ type: "step_begin",
1745
+ uuid: input.uuid,
1746
+ turn_id: input.turnId,
1747
+ step: input.step
1748
+ };
1749
+ }
1750
+ function buildStepEndRecord(input) {
1751
+ return {
1752
+ type: "step_end",
1753
+ uuid: input.uuid,
1754
+ turn_id: input.turnId,
1755
+ step: input.step,
1756
+ usage: input.usage,
1757
+ finish_reason: input.finishReason
1758
+ };
1759
+ }
1760
+ function buildContentPartRecord(input) {
1761
+ return {
1762
+ type: "content_part",
1763
+ uuid: input.uuid,
1764
+ turn_id: input.turnId,
1765
+ step: input.step,
1766
+ step_uuid: input.stepUuid,
1767
+ role: "assistant",
1768
+ part: input.part.kind === "text" ? {
1769
+ kind: "text",
1770
+ text: input.part.text
1771
+ } : input.part.encrypted !== void 0 ? {
1772
+ kind: "think",
1773
+ think: input.part.think,
1774
+ encrypted: input.part.encrypted
1775
+ } : {
1776
+ kind: "think",
1777
+ think: input.part.think
1778
+ }
1779
+ };
1780
+ }
1781
+ function buildToolCallRecord(input, data) {
1782
+ return {
1783
+ type: "tool_call",
1784
+ uuid: input.uuid,
1785
+ turn_id: input.turnId,
1786
+ step: input.step,
1787
+ step_uuid: input.stepUuid,
1788
+ data
1789
+ };
1903
1790
  }
1904
- /**
1905
- * Compute the next archive filename for a rotation:
1906
- * - No archives exist → `wire.1.jsonl`
1907
- * - Highest existing is `wire.3.jsonl` → `wire.4.jsonl`
1908
- */
1909
- function nextArchiveName(sessionDir, existingArchives) {
1910
- let maxN = 0;
1911
- for (const archive of existingArchives) {
1912
- const n = extractArchiveNumber(archive);
1913
- if (n > maxN) maxN = n;
1914
- }
1915
- return join(sessionDir, `wire.${maxN + 1}.jsonl`);
1791
+ function buildNotificationRecord(data) {
1792
+ return {
1793
+ type: "notification",
1794
+ data
1795
+ };
1916
1796
  }
1917
- async function rotateJournal(sessionDir, options) {
1918
- const { producer, protocolVersion, ...deps } = options;
1919
- const version = protocolVersion ?? "2.1";
1920
- const currentPath = join(sessionDir, "wire.jsonl");
1921
- const archivePath = nextArchiveName(sessionDir, (await readdir(sessionDir)).filter((e) => ARCHIVE_PATTERN.test(e)));
1922
- const renameFn = deps.renameFn ?? rename;
1923
- const retryDelayMs = deps.retryDelayMs ?? 500;
1924
- try {
1925
- await renameFn(currentPath, archivePath);
1926
- } catch (error) {
1927
- const code = error.code;
1928
- if (process.platform === "win32" && code === "EPERM") {
1929
- await new Promise((resolve, reject) => {
1930
- const timer = setTimeout(resolve, retryDelayMs);
1931
- const abortSignal = deps.signal;
1932
- if (abortSignal !== void 0) {
1933
- const onAbort = () => {
1934
- clearTimeout(timer);
1935
- reject(abortSignal.reason ?? /* @__PURE__ */ new Error("aborted"));
1936
- };
1937
- if (abortSignal.aborted) onAbort();
1938
- else abortSignal.addEventListener("abort", onAbort, { once: true });
1939
- }
1940
- });
1941
- await renameFn(currentPath, archivePath);
1942
- } else throw error;
1943
- }
1944
- await writeFileAtomicDurable(currentPath, JSON.stringify({
1945
- type: "metadata",
1946
- protocol_version: version,
1947
- created_at: Date.now(),
1948
- producer,
1949
- kimi_version: producer.version
1950
- }) + "\n");
1951
- await syncDir(sessionDir);
1797
+ function buildSystemReminderRecord(data) {
1952
1798
  return {
1953
- archivePath,
1954
- newCurrentPath: currentPath
1799
+ type: "system_reminder",
1800
+ content: data.content
1955
1801
  };
1956
1802
  }
1957
- /**
1958
- * Find the highest-numbered archive in a list of archive filenames.
1959
- * Returns the filename and its number, or `undefined` if the list is empty.
1960
- */
1961
- function findHighestArchive(archives) {
1962
- let highestN = 0;
1963
- let result;
1964
- for (const name of archives) {
1965
- const n = extractArchiveNumber(name);
1966
- if (n > highestN) {
1967
- highestN = n;
1968
- result = name;
1969
- }
1803
+ function buildClearRecord() {
1804
+ return { type: "context_cleared" };
1805
+ }
1806
+ function buildConfigChangeRecord(event) {
1807
+ switch (event.type) {
1808
+ case "system_prompt_changed": return {
1809
+ type: "system_prompt_changed",
1810
+ new_prompt: event.new_prompt
1811
+ };
1812
+ case "tools_changed": return {
1813
+ type: "tools_changed",
1814
+ operation: event.operation,
1815
+ tools: event.tools
1816
+ };
1970
1817
  }
1971
- return result !== void 0 ? {
1972
- name: result,
1973
- n: highestN
1974
- } : void 0;
1975
1818
  }
1819
+ function buildCompactionRecord(summary) {
1820
+ return {
1821
+ type: "compaction",
1822
+ summary: summary.summary,
1823
+ compacted_range: {
1824
+ from_turn: summary.compactedRange.fromTurn,
1825
+ to_turn: summary.compactedRange.toTurn,
1826
+ message_count: summary.compactedRange.messageCount
1827
+ },
1828
+ pre_compact_tokens: summary.preCompactTokens,
1829
+ post_compact_tokens: summary.postCompactTokens,
1830
+ trigger: summary.trigger,
1831
+ archive_file: summary.archiveFile
1832
+ };
1833
+ }
1834
+ //#endregion
1835
+ //#region ../../packages/kimi-core/src/storage/state/context-state.ts
1976
1836
  /**
1977
- * Detect and recover from a crash between file rotation steps.
1978
- *
1979
- * Three scenarios are handled:
1980
- *
1981
- * 1. **wire.jsonl missing**: `wire.jsonl` was renamed to `wire.N.jsonl`
1982
- * but the process crashed before the new `wire.jsonl` was created.
1983
- * Recovery: roll back the highest-numbered archive to `wire.jsonl`.
1984
- *
1985
- * 2. **wire.jsonl is metadata-only** (Slice 3.3 / M04): the rename
1986
- * succeeded AND the new `wire.jsonl` was created with a metadata
1987
- * header, but the process crashed before `appendBoundary` ran. The
1988
- * new file has only the metadata line — no session_initialized, no
1989
- * compaction record. Recovery: remove the half-complete file and
1990
- * roll back the highest archive.
1837
+ * Core ContextState logic. Both `WiredContextState` and
1838
+ * `InMemoryContextState` are thin wrappers that differ only in the journal
1839
+ * writer they supply and the constructor shape they expose.
1991
1840
  *
1992
- * 3. **wire.jsonl has metadata + session_initialized only** (Phase 23 /
1993
- * T7.7): `appendBoundary` copied `session_initialized` through as the
1994
- * second line, but the process crashed before the compaction record
1995
- * landed. Without the compaction record, the archived conversation is
1996
- * orphaned — replay of the new wire would show an empty post-boundary
1997
- * window and the archive would never be re-read. Recovery: same as
1998
- * case 2 — remove the half-complete file and restore the archive.
1841
+ * WAL-then-mirror atomicity:
1842
+ * 1. build the WireRecord
1843
+ * 2. await journalWriter.append(...)
1844
+ * 3. on success only, update the in-memory projection
1999
1845
  *
2000
- * Returns `true` if recovery was performed, `false` if no recovery was needed.
1846
+ * If step 2 throws, the in-memory state is unchanged.
2001
1847
  */
2002
- async function recoverRotation(sessionDir) {
2003
- const entries = await readdir(sessionDir);
2004
- const archives = entries.filter((e) => ARCHIVE_PATTERN.test(e));
2005
- if (!entries.includes("wire.jsonl")) {
2006
- const highest = findHighestArchive(archives);
2007
- if (highest === void 0) return false;
2008
- await rename(join(sessionDir, highest.name), join(sessionDir, "wire.jsonl"));
1848
+ var BaseContextState = class {
1849
+ journalWriter;
1850
+ projector;
1851
+ currentTurnId;
1852
+ memory;
1853
+ _broken = false;
1854
+ _brokenError;
1855
+ /**
1856
+ * Marks all future write entry points as failed. Read methods remain
1857
+ * callable so callers can still inspect the last valid in-memory state.
1858
+ */
1859
+ markBroken(error) {
1860
+ if (this._broken) return;
1861
+ this._broken = true;
1862
+ this._brokenError = error;
1863
+ }
1864
+ assertNotBroken() {
1865
+ if (this._broken) throw new ContextStateBrokenError("ContextState is broken due to a prior persist error", this._brokenError);
1866
+ }
1867
+ constructor(opts) {
1868
+ this.journalWriter = opts.journalWriter;
1869
+ this.projector = opts.projector ?? new DefaultConversationProjector();
1870
+ this.currentTurnId = opts.currentTurnId;
1871
+ this.memory = new ContextStateMemory(opts);
1872
+ }
1873
+ get model() {
1874
+ return this.memory.model;
1875
+ }
1876
+ get systemPrompt() {
1877
+ return this.memory.systemPrompt;
1878
+ }
1879
+ get activeTools() {
1880
+ return this.memory.activeTools;
1881
+ }
1882
+ get tokenCountWithPending() {
1883
+ return this.memory.tokenCountWithPending;
1884
+ }
1885
+ get thinkingLevel() {
1886
+ return this.memory.thinkingLevel;
1887
+ }
1888
+ get beforeStep() {
1889
+ return this.memory.beforeStep;
1890
+ }
1891
+ buildMessages() {
1892
+ return this.projector.project({
1893
+ history: this.memory.getHistory(),
1894
+ systemPrompt: this.memory.systemPrompt,
1895
+ model: this.memory.model,
1896
+ activeTools: this.memory.activeTools
1897
+ });
1898
+ }
1899
+ async applySteerMessages() {
1900
+ const steers = this.memory.drainSteerMessages();
1901
+ if (steers.length === 0) return false;
1902
+ this.assertNotBroken();
1903
+ for (const steer of steers) await this.appendUserMessage(steer);
2009
1904
  return true;
2010
1905
  }
2011
- if (archives.length > 0) {
2012
- const currentPath = join(sessionDir, "wire.jsonl");
2013
- const lines = (await readFile(currentPath, "utf8")).trim().split("\n").filter((l) => l.length > 0);
2014
- if (lines.length === 1 || lines.length === 2) {
2015
- const parsedLines = lines.map((l) => {
2016
- try {
2017
- return JSON.parse(l);
2018
- } catch {
2019
- return null;
2020
- }
2021
- });
2022
- const isMetadata = parsedLines[0]?.["type"] === "metadata";
2023
- if (lines.length === 1 ? isMetadata : isMetadata && parsedLines[1]?.["type"] === "session_initialized") {
2024
- const highest = findHighestArchive(archives);
2025
- if (highest !== void 0) {
2026
- await unlink(currentPath);
2027
- await rename(join(sessionDir, highest.name), join(sessionDir, "wire.jsonl"));
2028
- return true;
2029
- }
2030
- }
1906
+ pushSteer(input) {
1907
+ this.memory.pushSteer(input);
1908
+ }
1909
+ setBeforeStepHook(fn) {
1910
+ this.memory.setBeforeStepHook(fn);
1911
+ }
1912
+ getHistory() {
1913
+ return this.memory.getHistory();
1914
+ }
1915
+ async appendUserMessage(input, turnIdOverride) {
1916
+ this.assertNotBroken();
1917
+ const turnId = turnIdOverride ?? this.currentTurnId();
1918
+ await this.journalWriter.append(buildUserMessageRecord(turnId, input));
1919
+ this.memory.appendUserContent(userInputToContentParts(input));
1920
+ }
1921
+ async appendToolResult(parentUuid, toolCallId, result) {
1922
+ this.assertNotBroken();
1923
+ const normalisedOutput = result.output === void 0 ? null : result.output;
1924
+ const turnId = this.currentTurnId();
1925
+ await this.journalWriter.append(buildToolResultRecord(turnId, parentUuid, toolCallId, normalisedOutput, result));
1926
+ this.memory.appendToolResult(toolCallId, toolResultOutputToContentParts(normalisedOutput));
1927
+ }
1928
+ async appendStepBegin(input) {
1929
+ this.assertNotBroken();
1930
+ await this.journalWriter.append(buildStepBeginRecord(input));
1931
+ this.memory.openStep(input.uuid);
1932
+ }
1933
+ async appendStepEnd(input) {
1934
+ this.assertNotBroken();
1935
+ await this.journalWriter.append(buildStepEndRecord(input));
1936
+ this.memory.closeStep(input.uuid, input.usage);
1937
+ }
1938
+ async appendContentPart(input) {
1939
+ this.assertNotBroken();
1940
+ if (this.memory.getOpenStep(input.stepUuid) === void 0) throw new Error(`appendContentPart: unknown stepUuid '${input.stepUuid}' (no open step_begin)`);
1941
+ await this.journalWriter.append(buildContentPartRecord(input));
1942
+ this.memory.appendOpenStepContent(input.stepUuid, atomicPartToContentPart(input.part));
1943
+ }
1944
+ async appendToolCall(input) {
1945
+ this.assertNotBroken();
1946
+ if (this.memory.getOpenStep(input.stepUuid) === void 0) throw new Error(`appendToolCall: unknown stepUuid '${input.stepUuid}' (no open step_begin)`);
1947
+ const data = sanitizeToolCallData(input.data);
1948
+ await this.journalWriter.append(buildToolCallRecord(input, data));
1949
+ this.memory.appendOpenStepToolCall(input.stepUuid, toolCallDataToKosongToolCall(input.data));
1950
+ }
1951
+ async appendNotification(data) {
1952
+ this.assertNotBroken();
1953
+ await this.journalWriter.append(buildNotificationRecord(data));
1954
+ const text = renderNotificationXml(data);
1955
+ this.memory.appendUserContent([{
1956
+ type: "text",
1957
+ text
1958
+ }]);
1959
+ }
1960
+ async appendSystemReminder(data) {
1961
+ this.assertNotBroken();
1962
+ await this.journalWriter.append(buildSystemReminderRecord(data));
1963
+ const text = `<system-reminder>\n${data.content}\n</system-reminder>`;
1964
+ this.memory.appendUserContent([{
1965
+ type: "text",
1966
+ text
1967
+ }]);
1968
+ }
1969
+ async clear() {
1970
+ this.assertNotBroken();
1971
+ await this.journalWriter.append(buildClearRecord());
1972
+ this.memory.clearConversation();
1973
+ }
1974
+ async applyConfigChange(event) {
1975
+ this.assertNotBroken();
1976
+ switch (event.type) {
1977
+ case "system_prompt_changed":
1978
+ await this.journalWriter.append(buildConfigChangeRecord(event));
1979
+ this.memory.setSystemPrompt(event.new_prompt);
1980
+ return;
1981
+ case "tools_changed":
1982
+ await this.journalWriter.append(buildConfigChangeRecord(event));
1983
+ this.memory.applyToolsChanged(event.operation, event.tools);
2031
1984
  }
2032
1985
  }
2033
- return false;
2034
- }
1986
+ setModel(model) {
1987
+ this.memory.setModel(model);
1988
+ }
1989
+ setThinkingLevel(level) {
1990
+ this.memory.setThinkingLevel(level);
1991
+ }
1992
+ async resetToSummary(summary) {
1993
+ this.assertNotBroken();
1994
+ await this.journalWriter.append(buildCompactionRecord(summary));
1995
+ this.memory.resetToSummary(summary);
1996
+ }
1997
+ };
1998
+ var WiredContextState = class extends BaseContextState {};
1999
+ var InMemoryContextState = class extends BaseContextState {
2000
+ constructor(opts) {
2001
+ super({
2002
+ ...opts,
2003
+ currentTurnId: opts.currentTurnId ?? (() => "embedded"),
2004
+ journalWriter: new NoopJournalWriter()
2005
+ });
2006
+ }
2007
+ };
2008
+ //#endregion
2009
+ //#region ../../packages/kimi-core/src/storage/state/session-journal.ts
2010
+ /**
2011
+ * Production `SessionJournal` implementation — delegates every append to the
2012
+ * shared `JournalWriter`, which in turn serialises to wire.jsonl.
2013
+ *
2014
+ * Does not mutate any conversation projection state; management records do
2015
+ * not change buildMessages().
2016
+ */
2017
+ var WiredSessionJournalImpl = class {
2018
+ constructor(journalWriter) {
2019
+ this.journalWriter = journalWriter;
2020
+ }
2021
+ async appendTurnBegin(data) {
2022
+ await this.journalWriter.append(data);
2023
+ }
2024
+ async appendTurnEnd(data) {
2025
+ await this.journalWriter.append(data);
2026
+ }
2027
+ async appendSkillInvoked(data) {
2028
+ await this.journalWriter.append(data);
2029
+ }
2030
+ async appendSkillCompleted(data) {
2031
+ await this.journalWriter.append(data);
2032
+ }
2033
+ async appendApprovalRequest(data) {
2034
+ await this.journalWriter.append(data);
2035
+ }
2036
+ async appendApprovalResponse(data) {
2037
+ await this.journalWriter.append(data);
2038
+ }
2039
+ async appendTeamMail(data) {
2040
+ await this.journalWriter.append(data);
2041
+ }
2042
+ async appendToolDenied(data) {
2043
+ await this.journalWriter.append(data);
2044
+ }
2045
+ async appendSubagentSpawned(data) {
2046
+ await this.journalWriter.append(data);
2047
+ }
2048
+ async appendSubagentCompleted(data) {
2049
+ await this.journalWriter.append(data);
2050
+ }
2051
+ async appendSubagentFailed(data) {
2052
+ await this.journalWriter.append(data);
2053
+ }
2054
+ async appendOwnershipChanged(data) {
2055
+ await this.journalWriter.append(data);
2056
+ }
2057
+ };
2035
2058
  //#endregion
2036
2059
  //#region ../../packages/kimi-core/src/storage/fs/atomic-write.ts
2037
2060
  /**
@@ -2599,10 +2622,8 @@ async function raceExecuteWithGraceTimeout(executePromise, signal, toolName) {
2599
2622
  * later abort during tool execution does not lose the LLM usage that was
2600
2623
  * already spent.
2601
2624
  */
2602
- const UNKNOWN_TURN_ID = "unknown-turn";
2603
2625
  async function executeSoulStep(deps) {
2604
- const { config, context, runtime, signal, overrides, currentStep, emit, recordUsage } = deps;
2605
- const turnId = context.currentTurnId?.() ?? UNKNOWN_TURN_ID;
2626
+ const { config, context, turnId, runtime, signal, overrides, currentStep, emit, recordUsage } = deps;
2606
2627
  if (config.beforeStep !== void 0) {
2607
2628
  const beforeStep = await config.beforeStep({
2608
2629
  turnId,
@@ -2700,6 +2721,7 @@ function toStepEndUsage(usage) {
2700
2721
  //#endregion
2701
2722
  //#region ../../packages/kimi-core/src/soul/run-turn.ts
2702
2723
  const DEFAULT_MAX_STEPS = 100;
2724
+ const UNKNOWN_TURN_ID = "unknown-turn";
2703
2725
  async function runSoulTurn(config, context, runtime, sink, signal, overrides) {
2704
2726
  const maxSteps = config.maxSteps ?? DEFAULT_MAX_STEPS;
2705
2727
  const usage = {
@@ -2710,6 +2732,7 @@ async function runSoulTurn(config, context, runtime, sink, signal, overrides) {
2710
2732
  };
2711
2733
  let steps = 0;
2712
2734
  let stopReason = "end_turn";
2735
+ const turnId = context.currentTurnId?.() ?? UNKNOWN_TURN_ID;
2713
2736
  try {
2714
2737
  while (true) {
2715
2738
  signal.throwIfAborted();
@@ -2721,6 +2744,7 @@ async function runSoulTurn(config, context, runtime, sink, signal, overrides) {
2721
2744
  steps += 1;
2722
2745
  const stepResult = await executeSoulStep({
2723
2746
  config,
2747
+ turnId,
2724
2748
  context,
2725
2749
  runtime,
2726
2750
  signal,
@@ -2735,7 +2759,12 @@ async function runSoulTurn(config, context, runtime, sink, signal, overrides) {
2735
2759
  });
2736
2760
  if (stepResult.stopReason === "tool_use") continue;
2737
2761
  stopReason = stepResult.stopReason;
2738
- if (!(await config.beforeTurnCompletes?.())?.continue) break;
2762
+ if (!(await config.beforeTurnCompletes?.({
2763
+ ...stepResult,
2764
+ turnId,
2765
+ stepNumber: steps,
2766
+ context
2767
+ }))?.continue) break;
2739
2768
  }
2740
2769
  } catch (error) {
2741
2770
  if (isAbortError$3(error) || signal.aborted) {
@@ -3065,6 +3094,25 @@ var SubagentTooDeepError = class extends Error {
3065
3094
  this.limit = limit;
3066
3095
  }
3067
3096
  };
3097
+ function classifyBusinessError(error) {
3098
+ if (error instanceof LLMNotSetError) return {
3099
+ code: -32001,
3100
+ message: error.message
3101
+ };
3102
+ if (error instanceof LLMCapabilityMismatchError) return {
3103
+ code: -32002,
3104
+ message: error.message
3105
+ };
3106
+ if (error instanceof ProviderError) return {
3107
+ code: -32003,
3108
+ message: error.message
3109
+ };
3110
+ if (error instanceof Error && /provider|backend|upstream/i.test(error.message)) return {
3111
+ code: -32003,
3112
+ message: error.message
3113
+ };
3114
+ return null;
3115
+ }
3068
3116
  //#endregion
3069
3117
  //#region ../../packages/kimi-core/src/soul-plus/soul-registry.ts
3070
3118
  /**
@@ -5721,14 +5769,19 @@ function registerInteractiveTools(options) {
5721
5769
  * precomputed `tokenCountWithPending` off `SoulContextState`.
5722
5770
  */
5723
5771
  /**
5724
- * Estimate token count from text using a character-based heuristic
5725
- * (~4 chars per token for English; underestimates for CJK). The
5726
- * estimate is transient the next LLM call returns the real count
5772
+ * Estimate token count from text using a character-based heuristic.
5773
+ * - ASCII (~4 chars per token)
5774
+ * - CJK and other non-ASCII (~1 char per token)
5775
+ * The estimate is transient — the next LLM call returns the real count
5727
5776
  * and supersedes this value. Used to keep `tokenCountWithPending`
5728
5777
  * monotonic between LLM round-trips without paying for a tokenizer.
5729
5778
  */
5730
5779
  function estimateTokens(text) {
5731
- return Math.ceil(text.length / 4);
5780
+ let asciiCount = 0;
5781
+ let nonAsciiCount = 0;
5782
+ for (const char of text) if (char.codePointAt(0) <= 127) asciiCount++;
5783
+ else nonAsciiCount++;
5784
+ return Math.ceil(asciiCount / 4) + nonAsciiCount;
5732
5785
  }
5733
5786
  //#endregion
5734
5787
  //#region ../../packages/kimi-core/src/soul-plus/compaction/compaction-orchestrator.ts
@@ -5744,7 +5797,7 @@ var CompactionOrchestrator = class {
5744
5797
  * 2. emit compaction.begin
5745
5798
  * 3. provider.run(messages, signal, {userInstructions?})
5746
5799
  * 4. journalWriter.flush() (Phase 3 铁律 — before rotate)
5747
- * 5. journalCapability.rotate(...)
5800
+ * 5. journalWriter.rotate(...)
5748
5801
  * 6. contextState.resetToSummary(storageSummary)
5749
5802
  * 7. emit compaction.end
5750
5803
  * 8. finally: transitionTo('active')
@@ -5758,6 +5811,8 @@ var CompactionOrchestrator = class {
5758
5811
  * CompactionRecord.
5759
5812
  */
5760
5813
  async executeCompaction(signal, customInstruction, trigger = "auto") {
5814
+ const rotateCapability = this.deps.journalWriter.rotateCapability;
5815
+ if (!rotateCapability) throw new Error("JournalWriter missing rotate capability");
5761
5816
  const machine = this.deps.lifecycleStateMachine;
5762
5817
  machine.transitionTo("compacting");
5763
5818
  let tailUserText;
@@ -5768,22 +5823,17 @@ var CompactionOrchestrator = class {
5768
5823
  const preCompactTokens = this.deps.contextState.tokenCountWithPending;
5769
5824
  const summary = await (this.deps.runtimeSlot?.current().compactionProvider ?? this.deps.compactionProvider).run(messages, signal, customInstruction !== void 0 ? { userInstructions: customInstruction } : void 0);
5770
5825
  signal.throwIfAborted();
5771
- const baselineInit = await this.deps.journalCapability.readSessionInitialized();
5826
+ const baselineInit = await rotateCapability.readSessionInitialized();
5772
5827
  const cs = this.deps.contextState;
5773
5828
  const sessionInitialized = applyRuntimeOverlay(baselineInit, {
5774
5829
  system_prompt: cs.systemPrompt,
5775
5830
  active_tools: [...cs.activeTools]
5776
5831
  });
5777
- await this.deps.journalWriter.flush();
5778
- const rotateResult = await this.deps.journalCapability.rotate({
5779
- type: "compaction_boundary",
5780
- summary,
5781
- parent_file: ""
5782
- });
5783
- await this.deps.journalCapability.appendBoundary(sessionInitialized);
5784
- const storageSummary = bridgeSummaryMessage(summary, messages.length, preCompactTokens, trigger, rotateResult.archiveFile);
5785
- await this.deps.contextState.resetToSummary(storageSummary);
5832
+ const rotateResult = await rotateCapability.rotate();
5833
+ await rotateCapability.appendBoundary(sessionInitialized);
5786
5834
  tailUserText = extractUnpairedTailUserText(messages);
5835
+ const storageSummary = bridgeSummaryMessage(summary, messages.length, preCompactTokens, trigger, rotateResult.archivePath, this.deps.contextState.systemPrompt, tailUserText);
5836
+ await this.deps.contextState.resetToSummary(storageSummary);
5787
5837
  this.deps.sink.emit({
5788
5838
  type: "compaction.end",
5789
5839
  tokensBefore: preCompactTokens,
@@ -5887,7 +5937,10 @@ function extractUnpairedTailUserText(messages) {
5887
5937
  return parts.length > 0 ? parts.join("") : void 0;
5888
5938
  }
5889
5939
  }
5890
- function bridgeSummaryMessage(providerSummary, messagesCount, preCompactTokens, trigger, archiveFile) {
5940
+ function bridgeSummaryMessage(providerSummary, messagesCount, preCompactTokens, trigger, archivePath, systemPrompt, tailUserText) {
5941
+ const summaryTokens = estimateTokens(providerSummary.content);
5942
+ const systemTokens = systemPrompt ? estimateTokens(systemPrompt) : 0;
5943
+ const tailTokens = tailUserText ? estimateTokens(tailUserText) : 0;
5891
5944
  return {
5892
5945
  summary: providerSummary.content,
5893
5946
  compactedRange: {
@@ -5896,9 +5949,9 @@ function bridgeSummaryMessage(providerSummary, messagesCount, preCompactTokens,
5896
5949
  messageCount: messagesCount
5897
5950
  },
5898
5951
  preCompactTokens,
5899
- postCompactTokens: estimateTokens(providerSummary.content),
5952
+ postCompactTokens: summaryTokens + systemTokens + tailTokens,
5900
5953
  trigger,
5901
- archiveFile
5954
+ archivePath
5902
5955
  };
5903
5956
  }
5904
5957
  //#endregion
@@ -5924,7 +5977,6 @@ function createSoulPlusCompactionOrchestrator(deps) {
5924
5977
  compactionProvider: deps.compactionProvider,
5925
5978
  runtimeSlot: deps.runtimeSlot,
5926
5979
  lifecycleStateMachine: deps.lifecycleStateMachine,
5927
- journalCapability: deps.journalCapability,
5928
5980
  sink: deps.sink,
5929
5981
  journalWriter: deps.journalWriter,
5930
5982
  sessionId: deps.sessionId,
@@ -6922,53 +6974,84 @@ function toWirePatch(p) {
6922
6974
  return out;
6923
6975
  }
6924
6976
  //#endregion
6925
- //#region ../../packages/kimi-core/src/soul-plus/turn/dynamic-injection.ts
6926
- var DynamicInjectionManager = class {
6927
- providers = [];
6928
- onProviderError;
6929
- constructor(options = {}) {
6930
- if (options.initialProviders !== void 0) for (const provider of options.initialProviders) this.register(provider);
6931
- this.onProviderError = options.onProviderError;
6932
- }
6933
- /**
6934
- * Register a provider. Registration is idempotent on `id`: a second
6935
- * register with the same id replaces the previous entry in place.
6936
- * This keeps host-side re-initialisation (e.g. SessionManager resume)
6937
- * from double-injecting a built-in reminder.
6938
- */
6939
- register(provider) {
6940
- const existing = this.providers.findIndex((p) => p.id === provider.id);
6941
- if (existing === -1) {
6942
- this.providers.push(provider);
6943
- return;
6977
+ //#region ../../packages/kimi-core/src/soul-plus/injection/types.ts
6978
+ /**
6979
+ * Extract the text content from a history message.
6980
+ */
6981
+ function historyMessageText(msg) {
6982
+ return msg.content.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
6983
+ }
6984
+ /**
6985
+ * Scan history backwards and classify the plan-mode dedup state.
6986
+ *
6987
+ * The scan stops at the first `user` message it encounters going
6988
+ * backwards:
6989
+ * - If that user message is a plan-mode reminder → returns
6990
+ * `{found:true, newUserSinceReminder:false, assistantTurnsSince}`.
6991
+ * The caller decides dedup vs sparse vs full by consulting the
6992
+ * assistant-turn counter.
6993
+ * - If that user message is a real user prompt (not a reminder)
6994
+ * returns `{found:false, newUserSinceReminder:true, …}`. The caller
6995
+ * treats this as a fresh turn and emits the full reminder.
6996
+ * - If no user message exists at all → returns
6997
+ * `{found:false, newUserSinceReminder:false, …}`. First-time
6998
+ * injection.
6999
+ *
7000
+ * Only counts ASSISTANT messages between the tail and the reminder.
7001
+ * Non-reminder user messages never appear inside that window because
7002
+ * the scan stops at them.
7003
+ */
7004
+ function scanPlanReminderHistory(history) {
7005
+ let assistantTurnsSince = 0;
7006
+ for (let i = history.length - 1; i >= 0; i--) {
7007
+ const msg = history[i];
7008
+ if (msg === void 0) continue;
7009
+ if (msg.role === "assistant") {
7010
+ assistantTurnsSince += 1;
7011
+ continue;
6944
7012
  }
6945
- this.providers[existing] = provider;
6946
- }
6947
- unregister(id) {
6948
- const idx = this.providers.findIndex((p) => p.id === id);
6949
- if (idx !== -1) this.providers.splice(idx, 1);
6950
- }
6951
- /** Read-only view of registered providers. Primarily for tests. */
6952
- list() {
6953
- return this.providers;
6954
- }
6955
- /**
6956
- * Collect every active injection for the upcoming LLM step. Providers
6957
- * are iterated in registration order; a provider that throws is
6958
- * isolated (error forwarded to `onProviderError`, remaining providers
6959
- * still run). Same error-isolation pattern as HookEngine / SessionEventBus.
6960
- */
6961
- computeInjections(ctx, contextState) {
6962
- const out = [];
6963
- for (const provider of this.providers) try {
6964
- const injections = provider.getInjections(ctx, contextState);
6965
- if (injections !== void 0 && injections !== null && Array.isArray(injections) && injections.length > 0) out.push(...injections);
6966
- } catch (error) {
6967
- this.onProviderError?.(provider, error instanceof Error ? error : new Error(String(error)));
7013
+ if (msg.role === "user") {
7014
+ if (isPlanReminderMessage(msg)) return {
7015
+ found: true,
7016
+ newUserSinceReminder: false,
7017
+ assistantTurnsSince
7018
+ };
7019
+ return {
7020
+ found: false,
7021
+ newUserSinceReminder: true,
7022
+ assistantTurnsSince
7023
+ };
6968
7024
  }
6969
- return out;
6970
7025
  }
6971
- };
7026
+ return {
7027
+ found: false,
7028
+ newUserSinceReminder: false,
7029
+ assistantTurnsSince
7030
+ };
7031
+ }
7032
+ function isPlanReminderMessage(msg) {
7033
+ const text = historyMessageText(msg);
7034
+ if (!text.trimStart().startsWith("<system-reminder>")) return false;
7035
+ return text.includes("Plan mode is active") || text.includes("Plan mode still active") || text.includes("Re-entering Plan Mode");
7036
+ }
7037
+ /**
7038
+ * Same as above but for yolo mode reminder.
7039
+ */
7040
+ function hasYoloReminderWithoutNewUser(history) {
7041
+ return hasReminderWithoutNewUser(history, "yolo");
7042
+ }
7043
+ function hasReminderWithoutNewUser(history, fingerprint) {
7044
+ for (let i = history.length - 1; i >= 0; i--) {
7045
+ const msg = history[i];
7046
+ if (msg === void 0) continue;
7047
+ const text = historyMessageText(msg);
7048
+ if (msg.role === "user" && text.trimStart().startsWith("<system-reminder>") && text.toLowerCase().includes(fingerprint.toLowerCase())) return true;
7049
+ if (msg.role === "user" && !text.trimStart().startsWith("<system-reminder>") && !text.trimStart().startsWith("<notification")) return false;
7050
+ }
7051
+ return false;
7052
+ }
7053
+ //#endregion
7054
+ //#region ../../packages/kimi-core/src/soul-plus/injection/plan-mode.ts
6972
7055
  /**
6973
7056
  * Phase 18 §D.6 — reminder cadence constants.
6974
7057
  *
@@ -7033,7 +7116,7 @@ var PlanModeInjectionProvider = class {
7033
7116
  if (scan.assistantTurnsSince >= PLAN_MODE_DEDUP_MIN_TURNS) return "sparse";
7034
7117
  return null;
7035
7118
  }
7036
- getInjections(ctx, contextState) {
7119
+ getInjections(ctx) {
7037
7120
  if (!ctx.planMode) {
7038
7121
  this.pendingReentry = false;
7039
7122
  return [];
@@ -7041,23 +7124,19 @@ var PlanModeInjectionProvider = class {
7041
7124
  const planFilePath = ctx.planFilePath;
7042
7125
  if (this.pendingReentry) {
7043
7126
  this.pendingReentry = false;
7044
- return emit$1(contextState, reentryReminder(planFilePath));
7127
+ return [{
7128
+ kind: "system_reminder",
7129
+ content: reentryReminder(planFilePath)
7130
+ }];
7045
7131
  }
7046
7132
  const variant = this.getVariant(ctx);
7047
7133
  if (variant === null) return [];
7048
- return emit$1(contextState, variant === "full" ? fullReminder(planFilePath) : variant === "sparse" ? sparseReminder(planFilePath) : reentryReminder(planFilePath));
7134
+ return [{
7135
+ kind: "system_reminder",
7136
+ content: variant === "full" ? fullReminder(planFilePath) : variant === "sparse" ? sparseReminder(planFilePath) : reentryReminder(planFilePath)
7137
+ }];
7049
7138
  }
7050
7139
  };
7051
- function emit$1(contextState, content) {
7052
- if (contextState !== void 0) {
7053
- contextState.appendSystemReminder({ content });
7054
- return;
7055
- }
7056
- return [{
7057
- kind: "system_reminder",
7058
- content
7059
- }];
7060
- }
7061
7140
  /**
7062
7141
  * Phase 18 §D.7 — when the host has resolved the session's plan-file
7063
7142
  * path, append a `Plan file: {path}` footer so the LLM can target
@@ -7166,6 +7245,13 @@ function inlineReentryReminder() {
7166
7245
  "Your turn must end with either AskUserQuestion (to clarify requirements) or ExitPlanMode (to request plan approval)."
7167
7246
  ].join("\n");
7168
7247
  }
7248
+ //#endregion
7249
+ //#region ../../packages/kimi-core/src/soul-plus/injection/yolo-mode.ts
7250
+ const YOLO_MODE_REMINDER = [
7251
+ "You are running in non-interactive (yolo) mode. The user cannot answer questions or provide feedback during execution.",
7252
+ " - Do NOT call AskUserQuestion. If you need to make a decision, make your best judgment and proceed.",
7253
+ " - For ExitPlanMode, it will be auto-approved. You can use it normally but expect no user feedback."
7254
+ ].join("\n");
7169
7255
  /**
7170
7256
  * Yolo-mode reminder ported from Python `yolo_mode.py`. One-shot per
7171
7257
  * activation: the first turn after `permissionMode` flips to
@@ -7176,7 +7262,7 @@ function inlineReentryReminder() {
7176
7262
  var YoloModeInjectionProvider = class {
7177
7263
  id = "yolo_mode";
7178
7264
  injected = false;
7179
- getInjections(ctx, contextState) {
7265
+ getInjections(ctx) {
7180
7266
  if (ctx.permissionMode !== "bypassPermissions") {
7181
7267
  this.injected = false;
7182
7268
  return [];
@@ -7184,101 +7270,62 @@ var YoloModeInjectionProvider = class {
7184
7270
  if (ctx.history !== void 0 && hasYoloReminderWithoutNewUser(ctx.history)) return [];
7185
7271
  if (this.injected) return [];
7186
7272
  this.injected = true;
7187
- if (contextState !== void 0) {
7188
- contextState.appendSystemReminder({ content: YOLO_MODE_REMINDER });
7189
- return;
7190
- }
7191
7273
  return [{
7192
7274
  kind: "system_reminder",
7193
7275
  content: YOLO_MODE_REMINDER
7194
7276
  }];
7195
7277
  }
7196
7278
  };
7197
- const YOLO_MODE_REMINDER = [
7198
- "You are running in non-interactive (yolo) mode. The user cannot answer questions or provide feedback during execution.",
7199
- " - Do NOT call AskUserQuestion. If you need to make a decision, make your best judgment and proceed.",
7200
- " - For ExitPlanMode, it will be auto-approved. You can use it normally but expect no user feedback."
7201
- ].join("\n");
7202
- /**
7203
- * Extract the text content from a history message.
7204
- */
7205
- function historyMessageText(msg) {
7206
- return msg.content.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
7207
- }
7208
- /**
7209
- * Scan history backwards for the most recent `<system-reminder>` that
7210
- * contains the plan mode fingerprint. If found and no new user message
7211
- * has appeared since, return true (dedup hit).
7212
- */
7213
- /**
7214
- * Scan history backwards and classify the plan-mode dedup state.
7215
- *
7216
- * The scan stops at the first `user` message it encounters going
7217
- * backwards:
7218
- * - If that user message is a plan-mode reminder → returns
7219
- * `{found:true, newUserSinceReminder:false, assistantTurnsSince}`.
7220
- * The caller decides dedup vs sparse vs full by consulting the
7221
- * assistant-turn counter.
7222
- * - If that user message is a real user prompt (not a reminder) →
7223
- * returns `{found:false, newUserSinceReminder:true, …}`. The caller
7224
- * treats this as a fresh turn and emits the full reminder.
7225
- * - If no user message exists at all → returns
7226
- * `{found:false, newUserSinceReminder:false, …}`. First-time
7227
- * injection.
7228
- *
7229
- * Only counts ASSISTANT messages between the tail and the reminder.
7230
- * Non-reminder user messages never appear inside that window because
7231
- * the scan stops at them.
7232
- */
7233
- function scanPlanReminderHistory(history) {
7234
- let assistantTurnsSince = 0;
7235
- for (let i = history.length - 1; i >= 0; i--) {
7236
- const msg = history[i];
7237
- if (msg === void 0) continue;
7238
- if (msg.role === "assistant") {
7239
- assistantTurnsSince += 1;
7240
- continue;
7241
- }
7242
- if (msg.role === "user") {
7243
- if (isPlanReminderMessage(msg)) return {
7244
- found: true,
7245
- newUserSinceReminder: false,
7246
- assistantTurnsSince
7247
- };
7248
- return {
7249
- found: false,
7250
- newUserSinceReminder: true,
7251
- assistantTurnsSince
7252
- };
7279
+ //#endregion
7280
+ //#region ../../packages/kimi-core/src/soul-plus/injection/manager.ts
7281
+ var DynamicInjectionManager = class {
7282
+ providers = [];
7283
+ onProviderError;
7284
+ constructor(options = {}) {
7285
+ if (options.initialProviders !== void 0) for (const provider of options.initialProviders) this.register(provider);
7286
+ this.onProviderError = options.onProviderError;
7287
+ }
7288
+ /**
7289
+ * Register a provider. Registration is idempotent on `id`: a second
7290
+ * register with the same id replaces the previous entry in place.
7291
+ * This keeps host-side re-initialisation (e.g. SessionManager resume)
7292
+ * from double-injecting a built-in reminder.
7293
+ */
7294
+ register(provider) {
7295
+ const existing = this.providers.findIndex((p) => p.id === provider.id);
7296
+ if (existing === -1) {
7297
+ this.providers.push(provider);
7298
+ return;
7253
7299
  }
7300
+ this.providers[existing] = provider;
7254
7301
  }
7255
- return {
7256
- found: false,
7257
- newUserSinceReminder: false,
7258
- assistantTurnsSince
7259
- };
7260
- }
7261
- function isPlanReminderMessage(msg) {
7262
- const text = historyMessageText(msg);
7263
- if (!text.trimStart().startsWith("<system-reminder>")) return false;
7264
- return text.includes("Plan mode is active") || text.includes("Plan mode still active") || text.includes("Re-entering Plan Mode");
7265
- }
7266
- /**
7267
- * Same as above but for yolo mode reminder.
7268
- */
7269
- function hasYoloReminderWithoutNewUser(history) {
7270
- return hasReminderWithoutNewUser(history, "yolo");
7271
- }
7272
- function hasReminderWithoutNewUser(history, fingerprint) {
7273
- for (let i = history.length - 1; i >= 0; i--) {
7274
- const msg = history[i];
7275
- if (msg === void 0) continue;
7276
- const text = historyMessageText(msg);
7277
- if (msg.role === "user" && text.trimStart().startsWith("<system-reminder>") && text.toLowerCase().includes(fingerprint.toLowerCase())) return true;
7278
- if (msg.role === "user" && !text.trimStart().startsWith("<system-reminder>") && !text.trimStart().startsWith("<notification")) return false;
7302
+ unregister(id) {
7303
+ const idx = this.providers.findIndex((p) => p.id === id);
7304
+ if (idx !== -1) this.providers.splice(idx, 1);
7279
7305
  }
7280
- return false;
7281
- }
7306
+ /** Read-only view of registered providers. Primarily for tests. */
7307
+ list() {
7308
+ return this.providers;
7309
+ }
7310
+ /**
7311
+ * Collect every active injection for the upcoming LLM step. Providers
7312
+ * are iterated in registration order; a provider that throws is
7313
+ * isolated (error forwarded to `onProviderError`, remaining providers
7314
+ * still run). Same error-isolation pattern as HookEngine / SessionEventBus.
7315
+ */
7316
+ computeInjections(ctx, contextState) {
7317
+ const out = [];
7318
+ for (const provider of this.providers) try {
7319
+ const injections = provider.getInjections(ctx);
7320
+ if (injections !== void 0 && injections !== null && Array.isArray(injections) && injections.length > 0) if (contextState !== void 0) for (const injection of injections) if (injection.kind === "system_reminder" && typeof injection.content === "string") contextState.appendSystemReminder({ content: injection.content });
7321
+ else throw new Error(`Unsupported injection kind "${injection.kind}" from provider "${provider.id}"`);
7322
+ else out.push(...injections);
7323
+ } catch (error) {
7324
+ this.onProviderError?.(provider, error instanceof Error ? error : new Error(String(error)));
7325
+ }
7326
+ return out;
7327
+ }
7328
+ };
7282
7329
  /**
7283
7330
  * Build a DynamicInjectionManager pre-populated with the two Phase 3
7284
7331
  * built-in providers (plan-mode + yolo-mode). Hosts that want additional
@@ -7442,19 +7489,25 @@ function extractText(message, sep = "") {
7442
7489
  }
7443
7490
  //#endregion
7444
7491
  //#region ../../packages/kosong/src/capability.ts
7492
+ const UNKNOWN_CAPABILITY_MARKER = Symbol.for("moonshot-ai.kosong.UNKNOWN_CAPABILITY");
7445
7493
  /**
7446
7494
  * Shared read-only default returned when a provider has not catalogued a
7447
7495
  * given model. Frozen so accidental mutation at one call site cannot leak
7448
7496
  * into another.
7449
7497
  */
7450
- const UNKNOWN_CAPABILITY = Object.freeze({
7498
+ const UNKNOWN_CAPABILITY = Object.freeze(Object.defineProperty({
7451
7499
  image_in: false,
7452
7500
  video_in: false,
7453
7501
  audio_in: false,
7454
7502
  thinking: false,
7455
7503
  tool_use: false,
7456
7504
  max_context_tokens: 0
7457
- });
7505
+ }, UNKNOWN_CAPABILITY_MARKER, { value: true }));
7506
+ function isUnknownCapability(capability) {
7507
+ if (capability === UNKNOWN_CAPABILITY) return true;
7508
+ if (capability[UNKNOWN_CAPABILITY_MARKER] === true) return true;
7509
+ return capability.image_in === false && capability.video_in === false && capability.audio_in === false && capability.thinking === false && capability.tool_use === false && capability.max_context_tokens === 0;
7510
+ }
7458
7511
  //#endregion
7459
7512
  //#region ../../packages/kosong/src/errors.ts
7460
7513
  /**
@@ -20511,6 +20564,17 @@ function zeroUsage() {
20511
20564
  output: 0
20512
20565
  };
20513
20566
  }
20567
+ function configuredCapabilitiesToModelCapability(capabilities, maxContextTokens) {
20568
+ const caps = new Set(capabilities.map((cap) => cap.toLowerCase()));
20569
+ return {
20570
+ image_in: caps.has("image_in"),
20571
+ video_in: caps.has("video_in"),
20572
+ audio_in: caps.has("audio_in"),
20573
+ thinking: caps.has("thinking") || caps.has("always_thinking"),
20574
+ tool_use: caps.has("tool_use"),
20575
+ max_context_tokens: maxContextTokens
20576
+ };
20577
+ }
20514
20578
  function mergeTurnRules(sessionRules, pendingOverrides) {
20515
20579
  if (pendingOverrides === void 0) return sessionRules;
20516
20580
  const turnRules = [];
@@ -20771,8 +20835,12 @@ var TurnManager = class {
20771
20835
  async handlePrompt(req) {
20772
20836
  if (!this.deps.lifecycleStateMachine.isIdle() || this.getCurrentTurnId() !== void 0) return { error: "agent_busy" };
20773
20837
  const input = req.data.input;
20774
- const capability = ((this.runtimeSlot?.current())?.runtime ?? this.runtime).kosong.getCapability?.(this.deps.contextState.model);
20775
- if (capability !== void 0 && capability !== UNKNOWN_CAPABILITY) {
20838
+ const runtimeBundle = this.runtimeSlot?.current();
20839
+ const runtime = runtimeBundle?.runtime ?? this.runtime;
20840
+ const configuredCapabilities = runtimeBundle !== void 0 ? runtimeBundle.modelCapabilities : this.deps.modelCapabilities;
20841
+ const capability = configuredCapabilities !== void 0 ? configuredCapabilitiesToModelCapability(configuredCapabilities, runtimeBundle?.compactionConfig?.maxContextSize ?? this.compactionConfig?.maxContextSize ?? 0) : runtime.kosong.getCapability?.();
20842
+ const capabilityIsAuthoritative = configuredCapabilities !== void 0 || capability !== void 0 && !isUnknownCapability(capability);
20843
+ if (capability !== void 0 && capabilityIsAuthoritative) {
20776
20844
  let inputContainsImage = false;
20777
20845
  let inputContainsVideo = false;
20778
20846
  for (const part of input.parts ?? []) if (part.type === "image_url") inputContainsImage = true;
@@ -21211,7 +21279,6 @@ var SoulPlus = class {
21211
21279
  const contextState = deps.contextState;
21212
21280
  const sessionJournal = deps.sessionJournal;
21213
21281
  const journalWriter = contextState.journalWriter;
21214
- const journalCapability = deps.journalCapability;
21215
21282
  const runtimeSlot = deps.runtimeSlot ?? createSessionRuntimeSlot({
21216
21283
  model: contextState.model,
21217
21284
  runtime: deps.runtime,
@@ -21239,7 +21306,6 @@ var SoulPlus = class {
21239
21306
  compactionProvider,
21240
21307
  runtimeSlot,
21241
21308
  lifecycleStateMachine: stateMachine,
21242
- journalCapability,
21243
21309
  sink: eventBus,
21244
21310
  journalWriter,
21245
21311
  sessionId: deps.sessionId,
@@ -21307,7 +21373,8 @@ var SoulPlus = class {
21307
21373
  toolExecutionScopeFactory,
21308
21374
  approvalRuntime,
21309
21375
  hookEngine: deps.hookEngine,
21310
- compactionConfig: deps.compactionConfig
21376
+ compactionConfig: deps.compactionConfig,
21377
+ modelCapabilities: deps.modelCapabilities
21311
21378
  });
21312
21379
  turnManagerRef.bind(turnManager);
21313
21380
  const setPlanMode = createPlanModeSetter({
@@ -21340,8 +21407,7 @@ var SoulPlus = class {
21340
21407
  this.journal = {
21341
21408
  writer: journalWriter,
21342
21409
  contextState,
21343
- sessionJournal,
21344
- capability: journalCapability
21410
+ sessionJournal
21345
21411
  };
21346
21412
  this.services = {
21347
21413
  approvalRuntime,
@@ -21964,17 +22030,42 @@ const COMPACTION_SYSTEM_PROMPT = "You are a helpful assistant that compacts conv
21964
22030
  /**
21965
22031
  * Build the compaction prompt from conversation messages.
21966
22032
  *
21967
- * Each message is formatted as a numbered block with its role and text
21968
- * content. Non-text content (images, tool calls, etc.) is omitted —
21969
- * the summary is text-only, matching the Python implementation.
22033
+ * Mirrors Python's `SimpleCompaction.prepare()` serialization:
22034
+ * - Each message becomes a block: `## Message N\nRole: X\nContent:\n<text>`
22035
+ * - Only TextPart is retained; think / image / audio / video are dropped.
22036
+ * - Tool results are folded into the assistant message that issued the
22037
+ * call, so the summary sees both the invocation and its result.
22038
+ * - Message blocks are concatenated without separators, matching Python's
22039
+ * `compact_message.content.extend()` behaviour.
22040
+ * - The instruction suffix is joined with a single `\n` to match Python's
22041
+ * `prompt_text = "\n" + prompts.COMPACT`.
21970
22042
  */
21971
22043
  function buildCompactionPrompt(messages, userInstructions) {
22044
+ const toolResultsById = /* @__PURE__ */ new Map();
22045
+ for (const msg of messages) if (msg.role === "tool" && msg.toolCallId) {
22046
+ const text = msg.content.filter((p) => p.type === "text").map((p) => p.text).join("");
22047
+ toolResultsById.set(msg.toolCallId, text);
22048
+ }
21972
22049
  const parts = [];
21973
- for (const [i, msg] of messages.entries()) {
21974
- const textParts = msg.content.filter((p) => p.type === "text").map((p) => p.text);
21975
- if (textParts.length > 0) parts.push(`## Message ${i + 1}\nRole: ${msg.role}\nContent:\n${textParts.join("\n")}`);
22050
+ let seq = 1;
22051
+ for (const msg of messages) {
22052
+ if (msg.role === "tool") continue;
22053
+ const body = msg.content.filter((p) => p.type === "text").map((p) => p.text).join("");
22054
+ let block = `## Message ${seq}\nRole: ${msg.role}\nContent:\n${body}`;
22055
+ if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) for (const tc of msg.toolCalls) {
22056
+ let argsDisplay = tc.function.arguments ?? "";
22057
+ try {
22058
+ const parsed = JSON.parse(argsDisplay);
22059
+ argsDisplay = JSON.stringify(parsed);
22060
+ } catch {}
22061
+ const result = toolResultsById.get(tc.id);
22062
+ const resultStr = result !== void 0 ? ` ->\n${result}` : "";
22063
+ block += `\n[Tool: ${tc.function.name}(${argsDisplay})]${resultStr}`;
22064
+ }
22065
+ parts.push((block.endsWith("\n") ? block : block + "\n") + "\n");
22066
+ seq++;
21976
22067
  }
21977
- let prompt = parts.join("\n\n") + "\n\nSummarize the above conversation. Preserve:\n- Key decisions and their reasoning\n- Important code snippets, file paths, and technical details\n- Action items and their status\n- Any constraints or requirements mentioned\n\nBe concise but thorough. Do not lose critical context.";
22068
+ let prompt = parts.join("") + "\nSummarize the above conversation. Preserve:\n- Key decisions and their reasoning\n- Important code snippets, file paths, and technical details\n- Action items and their status\n- Any constraints or requirements mentioned\n\nBe concise but thorough. Do not lose critical context.";
21978
22069
  if (userInstructions !== void 0 && userInstructions.length > 0) prompt += "\n\n**User's Custom Compaction Instruction:**\nThe user has specifically requested the following focus during compaction. You MUST prioritize this instruction above the default compression priorities:\n" + userInstructions;
21979
22070
  return prompt;
21980
22071
  }
@@ -21993,7 +22084,7 @@ var KosongCompactionProvider = class {
21993
22084
  toolCalls: []
21994
22085
  };
21995
22086
  return {
21996
- content: (await generate$1(this.provider, COMPACTION_SYSTEM_PROMPT, [], [compactionMessage], void 0, { signal })).message.content.filter((p) => p.type === "text").map((p) => p.text).join("\n"),
22087
+ content: (await generate$1(this.provider, COMPACTION_SYSTEM_PROMPT, [], [compactionMessage], void 0, { signal })).message.content.filter((p) => p.type === "text").map((p) => p.text).join(""),
21997
22088
  original_turn_count: messages.length
21998
22089
  };
21999
22090
  }
@@ -22002,48 +22093,6 @@ function createKosongCompactionProvider(provider) {
22002
22093
  return new KosongCompactionProvider(provider);
22003
22094
  }
22004
22095
  //#endregion
22005
- //#region ../../packages/kimi-core/src/soul-plus/compaction/journal-capability.ts
22006
- /**
22007
- * WiredJournalCapability — real JournalCapability backed by physical
22008
- * wire.jsonl rotation (Slice 3.3 / M05).
22009
- *
22010
- * Replaces `createStubJournalCapability()` from Slice 3 for production
22011
- * use. Wraps `rotateJournal()` from `src/storage/compaction.ts` and
22012
- * coordinates with `WiredJournalWriter.resetForRotation()` so the
22013
- * journal's monotonic seq counter restarts at 0 in the new file.
22014
- */
22015
- var WiredJournalCapability = class {
22016
- sessionDir;
22017
- journalWriter;
22018
- producer;
22019
- constructor(deps) {
22020
- this.sessionDir = deps.sessionDir;
22021
- this.journalWriter = deps.journalWriter;
22022
- this.producer = deps.producer;
22023
- }
22024
- async rotate(_boundaryRecord) {
22025
- const result = await rotateJournal(this.sessionDir, { producer: this.producer });
22026
- this.journalWriter.resetForRotation();
22027
- return { archiveFile: basename(result.archivePath) };
22028
- }
22029
- async readSessionInitialized() {
22030
- const wirePath = join(this.sessionDir, "wire.jsonl");
22031
- const lines = (await readFile(wirePath, "utf-8")).split("\n").filter((l) => l.length > 0);
22032
- if (lines.length < 2) throw new Error(`readSessionInitialized: wire.jsonl at ${wirePath} has fewer than 2 lines`);
22033
- const parsed = JSON.parse(lines[1]);
22034
- const result = SessionInitializedRecordSchema.safeParse(parsed);
22035
- if (!result.success) throw new Error(`readSessionInitialized: wire.jsonl line 2 at ${wirePath} is not a valid session_initialized record: ${result.error.message}`);
22036
- return result.data;
22037
- }
22038
- async appendBoundary(sessionInitialized) {
22039
- const { seq: _seq, time: _time, ...input } = sessionInitialized;
22040
- await this.journalWriter.append(input);
22041
- }
22042
- };
22043
- function createWiredJournalCapability(deps) {
22044
- return new WiredJournalCapability(deps);
22045
- }
22046
- //#endregion
22047
22096
  //#region ../../packages/kimi-core/src/soul-plus/approval/wired-approval-runtime.ts
22048
22097
  /**
22049
22098
  * WiredApprovalRuntime — the production `ApprovalRuntime` (v2 §9-G).
@@ -30264,6 +30313,10 @@ const BUS_EVENT_TRANSLATORS = {
30264
30313
  hook_id: event.hook_id,
30265
30314
  outcome: event.outcome
30266
30315
  }),
30316
+ "session.created": (event, ctx) => {
30317
+ const data = event.data;
30318
+ return draft(event, ctx, "session.created", data);
30319
+ },
30267
30320
  "status.update": (event, ctx) => draft(event, ctx, "status.update", event.data),
30268
30321
  "session_meta.changed": (event, ctx) => draft(event, ctx, "session_meta.changed", event.data),
30269
30322
  "thinking.changed": (event, ctx) => draft(event, ctx, "thinking.changed", { level: event.level }),
@@ -30374,6 +30427,9 @@ function installWireEventBridge(opts) {
30374
30427
  * -32700 Parse error — codec decode failure (unparseable frame)
30375
30428
  * -32600 Invalid request — envelope schema rejection
30376
30429
  * -32602 Invalid params — zod-validated method params failure
30430
+ * -32001 LLM not configured
30431
+ * -32002 LLM capability mismatch
30432
+ * -32003 Provider-level failure
30377
30433
  * -32603 Internal error — fallback
30378
30434
  */
30379
30435
  function mapToWireError(err) {
@@ -30417,6 +30473,11 @@ function mapToWireError(err) {
30417
30473
  details: { session_id: err.sessionId }
30418
30474
  }
30419
30475
  };
30476
+ const businessError = classifyBusinessError(err);
30477
+ if (businessError !== null) return {
30478
+ request_id: null,
30479
+ error: businessError
30480
+ };
30420
30481
  return {
30421
30482
  request_id: null,
30422
30483
  error: {
@@ -30848,8 +30909,8 @@ var DefaultSessionApplicationService = class {
30848
30909
  eventBus,
30849
30910
  hookEngine
30850
30911
  });
30851
- const systemPrompt = input.systemPrompt ?? this.deps.defaultSystemPromptProvider?.();
30852
- const enabledToolNames = this.deps.enabledToolNamesProvider?.();
30912
+ const systemPrompt = input.systemPrompt ?? await this.deps.defaultSystemPromptProvider?.();
30913
+ const enabledToolNames = await this.deps.enabledToolNamesProvider?.();
30853
30914
  const model = input.model ?? this.deps.defaultModelProvider();
30854
30915
  const runtimeBundle = await this.deps.runtimeBundleProvider?.({
30855
30916
  sessionId,
@@ -30863,11 +30924,17 @@ var DefaultSessionApplicationService = class {
30863
30924
  const runtime = runtimeSlotBundle?.runtime ?? this.deps.runtimeProvider();
30864
30925
  const compactionProvider = runtimeSlotBundle?.compactionProvider ?? this.deps.compactionProviderProvider();
30865
30926
  const runtimeSlot = runtimeSlotBundle !== void 0 ? createSessionRuntimeSlot(runtimeSlotBundle) : void 0;
30927
+ const tools = await this.deps.toolsProvider({
30928
+ sessionId,
30929
+ model
30930
+ });
30931
+ const skillManager = await this.deps.skillManagerProvider?.() ?? this.deps.skillManager;
30932
+ const agentTypeRegistry = await this.deps.agentTypeRegistryProvider?.() ?? this.deps.agentTypeRegistry;
30866
30933
  const managed = await this.deps.sessionManager.createSession({
30867
30934
  sessionId,
30868
30935
  runtime,
30869
30936
  runtimeSlot,
30870
- tools: this.deps.toolsProvider(),
30937
+ tools,
30871
30938
  enabledToolNames,
30872
30939
  model,
30873
30940
  systemPrompt,
@@ -30878,12 +30945,19 @@ var DefaultSessionApplicationService = class {
30878
30945
  approvalRuntime: this.deps.approvalRuntime,
30879
30946
  approvalRuntimeFactory: this.deps.approvalRuntimeFactory,
30880
30947
  hookEngine,
30881
- skillManager: this.deps.skillManager,
30882
- agentTypeRegistry: this.deps.agentTypeRegistry,
30948
+ skillManager,
30949
+ agentTypeRegistry,
30883
30950
  questionRuntime: this.deps.questionRuntimeProvider?.({ sessionId }),
30884
30951
  logger: this.deps.logger
30885
30952
  });
30886
30953
  this.deps.sessionLifecycle?.onSessionCreated?.(managed);
30954
+ managed.eventBus?.emit({
30955
+ type: "session.created",
30956
+ data: {
30957
+ session_id: managed.sessionId,
30958
+ model
30959
+ }
30960
+ });
30887
30961
  return {
30888
30962
  sessionId: managed.sessionId,
30889
30963
  managed
@@ -30913,18 +30987,24 @@ var DefaultSessionApplicationService = class {
30913
30987
  const runtime = runtimeSlotBundle?.runtime ?? this.deps.runtimeProvider();
30914
30988
  const compactionProvider = runtimeSlotBundle?.compactionProvider ?? this.deps.compactionProviderProvider();
30915
30989
  const runtimeSlot = runtimeSlotBundle !== void 0 ? createSessionRuntimeSlot(runtimeSlotBundle) : void 0;
30990
+ const tools = await this.deps.toolsProvider({
30991
+ sessionId,
30992
+ model: initialModel
30993
+ });
30994
+ const skillManager = await this.deps.skillManagerProvider?.() ?? this.deps.skillManager;
30995
+ const agentTypeRegistry = await this.deps.agentTypeRegistryProvider?.() ?? this.deps.agentTypeRegistry;
30916
30996
  const managed = await this.deps.sessionManager.resumeSession(sessionId, {
30917
30997
  runtime,
30918
30998
  runtimeSlot,
30919
- tools: this.deps.toolsProvider(),
30999
+ tools,
30920
31000
  eventBus,
30921
31001
  compactionProvider,
30922
31002
  compactionConfig,
30923
31003
  approvalRuntime: this.deps.approvalRuntime,
30924
31004
  approvalRuntimeFactory: this.deps.approvalRuntimeFactory,
30925
31005
  hookEngine,
30926
- skillManager: this.deps.skillManager,
30927
- agentTypeRegistry: this.deps.agentTypeRegistry,
31006
+ skillManager,
31007
+ agentTypeRegistry,
30928
31008
  questionRuntime: this.deps.questionRuntimeProvider?.({ sessionId }),
30929
31009
  logger: this.deps.logger
30930
31010
  });
@@ -31116,17 +31196,20 @@ var DefaultSessionApplicationService = class {
31116
31196
  }
31117
31197
  async getBackgroundTasks(sessionId) {
31118
31198
  return {
31119
- background_tasks: this.deps.backgroundProcessManager?.list() ?? [],
31199
+ background_tasks: this.backgroundProcessManager()?.list() ?? [],
31120
31200
  agent_instances: await new SubagentStore(this.deps.pathConfig.sessionDir(sessionId)).listInstances()
31121
31201
  };
31122
31202
  }
31123
31203
  async stopBackgroundTask(taskId) {
31124
- const manager = this.deps.backgroundProcessManager;
31204
+ const manager = this.backgroundProcessManager();
31125
31205
  if (manager === void 0) throw new Error(`session.stopBackgroundTask: no BackgroundProcessManager wired (task ${taskId})`);
31126
31206
  if (await manager.stop(taskId) === void 0) throw new Error(`Background task not found: ${taskId}`);
31127
31207
  }
31128
31208
  getBackgroundTaskOutput(taskId, tail) {
31129
- return this.deps.backgroundProcessManager?.getOutput(taskId, tail) ?? "";
31209
+ return this.backgroundProcessManager()?.getOutput(taskId, tail) ?? "";
31210
+ }
31211
+ backgroundProcessManager() {
31212
+ return this.deps.backgroundProcessManagerProvider?.() ?? this.deps.backgroundProcessManager;
31130
31213
  }
31131
31214
  getManaged(sessionId) {
31132
31215
  const managed = this.deps.sessionManager.get(sessionId);
@@ -31603,15 +31686,25 @@ const CONVERSATION_HANDLER_DESCRIPTORS = [
31603
31686
  }).join("");
31604
31687
  inputParts = payload.input;
31605
31688
  }
31606
- const dispatch = await ctx.sessionApplication.prompt(msg.session_id, {
31607
- text: inputText,
31608
- parts: inputParts
31609
- });
31610
- return createWireResponse({
31611
- requestId: msg.id,
31612
- sessionId: msg.session_id,
31613
- data: dispatch
31614
- });
31689
+ try {
31690
+ const dispatch = await ctx.sessionApplication.prompt(msg.session_id, {
31691
+ text: inputText,
31692
+ parts: inputParts
31693
+ });
31694
+ return createWireResponse({
31695
+ requestId: msg.id,
31696
+ sessionId: msg.session_id,
31697
+ data: dispatch
31698
+ });
31699
+ } catch (error) {
31700
+ const classified = classifyBusinessError(error);
31701
+ if (classified !== null) return createWireResponse({
31702
+ requestId: msg.id,
31703
+ sessionId: msg.session_id,
31704
+ error: classified
31705
+ });
31706
+ throw error;
31707
+ }
31615
31708
  });
31616
31709
  }),
31617
31710
  defineWireMethodDescriptor("session.steer", "conversation", (ctx) => {
@@ -32260,6 +32353,7 @@ const SUPPORTED_WIRE_EVENTS = [
32260
32353
  "notification",
32261
32354
  "hook.triggered",
32262
32355
  "hook.resolved",
32356
+ "session.created",
32263
32357
  "session.error",
32264
32358
  "session.replay.chunk",
32265
32359
  "session.replay.end",
@@ -32358,8 +32452,8 @@ function registerDefaultWireHandlers(deps) {
32358
32452
  sessionManager,
32359
32453
  pathConfig,
32360
32454
  runtimeProvider: () => deps.runtimeProvider?.() ?? runtime,
32361
- toolsProvider: () => tools,
32362
- ...deps.enabledToolNames !== void 0 ? { enabledToolNamesProvider: () => deps.enabledToolNames } : {},
32455
+ toolsProvider: (ctx) => deps.toolsProvider?.(ctx) ?? tools,
32456
+ ...deps.enabledToolNames !== void 0 || deps.enabledToolNamesProvider !== void 0 ? { enabledToolNamesProvider: async () => deps.enabledToolNamesProvider !== void 0 ? await deps.enabledToolNamesProvider() : deps.enabledToolNames } : {},
32363
32457
  defaultModelProvider: () => deps.defaultModelProvider?.() ?? defaultModel,
32364
32458
  ...deps.defaultSystemPromptProvider !== void 0 ? { defaultSystemPromptProvider: deps.defaultSystemPromptProvider } : {},
32365
32459
  compactionProviderProvider: () => deps.compactionProviderProvider?.() ?? compactionProvider,
@@ -32371,10 +32465,13 @@ function registerDefaultWireHandlers(deps) {
32371
32465
  workspaceDir,
32372
32466
  approvalStateStore,
32373
32467
  skillManager: deps.skillManager,
32468
+ ...deps.skillManagerProvider !== void 0 ? { skillManagerProvider: deps.skillManagerProvider } : {},
32374
32469
  agentTypeRegistry: deps.agentTypeRegistry,
32470
+ ...deps.agentTypeRegistryProvider !== void 0 ? { agentTypeRegistryProvider: deps.agentTypeRegistryProvider } : {},
32375
32471
  questionRuntimeProvider: ({ sessionId }) => reverse !== void 0 ? createWireQuestionRuntime(reverse, sessionId) : void 0,
32376
32472
  rebuildRuntimeForModel,
32377
32473
  backgroundProcessManager,
32474
+ ...deps.backgroundProcessManagerProvider !== void 0 ? { backgroundProcessManagerProvider: deps.backgroundProcessManagerProvider } : {},
32378
32475
  sessionLifecycle: {
32379
32476
  onSessionCreated: (managed) => deps.sessionLifecycle?.onSessionCreated?.(managed),
32380
32477
  onSessionDestroyed: (sessionId) => {
@@ -33461,7 +33558,8 @@ var SessionManager = class {
33461
33558
  model: options.model,
33462
33559
  runtime: options.runtime,
33463
33560
  compactionProvider: options.compactionProvider,
33464
- ...options.compactionConfig !== void 0 ? { compactionConfig: options.compactionConfig } : {}
33561
+ ...options.compactionConfig !== void 0 ? { compactionConfig: options.compactionConfig } : {},
33562
+ ...options.modelCapabilities !== void 0 ? { modelCapabilities: options.modelCapabilities } : {}
33465
33563
  });
33466
33564
  const runtimeBundle = runtimeSlot.current();
33467
33565
  const mainInitInput = {
@@ -33523,12 +33621,8 @@ var SessionManager = class {
33523
33621
  skillManager: options.skillManager,
33524
33622
  questionRuntime: options.questionRuntime,
33525
33623
  compactionConfig: runtimeBundle.compactionConfig,
33624
+ modelCapabilities: runtimeBundle.modelCapabilities,
33526
33625
  compactionProvider: runtimeBundle.compactionProvider,
33527
- journalCapability: options.journalCapability ?? createWiredJournalCapability({
33528
- sessionDir,
33529
- journalWriter,
33530
- producer: this.producer
33531
- }),
33532
33626
  approvalRuntime,
33533
33627
  hookEngine: options.hookEngine,
33534
33628
  toolExecutionScopeFactory: options.toolExecutionScopeFactory,
@@ -33654,7 +33748,8 @@ var SessionManager = class {
33654
33748
  model: projected.model,
33655
33749
  runtime: options.runtime,
33656
33750
  compactionProvider: options.compactionProvider,
33657
- compactionConfig: options.compactionConfig
33751
+ compactionConfig: options.compactionConfig,
33752
+ ...options.modelCapabilities !== void 0 ? { modelCapabilities: options.modelCapabilities } : {}
33658
33753
  });
33659
33754
  const runtimeBundle = runtimeSlot.current();
33660
33755
  await stateCache.write({
@@ -33702,12 +33797,8 @@ var SessionManager = class {
33702
33797
  skillManager: options.skillManager,
33703
33798
  ...options.questionRuntime !== void 0 ? { questionRuntime: options.questionRuntime } : {},
33704
33799
  ...runtimeBundle.compactionConfig !== void 0 ? { compactionConfig: runtimeBundle.compactionConfig } : {},
33800
+ ...runtimeBundle.modelCapabilities !== void 0 ? { modelCapabilities: runtimeBundle.modelCapabilities } : {},
33705
33801
  compactionProvider: runtimeBundle.compactionProvider,
33706
- journalCapability: options.journalCapability ?? createWiredJournalCapability({
33707
- sessionDir,
33708
- journalWriter,
33709
- producer: this.producer
33710
- }),
33711
33802
  approvalRuntime,
33712
33803
  hookEngine: options.hookEngine,
33713
33804
  ...options.toolExecutionScopeFactory !== void 0 ? { toolExecutionScopeFactory: options.toolExecutionScopeFactory } : {},
@@ -42000,6 +42091,16 @@ function injectEnvVars(config) {
42000
42091
  yolo
42001
42092
  };
42002
42093
  }
42094
+ function parseTomlData(data) {
42095
+ const raw = JSON.parse(JSON.stringify(data));
42096
+ const transformed = transformTomlData(data);
42097
+ transformed["raw"] = raw;
42098
+ try {
42099
+ return KimiConfigSchema.parse(transformed);
42100
+ } catch (error) {
42101
+ throw new ConfigError(`Invalid configuration: ${error instanceof Error ? error.message : String(error)}`);
42102
+ }
42103
+ }
42003
42104
  /**
42004
42105
  * Load and merge configuration from global, project, and override layers.
42005
42106
  *
@@ -42027,6 +42128,16 @@ function loadConfig(options) {
42027
42128
  return config;
42028
42129
  }
42029
42130
  /**
42131
+ * Load configuration from exactly one TOML file.
42132
+ *
42133
+ * Missing files are treated as an empty config. This is the preferred entry
42134
+ * point for default-host assembly, where the caller owns the config path and
42135
+ * no global/project layering should be inferred.
42136
+ */
42137
+ function loadConfigFile(filePath) {
42138
+ return injectEnvVars(parseTomlData(readTomlFile(filePath) ?? {}));
42139
+ }
42140
+ /**
42030
42141
  * Save a validated {@link KimiConfig} to TOML. Existing raw keys loaded from
42031
42142
  * disk are preserved when `config.raw` is present; typed fields are overlaid
42032
42143
  * in snake_case so the file remains compatible with the user-facing format.
@@ -78233,6 +78344,50 @@ async function loadBundledAgentTypeRegistry(defaultAgentYamlPath, onWarning) {
78233
78344
  return { agentTypeRegistry: registry };
78234
78345
  }
78235
78346
  //#endregion
78347
+ //#region ../../packages/kimi-core/src/soul-plus/host/config.ts
78348
+ const DEFAULT_SOUL_PLUS_CONFIG = {
78349
+ providers: {},
78350
+ defaultProvider: void 0,
78351
+ defaultModel: void 0,
78352
+ models: void 0,
78353
+ thinking: void 0,
78354
+ planMode: void 0,
78355
+ yolo: void 0,
78356
+ defaultThinking: false,
78357
+ defaultYolo: false,
78358
+ defaultPlanMode: false,
78359
+ hooks: void 0,
78360
+ services: void 0,
78361
+ mergeAllAvailableSkills: false,
78362
+ loopControl: {
78363
+ reservedContextSize: 5e4,
78364
+ compactionTriggerRatio: .85
78365
+ },
78366
+ raw: void 0
78367
+ };
78368
+ function normalizeDefaultSoulPlusConfig(config) {
78369
+ return {
78370
+ ...DEFAULT_SOUL_PLUS_CONFIG,
78371
+ ...config,
78372
+ providers: {
78373
+ ...DEFAULT_SOUL_PLUS_CONFIG.providers,
78374
+ ...config.providers
78375
+ },
78376
+ ...DEFAULT_SOUL_PLUS_CONFIG.services !== void 0 || config.services !== void 0 ? { services: {
78377
+ ...DEFAULT_SOUL_PLUS_CONFIG.services,
78378
+ ...config.services
78379
+ } } : {},
78380
+ loopControl: {
78381
+ ...DEFAULT_SOUL_PLUS_CONFIG.loopControl,
78382
+ ...config.loopControl
78383
+ },
78384
+ raw: config.raw
78385
+ };
78386
+ }
78387
+ function loadDefaultSoulPlusConfig(configPath) {
78388
+ return normalizeDefaultSoulPlusConfig(loadConfigFile(configPath));
78389
+ }
78390
+ //#endregion
78236
78391
  //#region ../../packages/kimi-core/src/soul-plus/host/runtime.ts
78237
78392
  function isVideoUploadCapableProvider(value) {
78238
78393
  return typeof value === "object" && value !== null && typeof value.uploadVideo === "function";
@@ -78265,21 +78420,27 @@ function resolveDefaultSoulPlusDefaultModel(options) {
78265
78420
  function getDefaultSoulPlusMaxContextSize(options, defaultModel) {
78266
78421
  return options.kimiConfig?.models?.[defaultModel]?.maxContextSize ?? 2e5;
78267
78422
  }
78423
+ function getDefaultSoulPlusModelCapabilities(options, defaultModel) {
78424
+ return options.kimiConfig?.models?.[defaultModel]?.capabilities;
78425
+ }
78268
78426
  async function createDefaultSoulPlusRuntimeBundle(options) {
78269
78427
  const defaultModel = resolveDefaultSoulPlusDefaultModel(options);
78270
78428
  const maxContextSize = getDefaultSoulPlusMaxContextSize(options, defaultModel);
78429
+ const modelCapabilities = getDefaultSoulPlusModelCapabilities(options, defaultModel);
78271
78430
  if (options.runtime !== void 0 && options.compactionProvider !== void 0) return {
78272
78431
  runtime: options.runtime,
78273
78432
  compactionProvider: options.compactionProvider,
78274
78433
  defaultModel,
78275
- maxContextSize
78434
+ maxContextSize,
78435
+ ...modelCapabilities !== void 0 ? { modelCapabilities } : {}
78276
78436
  };
78277
78437
  if (options.runtime !== void 0 || options.compactionProvider !== void 0) throw new Error("createDefaultSoulPlusWireClient: runtime and compactionProvider must be provided together");
78278
78438
  if (defaultModel.length === 0) return {
78279
78439
  runtime: createRuntime({ kosong: new UnconfiguredKosongAdapter() }),
78280
78440
  compactionProvider: new UnconfiguredCompactionProvider(),
78281
78441
  defaultModel,
78282
- maxContextSize
78442
+ maxContextSize,
78443
+ ...modelCapabilities !== void 0 ? { modelCapabilities } : {}
78283
78444
  };
78284
78445
  if (options.kimiConfig === void 0) throw new Error("createDefaultSoulPlusWireClient: provide either runtime+compactionProvider or kimiConfig");
78285
78446
  const provider = await createProviderFromConfig(options.kimiConfig, defaultModel, {
@@ -78296,6 +78457,7 @@ async function createDefaultSoulPlusRuntimeBundle(options) {
78296
78457
  compactionProvider: createKosongCompactionProvider(provider),
78297
78458
  defaultModel,
78298
78459
  maxContextSize,
78460
+ ...modelCapabilities !== void 0 ? { modelCapabilities } : {},
78299
78461
  videoUploader
78300
78462
  };
78301
78463
  }
@@ -78783,7 +78945,7 @@ new AsyncLocalStorage();
78783
78945
  //#endregion
78784
78946
  //#region ../../packages/kimi-core/src/soul-plus/host/tools.ts
78785
78947
  async function createDefaultSoulPlusBuiltinTools(options) {
78786
- const backgroundManager = new BackgroundProcessManager();
78948
+ const backgroundManager = options.backgroundManager ?? new BackgroundProcessManager();
78787
78949
  const todoStore = new InMemoryTodoStore();
78788
78950
  const hostEnvironment = await detectEnvironmentFromNode();
78789
78951
  const defaultCapabilities = new Set(resolveModelCapabilities(options));
@@ -93460,18 +93622,21 @@ function applyManagedKimiCodeConfig(config, options) {
93460
93622
  }
93461
93623
  async function provisionManagedKimiCodeConfigAfterLogin(options) {
93462
93624
  const models = await fetchManagedKimiCodeModels(options);
93463
- const config = loadConfig({ pathConfig: options.pathConfig });
93625
+ const config = options.configPath !== void 0 ? loadConfigFile(options.configPath) : loadConfig({ pathConfig: options.pathConfig });
93464
93626
  const applied = applyManagedKimiCodeConfig(config, {
93465
93627
  models,
93466
93628
  baseUrl: options.baseUrl
93467
93629
  });
93468
- await saveConfig(config, { pathConfig: options.pathConfig });
93630
+ await saveConfig(config, {
93631
+ pathConfig: options.pathConfig,
93632
+ filePath: options.configPath
93633
+ });
93469
93634
  return {
93470
93635
  providerName: KIMI_CODE_PROVIDER_NAME,
93471
93636
  defaultModel: applied.defaultModel,
93472
93637
  defaultThinking: applied.defaultThinking,
93473
93638
  models,
93474
- configPath: join(options.pathConfig.home, "config.toml")
93639
+ configPath: options.configPath ?? join(options.pathConfig.home, "config.toml")
93475
93640
  };
93476
93641
  }
93477
93642
  //#endregion
@@ -93750,9 +93915,11 @@ var DefaultSoulPlusOAuthServiceImpl = class {
93750
93915
  })).accessToken;
93751
93916
  await provisionManagedKimiCodeConfigAfterLogin({
93752
93917
  pathConfig: this.options.pathConfig,
93918
+ configPath: this.options.configPath,
93753
93919
  accessToken
93754
93920
  });
93755
- await this.options.onConfigProvisioned?.(loadConfig({ pathConfig: this.options.pathConfig }));
93921
+ const config = this.options.configPath !== void 0 ? loadConfigFile(this.options.configPath) : loadConfig({ pathConfig: this.options.pathConfig });
93922
+ await this.options.onConfigProvisioned?.(config);
93756
93923
  return {
93757
93924
  provider_name: name,
93758
93925
  ok: true
@@ -94646,7 +94813,9 @@ async function startCoreWireServer(options) {
94646
94813
  compactionProvider: options.compactionProvider,
94647
94814
  kosong: options.runtime.kosong,
94648
94815
  tools: options.tools ?? [],
94816
+ ...options.toolsProvider !== void 0 ? { toolsProvider: options.toolsProvider } : {},
94649
94817
  ...options.enabledToolNames !== void 0 ? { enabledToolNames: options.enabledToolNames } : {},
94818
+ ...options.enabledToolNamesProvider !== void 0 ? { enabledToolNamesProvider: options.enabledToolNamesProvider } : {},
94650
94819
  approval: options.approval,
94651
94820
  ...options.enableWiredApprovals !== void 0 ? { enableWiredApprovals: options.enableWiredApprovals } : {},
94652
94821
  eventBus,
@@ -94668,10 +94837,13 @@ async function startCoreWireServer(options) {
94668
94837
  server: options.transport,
94669
94838
  hookEngine,
94670
94839
  skillManager: options.skillManager,
94840
+ ...options.skillManagerProvider !== void 0 ? { skillManagerProvider: options.skillManagerProvider } : {},
94671
94841
  ...options.agentTypeRegistry !== void 0 ? { agentTypeRegistry: options.agentTypeRegistry } : {},
94842
+ ...options.agentTypeRegistryProvider !== void 0 ? { agentTypeRegistryProvider: options.agentTypeRegistryProvider } : {},
94672
94843
  mcpRegistry: options.mcpRegistry,
94673
94844
  authService: options.authService,
94674
94845
  ...options.backgroundProcessManager !== void 0 ? { backgroundProcessManager: options.backgroundProcessManager } : {},
94846
+ ...options.backgroundProcessManagerProvider !== void 0 ? { backgroundProcessManagerProvider: options.backgroundProcessManagerProvider } : {},
94675
94847
  sessionLifecycle: {
94676
94848
  onSessionCreated: installBridge,
94677
94849
  onSessionDestroyed: disposeBridge
@@ -94748,10 +94920,8 @@ const DEFAULT_RESERVED_CONTEXT_SIZE = 5e4;
94748
94920
  const DEFAULT_RESERVED_CONTEXT_FRACTION = .25;
94749
94921
  async function createDefaultSoulPlusWireClient(options) {
94750
94922
  const pathConfig = options.pathConfig ?? new PathConfig();
94751
- const loadHostConfig = () => options.kimiConfig ?? loadConfig({
94752
- pathConfig,
94753
- workspaceDir: options.workspaceDir
94754
- });
94923
+ const configPath = options.configPath ?? join(pathConfig.home, "config.toml");
94924
+ const loadHostConfig = () => options.kimiConfig !== void 0 ? normalizeDefaultSoulPlusConfig(options.kimiConfig) : loadDefaultSoulPlusConfig(configPath);
94755
94925
  const resolveHostConfig = (config) => {
94756
94926
  const requestedDefaultModel = resolveDefaultSoulPlusDefaultModel({
94757
94927
  ...options,
@@ -94771,7 +94941,45 @@ async function createDefaultSoulPlusWireClient(options) {
94771
94941
  let authService;
94772
94942
  let activeVideoUploader;
94773
94943
  let activeDefaultModel = defaultModel;
94774
- let activeMaxContextSize = 2e5;
94944
+ let activeMaxContextSize = getDefaultSoulPlusMaxContextSize({
94945
+ kimiConfig: effectiveConfig,
94946
+ defaultModel
94947
+ }, defaultModel);
94948
+ const backgroundManager = new BackgroundProcessManager();
94949
+ let defaultsPromise;
94950
+ const builtinByModel = /* @__PURE__ */ new Map();
94951
+ const ensureDefaults = () => {
94952
+ defaultsPromise ??= prepareDefaultSoulPlusSessionDefaults({
94953
+ workspaceDir: options.workspaceDir,
94954
+ pathConfig
94955
+ });
94956
+ return defaultsPromise;
94957
+ };
94958
+ const ensureBuiltin = (model) => {
94959
+ let existing = builtinByModel.get(model);
94960
+ if (existing !== void 0) return existing;
94961
+ existing = (async () => {
94962
+ const defaults = await ensureDefaults();
94963
+ const webProviders = createDefaultSoulPlusWebProviders({
94964
+ kimiConfig: effectiveConfig,
94965
+ oauthManagers: authService.oauthManagers,
94966
+ userAgent: options.userAgent ?? inferUserAgent(options.defaultHeaders)
94967
+ });
94968
+ return createDefaultSoulPlusBuiltinTools({
94969
+ workspaceDir: options.workspaceDir,
94970
+ workspace: defaults.workspace,
94971
+ kimiConfig: effectiveConfig,
94972
+ modelAlias: model,
94973
+ enabledToolNames: defaults.enabledToolNames,
94974
+ webSearchProvider: webProviders.webSearchProvider,
94975
+ urlFetcher: webProviders.urlFetcher,
94976
+ videoUploader: activeVideoUploader,
94977
+ backgroundManager
94978
+ });
94979
+ })();
94980
+ builtinByModel.set(model, existing);
94981
+ return existing;
94982
+ };
94775
94983
  const buildRuntimeBundle = async (model) => {
94776
94984
  const next = await createDefaultSoulPlusRuntimeBundle({
94777
94985
  ...options,
@@ -94786,7 +94994,8 @@ async function createDefaultSoulPlusWireClient(options) {
94786
94994
  model: next.defaultModel,
94787
94995
  runtime: next.runtime,
94788
94996
  compactionProvider: next.compactionProvider,
94789
- compactionConfig: buildCompactionConfig(effectiveConfig, next.defaultModel, next.maxContextSize)
94997
+ compactionConfig: buildCompactionConfig(effectiveConfig, next.defaultModel, next.maxContextSize),
94998
+ ...next.modelCapabilities !== void 0 ? { modelCapabilities: next.modelCapabilities } : {}
94790
94999
  };
94791
95000
  };
94792
95001
  const reloadConfigFromDisk = async () => {
@@ -94795,6 +95004,7 @@ async function createDefaultSoulPlusWireClient(options) {
94795
95004
  effectiveConfig = resolved.effectiveConfig;
94796
95005
  defaultModel = resolved.defaultModel;
94797
95006
  activeDefaultModel = defaultModel;
95007
+ builtinByModel.clear();
94798
95008
  activeMaxContextSize = getDefaultSoulPlusMaxContextSize({
94799
95009
  kimiConfig: effectiveConfig,
94800
95010
  defaultModel
@@ -94804,45 +95014,28 @@ async function createDefaultSoulPlusWireClient(options) {
94804
95014
  kimiConfig: effectiveConfig,
94805
95015
  modelAlias: defaultModel,
94806
95016
  pathConfig,
95017
+ configPath,
94807
95018
  forceManagedKimiCodeOAuth: defaultModel.length === 0,
94808
95019
  onConfigProvisioned: reloadConfigFromDisk
94809
95020
  });
94810
- const initialRuntimeBundle = await buildRuntimeBundle(defaultModel);
94811
- activeMaxContextSize = initialRuntimeBundle.compactionConfig?.maxContextSize ?? 2e5;
94812
- const defaults = await prepareDefaultSoulPlusSessionDefaults({
94813
- workspaceDir: options.workspaceDir,
94814
- pathConfig
94815
- });
94816
- const webProviders = createDefaultSoulPlusWebProviders({
94817
- kimiConfig: effectiveConfig,
94818
- oauthManagers: authService.oauthManagers,
94819
- userAgent: options.userAgent ?? inferUserAgent(options.defaultHeaders)
94820
- });
94821
- const builtin = await createDefaultSoulPlusBuiltinTools({
94822
- workspaceDir: options.workspaceDir,
94823
- workspace: defaults.workspace,
94824
- kimiConfig: effectiveConfig,
94825
- modelAlias: defaultModel,
94826
- enabledToolNames: defaults.enabledToolNames,
94827
- webSearchProvider: webProviders.webSearchProvider,
94828
- urlFetcher: webProviders.urlFetcher,
94829
- videoUploader: activeVideoUploader
95021
+ const fallbackRuntimeBundle = await createDefaultSoulPlusRuntimeBundle({
95022
+ defaultModel: "",
95023
+ kimiConfig: effectiveConfig
94830
95024
  });
94831
- const backgroundManager = builtin.backgroundManager;
94832
95025
  const handle = await createInProcessWireClient({
94833
95026
  pathConfig,
94834
- runtime: initialRuntimeBundle.runtime,
94835
- compactionProvider: initialRuntimeBundle.compactionProvider,
95027
+ runtime: fallbackRuntimeBundle.runtime,
95028
+ compactionProvider: fallbackRuntimeBundle.compactionProvider,
94836
95029
  runtimeBundleProvider: ({ model }) => buildRuntimeBundle(model),
94837
95030
  workspaceDir: options.workspaceDir,
94838
95031
  defaultModel: activeDefaultModel,
94839
95032
  defaultModelProvider: () => activeDefaultModel,
94840
- enabledToolNames: defaults.enabledToolNames,
95033
+ enabledToolNamesProvider: async () => (await ensureDefaults()).enabledToolNames,
94841
95034
  enableWiredApprovals: true,
94842
- defaultSystemPromptProvider: () => defaults.systemPrompt,
94843
- tools: builtin.tools,
94844
- skillManager: defaults.skillManager,
94845
- ...defaults.agentTypeRegistry !== void 0 ? { agentTypeRegistry: defaults.agentTypeRegistry } : {},
95035
+ defaultSystemPromptProvider: async () => (await ensureDefaults()).systemPrompt,
95036
+ toolsProvider: async ({ model }) => (await ensureBuiltin(model)).tools,
95037
+ skillManagerProvider: async () => (await ensureDefaults()).skillManager,
95038
+ agentTypeRegistryProvider: async () => (await ensureDefaults()).agentTypeRegistry,
94846
95039
  authService,
94847
95040
  modelsProvider: () => ({
94848
95041
  models: Object.keys(effectiveConfig.models ?? {}),
@@ -96076,6 +96269,12 @@ const APPROVED_PLAN_MARKER = "## Approved Plan:";
96076
96269
  function str(v) {
96077
96270
  return typeof v === "string" ? v : "";
96078
96271
  }
96272
+ function formatSubagentTokens(usage) {
96273
+ if (usage === void 0) return void 0;
96274
+ const total = usage.input + usage.output;
96275
+ if (total <= 0) return void 0;
96276
+ return `${total >= 1e3 ? `${(total / 1e3).toFixed(1)}k` : String(total)} tok`;
96277
+ }
96079
96278
  function extractApprovedPlan(output) {
96080
96279
  const markerIndex = output.indexOf(APPROVED_PLAN_MARKER);
96081
96280
  if (markerIndex < 0) return "";
@@ -96262,6 +96461,11 @@ var ToolCallComponent = class extends Container {
96262
96461
  * 最后几行,避免占用过多屏幕。
96263
96462
  */
96264
96463
  subagentText = "";
96464
+ subagentPhase;
96465
+ subagentRunInBackground = false;
96466
+ subagentUsage;
96467
+ subagentResultSummary;
96468
+ subagentError;
96265
96469
  constructor(toolCall, result, colors, ui, markdownTheme) {
96266
96470
  super();
96267
96471
  this.toolCall = toolCall;
@@ -96346,8 +96550,42 @@ var ToolCallComponent = class extends Container {
96346
96550
  this.rebuildContent();
96347
96551
  this.ui?.requestRender();
96348
96552
  }
96553
+ /**
96554
+ * 来自 wire 'subagent.spawned'。子 agent 已被注册,但内部活动事件
96555
+ * (content.delta / tool.call) 可能尚未到达——把 UI 切到 'spawning'
96556
+ * 占位状态(除非 runInBackground,这种情况直接进 'backgrounded')。
96557
+ */
96558
+ onSubagentSpawned(meta) {
96559
+ this.subagentAgentId = meta.agentId;
96560
+ this.subagentAgentName = meta.agentName;
96561
+ this.subagentRunInBackground = meta.runInBackground;
96562
+ this.subagentPhase = meta.runInBackground ? "backgrounded" : "spawning";
96563
+ this.rebuildContent();
96564
+ this.ui?.requestRender();
96565
+ }
96566
+ /**
96567
+ * 来自 wire 'subagent.completed'。把 phase 切到 'done',记录 token
96568
+ * usage 与 result summary(用于头部右侧 chip + 尾部摘要行)。
96569
+ */
96570
+ onSubagentCompleted(payload) {
96571
+ this.subagentPhase = "done";
96572
+ this.subagentUsage = payload.usage;
96573
+ this.subagentResultSummary = payload.resultSummary.length > 0 ? payload.resultSummary : void 0;
96574
+ this.rebuildContent();
96575
+ this.ui?.requestRender();
96576
+ }
96577
+ /**
96578
+ * 来自 wire 'subagent.failed'。
96579
+ */
96580
+ onSubagentFailed(payload) {
96581
+ this.subagentPhase = "failed";
96582
+ this.subagentError = payload.error;
96583
+ this.rebuildContent();
96584
+ this.ui?.requestRender();
96585
+ }
96349
96586
  appendSubagentText(text) {
96350
96587
  this.subagentText += text;
96588
+ if (this.subagentPhase === void 0 || this.subagentPhase === "spawning") this.subagentPhase = "running";
96351
96589
  this.rebuildContent();
96352
96590
  this.ui?.requestRender();
96353
96591
  }
@@ -96358,6 +96596,7 @@ var ToolCallComponent = class extends Container {
96358
96596
  args: call.args,
96359
96597
  ...existing?.streamingArguments !== void 0 ? { streamingArguments: existing.streamingArguments } : {}
96360
96598
  });
96599
+ if (this.subagentPhase === void 0 || this.subagentPhase === "spawning") this.subagentPhase = "running";
96361
96600
  this.rebuildContent();
96362
96601
  this.ui?.requestRender();
96363
96602
  }
@@ -96440,10 +96679,11 @@ var ToolCallComponent = class extends Container {
96440
96679
  this.buildSubagentBlock();
96441
96680
  }
96442
96681
  buildSubagentBlock() {
96443
- if (this.subagentAgentId === void 0 && this.ongoingSubCalls.size === 0 && this.finishedSubCalls.length === 0 && this.subagentText.length === 0) return;
96682
+ if (this.subagentAgentId === void 0 && this.ongoingSubCalls.size === 0 && this.finishedSubCalls.length === 0 && this.subagentText.length === 0 && this.subagentPhase === void 0) return;
96444
96683
  const dim = chalk.dim;
96445
- const header = this.subagentAgentName !== void 0 ? `subagent ${this.subagentAgentName} (${this.formatAgentId()})` : `subagent (${this.formatAgentId()})`;
96446
- this.addChild(new Text(dim(` ${header}`), 0, 0));
96684
+ const phaseChip = this.formatPhaseChip();
96685
+ const headerLabel = this.subagentAgentName !== void 0 ? `subagent ${this.subagentAgentName} (${this.formatAgentId()})` : `subagent (${this.formatAgentId()})`;
96686
+ this.addChild(new Text(` ${dim(`↳ ${headerLabel}`)}${phaseChip}`, 0, 0));
96447
96687
  if (this.hiddenSubCallCount > 0) {
96448
96688
  const suffix = this.hiddenSubCallCount > 1 ? "s" : "";
96449
96689
  this.addChild(new Text(dim.italic(` ${String(this.hiddenSubCallCount)} more tool call${suffix} ...`), 0, 0));
@@ -96465,6 +96705,50 @@ var ToolCallComponent = class extends Container {
96465
96705
  const tailLines = this.subagentText.split("\n").slice(-3);
96466
96706
  for (const line of tailLines) this.addChild(new Text(` ${dim(line)}`, 0, 0));
96467
96707
  }
96708
+ if (this.subagentPhase === "done" && this.subagentResultSummary !== void 0) {
96709
+ const summaryLines = this.subagentResultSummary.split("\n").slice(0, 2);
96710
+ for (const line of summaryLines) this.addChild(new Text(` ${dim("└")} ${line}`, 0, 0));
96711
+ }
96712
+ if (this.subagentPhase === "failed" && this.subagentError !== void 0) {
96713
+ const errLines = this.subagentError.split("\n");
96714
+ for (const line of errLines) this.addChild(new Text(` ${chalk.hex(this.colors.error)("└")} ${line}`, 0, 0));
96715
+ }
96716
+ }
96717
+ /**
96718
+ * 头部右侧 phase / token chip。phase=undefined 时无 chip。
96719
+ * spawning → ↻ starting…
96720
+ * running → ↻ running
96721
+ * done → ✓ N tools · 8.4k tok
96722
+ * failed → ✗ failed
96723
+ * backgrounded → ◐ backgrounded
96724
+ */
96725
+ formatPhaseChip() {
96726
+ if (this.subagentPhase === void 0) return "";
96727
+ const dim = chalk.dim;
96728
+ const parts = [];
96729
+ switch (this.subagentPhase) {
96730
+ case "spawning":
96731
+ parts.push("↻ starting…");
96732
+ break;
96733
+ case "running":
96734
+ parts.push("↻ running");
96735
+ break;
96736
+ case "done": {
96737
+ parts.push(chalk.hex(this.colors.success)("✓ done"));
96738
+ const toolCount = this.finishedSubCalls.length + this.hiddenSubCallCount;
96739
+ if (toolCount > 0) parts.push(`${String(toolCount)} tool${toolCount > 1 ? "s" : ""}`);
96740
+ const tokens = formatSubagentTokens(this.subagentUsage);
96741
+ if (tokens !== void 0) parts.push(tokens);
96742
+ break;
96743
+ }
96744
+ case "failed":
96745
+ parts.push(chalk.hex(this.colors.error)("✗ failed"));
96746
+ break;
96747
+ case "backgrounded":
96748
+ parts.push("◐ backgrounded");
96749
+ break;
96750
+ }
96751
+ return parts.length > 0 ? dim(` · ${parts.join(" · ")}`) : "";
96468
96752
  }
96469
96753
  formatAgentId() {
96470
96754
  const id = this.subagentAgentId ?? "";
@@ -97392,6 +97676,28 @@ function handleSubagentSourceEvent(ectx, payload) {
97392
97676
  });
97393
97677
  }
97394
97678
  }
97679
+ function handleSubagentSpawned(ectx, data) {
97680
+ const tc = ectx.state.pendingToolComponents.get(data.parent_tool_call_id);
97681
+ if (tc === void 0) return;
97682
+ tc.onSubagentSpawned({
97683
+ agentId: data.agent_id,
97684
+ agentName: data.agent_name,
97685
+ runInBackground: data.run_in_background
97686
+ });
97687
+ }
97688
+ function handleSubagentCompleted(ectx, data) {
97689
+ const tc = ectx.state.pendingToolComponents.get(data.parent_tool_call_id);
97690
+ if (tc === void 0) return;
97691
+ tc.onSubagentCompleted({
97692
+ usage: data.usage,
97693
+ resultSummary: data.result_summary
97694
+ });
97695
+ }
97696
+ function handleSubagentFailed(ectx, data) {
97697
+ const tc = ectx.state.pendingToolComponents.get(data.parent_tool_call_id);
97698
+ if (tc === void 0) return;
97699
+ tc.onSubagentFailed({ error: data.error });
97700
+ }
97395
97701
  //#endregion
97396
97702
  //#region src/utils/git/git-ls-files.ts
97397
97703
  /**
@@ -100372,6 +100678,15 @@ function dispatchEvent(event, ectx, sendQueued) {
100372
100678
  case "notification":
100373
100679
  handleNotification(ectx, event.data);
100374
100680
  break;
100681
+ case "subagent.spawned":
100682
+ handleSubagentSpawned(ectx, event.data);
100683
+ break;
100684
+ case "subagent.completed":
100685
+ handleSubagentCompleted(ectx, event.data);
100686
+ break;
100687
+ case "subagent.failed":
100688
+ handleSubagentFailed(ectx, event.data);
100689
+ break;
100375
100690
  default: break;
100376
100691
  }
100377
100692
  }