kfc-code-cli 0.0.1-alpha.3 → 0.0.1-alpha.4

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 +1182 -670
  2. package/package.json +2 -2
package/dist/main.mjs CHANGED
@@ -5,7 +5,7 @@ import { createRequire } from "node:module";
5
5
  import { Command, Option } from "commander";
6
6
  import * as nodeFs from "node:fs";
7
7
  import Vt, { chmodSync, closeSync, constants, createReadStream, createWriteStream, existsSync, fsyncSync, mkdirSync, openSync, promises, readFileSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
8
- import jn, { access, appendFile, chmod, lstat, mkdir, mkdtemp, open, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
8
+ import fs, { access, appendFile, chmod, lstat, mkdir, mkdtemp, open, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
9
9
  import * as path$2 from "node:path";
10
10
  import path, { basename, dirname, extname, isAbsolute, join, normalize, posix, resolve, sep, win32 } from "node:path";
11
11
  import { finished, pipeline } from "node:stream/promises";
@@ -28,12 +28,12 @@ import * as ks from "zlib";
28
28
  import qr from "zlib";
29
29
  import so from "node:assert";
30
30
  import { parse as parse$1, stringify } from "smol-toml";
31
- import * as fs$1 from "fs/promises";
31
+ import * as fs$2 from "fs/promises";
32
32
  import { writeFile as writeFile$1 } from "fs/promises";
33
33
  import { AsyncLocalStorage } from "node:async_hooks";
34
34
  import "yazl";
35
35
  import pino from "pino";
36
- import { CombinedAutocompleteProvider, Container, Editor, Image, Key, Markdown, ProcessTerminal, Spacer, TUI, Text, decodeKittyPrintable, fuzzyFilter, fuzzyMatch, getCapabilities, matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
36
+ import { CombinedAutocompleteProvider, Container, Editor, Image, Input, Key, Markdown, ProcessTerminal, Spacer, TUI, Text, decodeKittyPrintable, fuzzyFilter, fuzzyMatch, getCapabilities, matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
37
37
  import chalk from "chalk";
38
38
  import { highlight } from "cli-highlight";
39
39
  import { gt, valid } from "semver";
@@ -1026,26 +1026,12 @@ var BaseContextState = class {
1026
1026
  this._systemPrompt = event.new_prompt;
1027
1027
  return;
1028
1028
  case "model_changed":
1029
- await this.journalWriter.append({
1030
- type: "model_changed",
1031
- old_model: event.old_model,
1032
- new_model: event.new_model
1033
- });
1034
1029
  this._model = event.new_model;
1035
1030
  return;
1036
1031
  case "thinking_changed":
1037
- await this.journalWriter.append({
1038
- type: "thinking_changed",
1039
- level: event.level
1040
- });
1041
1032
  this._thinkingLevel = event.level;
1042
1033
  return;
1043
- case "plan_mode_changed":
1044
- await this.journalWriter.append({
1045
- type: "plan_mode_changed",
1046
- enabled: event.enabled
1047
- });
1048
- return;
1034
+ case "plan_mode_changed": return;
1049
1035
  case "tools_changed":
1050
1036
  await this.journalWriter.append({
1051
1037
  type: "tools_changed",
@@ -1146,9 +1132,6 @@ var WiredSessionJournalImpl = class {
1146
1132
  async appendTeamMail(data) {
1147
1133
  await this.journalWriter.append(data);
1148
1134
  }
1149
- async appendPermissionModeChanged(data) {
1150
- await this.journalWriter.append(data);
1151
- }
1152
1135
  async appendToolDenied(data) {
1153
1136
  await this.journalWriter.append(data);
1154
1137
  }
@@ -1164,9 +1147,6 @@ var WiredSessionJournalImpl = class {
1164
1147
  async appendOwnershipChanged(data) {
1165
1148
  await this.journalWriter.append(data);
1166
1149
  }
1167
- async appendSessionMetaChanged(data) {
1168
- await this.journalWriter.append(data);
1169
- }
1170
1150
  };
1171
1151
  //#endregion
1172
1152
  //#region ../../packages/kimi-core/src/storage/schema/records.ts
@@ -1284,25 +1264,6 @@ const _rawSystemPromptChangedRecordSchema = z.object({
1284
1264
  time: z.number(),
1285
1265
  new_prompt: z.string()
1286
1266
  });
1287
- const _rawModelChangedRecordSchema = z.object({
1288
- type: z.literal("model_changed"),
1289
- seq: z.number(),
1290
- time: z.number(),
1291
- old_model: z.string(),
1292
- new_model: z.string()
1293
- });
1294
- const _rawThinkingChangedRecordSchema = z.object({
1295
- type: z.literal("thinking_changed"),
1296
- seq: z.number(),
1297
- time: z.number(),
1298
- level: z.string()
1299
- });
1300
- const _rawPlanModeChangedRecordSchema = z.object({
1301
- type: z.literal("plan_mode_changed"),
1302
- seq: z.number(),
1303
- time: z.number(),
1304
- enabled: z.boolean()
1305
- });
1306
1267
  const _rawToolsChangedRecordSchema = z.object({
1307
1268
  type: z.literal("tools_changed"),
1308
1269
  seq: z.number(),
@@ -1359,17 +1320,6 @@ const _rawNotificationRecordSchema = z.object({
1359
1320
  envelope_id: z.string().optional()
1360
1321
  })
1361
1322
  });
1362
- const _rawPermissionModeChangedRecordSchema = z.object({
1363
- type: z.literal("permission_mode_changed"),
1364
- seq: z.number(),
1365
- time: z.number(),
1366
- turn_id: z.string().optional(),
1367
- data: z.object({
1368
- from: z.string(),
1369
- to: z.string(),
1370
- reason: z.string()
1371
- })
1372
- });
1373
1323
  const _rawToolDeniedRecordSchema = z.object({
1374
1324
  type: z.literal("tool_denied"),
1375
1325
  seq: z.number(),
@@ -1611,25 +1561,6 @@ const _rawOwnershipChangedRecordSchema = z.object({
1611
1561
  old_owner: z.string().nullable(),
1612
1562
  new_owner: z.string()
1613
1563
  });
1614
- const _rawSessionMetaChangedRecordSchema = z.object({
1615
- type: z.literal("session_meta_changed"),
1616
- seq: z.number(),
1617
- time: z.number(),
1618
- patch: z.object({
1619
- title: z.string().optional(),
1620
- tags: z.array(z.string()).optional(),
1621
- description: z.string().optional(),
1622
- archived: z.boolean().optional(),
1623
- color: z.string().optional(),
1624
- plan_slug: z.string().optional()
1625
- }),
1626
- source: z.enum([
1627
- "user",
1628
- "auto",
1629
- "system"
1630
- ]),
1631
- reason: z.string().optional()
1632
- });
1633
1564
  const _rawContextEditRecordSchema = z.object({
1634
1565
  type: z.literal("context_edit"),
1635
1566
  seq: z.number(),
@@ -1662,15 +1593,15 @@ const _sessionInitializedCommonShape = {
1662
1593
  seq: z.number(),
1663
1594
  time: z.number(),
1664
1595
  system_prompt: z.string(),
1665
- model: z.string(),
1666
1596
  active_tools: z.array(z.string()),
1597
+ model: z.string().optional(),
1667
1598
  permission_mode: z.enum([
1668
1599
  "default",
1669
1600
  "acceptEdits",
1670
1601
  "bypassPermissions"
1671
- ]),
1672
- plan_mode: z.boolean(),
1673
- workspace_dir: z.string(),
1602
+ ]).optional(),
1603
+ plan_mode: z.boolean().optional(),
1604
+ workspace_dir: z.string().optional(),
1674
1605
  thinking_level: z.string().optional()
1675
1606
  };
1676
1607
  const _rawSessionInitializedMainSchema = z.object({
@@ -1707,13 +1638,9 @@ const WireRecordSchema = z.discriminatedUnion("type", [
1707
1638
  _rawToolResultRecordSchema,
1708
1639
  _rawCompactionRecordSchema,
1709
1640
  _rawSystemPromptChangedRecordSchema,
1710
- _rawModelChangedRecordSchema,
1711
- _rawThinkingChangedRecordSchema,
1712
- _rawPlanModeChangedRecordSchema,
1713
1641
  _rawToolsChangedRecordSchema,
1714
1642
  _rawSystemReminderRecordSchema,
1715
1643
  _rawNotificationRecordSchema,
1716
- _rawPermissionModeChangedRecordSchema,
1717
1644
  _rawToolDeniedRecordSchema,
1718
1645
  _rawStepBeginRecordSchema,
1719
1646
  _rawStepEndRecordSchema,
@@ -1730,7 +1657,6 @@ const WireRecordSchema = z.discriminatedUnion("type", [
1730
1657
  _rawOwnershipChangedRecordSchema,
1731
1658
  _rawContextEditRecordSchema,
1732
1659
  _rawContextClearedRecordSchema,
1733
- _rawSessionMetaChangedRecordSchema,
1734
1660
  _rawSessionInitializedRecordSchema
1735
1661
  ]);
1736
1662
  //#endregion
@@ -1849,13 +1775,9 @@ const KNOWN_RECORD_TYPES = new Set([
1849
1775
  "tool_result",
1850
1776
  "compaction",
1851
1777
  "system_prompt_changed",
1852
- "model_changed",
1853
- "thinking_changed",
1854
- "plan_mode_changed",
1855
1778
  "tools_changed",
1856
1779
  "system_reminder",
1857
1780
  "notification",
1858
- "permission_mode_changed",
1859
1781
  "tool_denied",
1860
1782
  "step_begin",
1861
1783
  "step_end",
@@ -1871,8 +1793,7 @@ const KNOWN_RECORD_TYPES = new Set([
1871
1793
  "subagent_failed",
1872
1794
  "ownership_changed",
1873
1795
  "context_edit",
1874
- "context_cleared",
1875
- "session_meta_changed"
1796
+ "context_cleared"
1876
1797
  ]);
1877
1798
  //#endregion
1878
1799
  //#region ../../packages/kimi-core/src/storage/journal/rotation.ts
@@ -2286,6 +2207,15 @@ async function runSoulTurn(config, context, runtime, sink, signal, overrides) {
2286
2207
  if (steps >= maxSteps) throw new MaxStepsExceededError(maxSteps);
2287
2208
  steps += 1;
2288
2209
  const currentStep = steps;
2210
+ const turnId = context.currentTurnId?.() ?? UNKNOWN_TURN_ID;
2211
+ if (config.beforeStep !== void 0) {
2212
+ const beforeStep = await config.beforeStep({
2213
+ turnId,
2214
+ stepNumber: currentStep,
2215
+ context
2216
+ }, signal);
2217
+ if (beforeStep?.block === true) throw new Error(beforeStep.reason ?? `Step ${String(currentStep)} was blocked`);
2218
+ }
2289
2219
  safeEmit(sink, {
2290
2220
  type: "step.begin",
2291
2221
  step: currentStep
@@ -2298,7 +2228,6 @@ async function runSoulTurn(config, context, runtime, sink, signal, overrides) {
2298
2228
  const visibleTools = buildLLMVisibleTools(config.tools, overrides?.activeTools);
2299
2229
  const messages = context.buildMessages();
2300
2230
  const stepUuid = randomUUID();
2301
- const turnId = context.currentTurnId?.() ?? UNKNOWN_TURN_ID;
2302
2231
  const toolCallByProviderId = /* @__PURE__ */ new Map();
2303
2232
  const prefetchedResults = /* @__PURE__ */ new Map();
2304
2233
  await context.appendStepBegin({
@@ -2420,6 +2349,14 @@ async function runSoulTurn(config, context, runtime, sink, signal, overrides) {
2420
2349
  step: currentStep
2421
2350
  });
2422
2351
  const sr = response.stopReason ?? "end_turn";
2352
+ if (config.afterStep !== void 0) try {
2353
+ await config.afterStep({
2354
+ turnId,
2355
+ stepNumber: currentStep,
2356
+ context,
2357
+ stopReason: sr
2358
+ }, signal);
2359
+ } catch {}
2423
2360
  if (sr === "tool_use") continue;
2424
2361
  stopReason = sr;
2425
2362
  break;
@@ -2929,21 +2866,26 @@ function hookDedupeKey(hook) {
2929
2866
  /**
2930
2867
  * Extracts the string fed to a hook's matcher regex. Event-dependent:
2931
2868
  *
2869
+ * - `PreTurn` / `UserPromptSubmit` — the prompt text itself.
2870
+ * - `PostTurn` / `Stop` — the turn reason (`done` / `cancelled` /
2871
+ * `error`), so hooks can filter e.g. `/^error$/`.
2872
+ * - `PreStep` / `PostStep` — the step number as a string.
2932
2873
  * - `PreToolUse` / `PostToolUse` / `OnToolFailure` — tool name
2933
2874
  * (mirrors Python's `matcher_value=toolCall.name` contract).
2934
- * - `UserPromptSubmit` — the prompt text itself (Python parity).
2935
- * - `Stop` — the turn reason (`done` / `cancelled` / `error`), so
2936
- * hooks can filter e.g. `/^error$/`.
2937
2875
  * - `Notification` — the notification type string, so a single hook
2938
2876
  * can subscribe to an entire notification class via regex.
2939
2877
  */
2940
2878
  function extractMatcherValue(input) {
2941
2879
  switch (input.event) {
2880
+ case "PreTurn":
2881
+ case "UserPromptSubmit": return input.prompt;
2882
+ case "PostTurn":
2883
+ case "Stop": return input.reason;
2884
+ case "PreStep":
2885
+ case "PostStep": return String(input.stepNumber);
2942
2886
  case "PreToolUse":
2943
2887
  case "PostToolUse":
2944
2888
  case "OnToolFailure": return input.toolCall.name;
2945
- case "UserPromptSubmit": return input.prompt;
2946
- case "Stop": return input.reason;
2947
2889
  case "Notification": return input.notificationType;
2948
2890
  case "StopFailure": return input.error;
2949
2891
  case "SubagentStart":
@@ -2953,6 +2895,7 @@ function extractMatcherValue(input) {
2953
2895
  case "PreCompact":
2954
2896
  case "PostCompact": return "";
2955
2897
  }
2898
+ return input;
2956
2899
  }
2957
2900
  const AgentToolInputSchema = z.preprocess((input) => {
2958
2901
  if (typeof input !== "object" || input === null || Array.isArray(input)) return input;
@@ -3252,7 +3195,7 @@ var EnterPlanModeTool = class {
3252
3195
  isError: true,
3253
3196
  content: "Plan mode is already active. Use ExitPlanMode when the plan is ready."
3254
3197
  };
3255
- if (this.deps.isYoloMode()) {
3198
+ if (this.deps.isYoloMode() || this.deps.shouldAutoApprove?.() === true) {
3256
3199
  try {
3257
3200
  await this.deps.setPlanMode(true);
3258
3201
  } catch (error) {
@@ -3588,15 +3531,10 @@ var CompactionOrchestrator = class {
3588
3531
  const summary = await (this.deps.runtimeSlot?.current().compactionProvider ?? this.deps.compactionProvider).run(messages, signal, customInstruction !== void 0 ? { userInstructions: customInstruction } : void 0);
3589
3532
  signal.throwIfAborted();
3590
3533
  const baselineInit = await this.deps.journalCapability.readSessionInitialized();
3591
- const runtime = this.deps.runtimeStateProvider();
3592
3534
  const cs = this.deps.contextState;
3593
3535
  const sessionInitialized = applyRuntimeOverlay(baselineInit, {
3594
3536
  system_prompt: cs.systemPrompt,
3595
- model: cs.model,
3596
- active_tools: [...cs.activeTools],
3597
- permission_mode: runtime.permissionMode,
3598
- plan_mode: runtime.planMode,
3599
- thinking_level: runtime.thinkingLevel
3537
+ active_tools: [...cs.activeTools]
3600
3538
  });
3601
3539
  await this.deps.journalWriter.flush();
3602
3540
  const rotateResult = await this.deps.journalCapability.rotate({
@@ -3663,16 +3601,15 @@ var CompactionOrchestrator = class {
3663
3601
  };
3664
3602
  /**
3665
3603
  * Phase 23 fix — overlay the runtime-mutable subset of a baseline
3666
- * `session_initialized` with the live ContextState + TurnManager state.
3604
+ * `session_initialized` with the live wire-owned ContextState baseline.
3667
3605
  *
3668
3606
  * Identity-class fields (`type`, `seq`, `time`, `agent_type`, `session_id`,
3669
- * `agent_id`, parent lineage, `workspace_dir`) are preserved verbatim
3607
+ * `agent_id`, parent lineage) are preserved verbatim
3670
3608
  * because they cannot legally mutate at runtime — see the discriminated-union
3671
- * shape in `wire-record.ts`. The mutable fields (`system_prompt`, `model`,
3672
- * `active_tools`, `permission_mode`, `plan_mode`, `thinking_level`) are
3673
- * overwritten so the post-rotate baseline reflects the compaction-time
3674
- * snapshot rather than the original startup config. `thinking_level` is
3675
- * mutable: `session.setThinking` (Phase 24 24b) changes it at runtime.
3609
+ * shape in `wire-record.ts`. The mutable wire-owned fields
3610
+ * (`system_prompt`, `active_tools`) are overwritten so the post-rotate
3611
+ * baseline reflects the compaction-time snapshot rather than the original
3612
+ * startup config. Runtime/session fields live in state.json.
3676
3613
  *
3677
3614
  * The generic preserves the discriminated-union narrowing so each branch
3678
3615
  * (main / sub / independent) returns its own concrete type.
@@ -3681,11 +3618,7 @@ function applyRuntimeOverlay(baseline, overlay) {
3681
3618
  return {
3682
3619
  ...baseline,
3683
3620
  system_prompt: overlay.system_prompt,
3684
- model: overlay.model,
3685
- active_tools: [...overlay.active_tools],
3686
- permission_mode: overlay.permission_mode,
3687
- plan_mode: overlay.plan_mode,
3688
- ...overlay.thinking_level !== void 0 ? { thinking_level: overlay.thinking_level } : {}
3621
+ active_tools: [...overlay.active_tools]
3689
3622
  };
3690
3623
  }
3691
3624
  /**
@@ -4449,11 +4382,13 @@ var DefaultSessionControl = class {
4449
4382
  contextState;
4450
4383
  sessionJournal;
4451
4384
  setPlanModeOverride;
4385
+ setYoloOverride;
4452
4386
  constructor(deps) {
4453
4387
  this.turnManager = deps.turnManager;
4454
4388
  this.contextState = deps.contextState;
4455
4389
  this.sessionJournal = deps.sessionJournal;
4456
4390
  this.setPlanModeOverride = deps.setPlanModeOverride;
4391
+ this.setYoloOverride = deps.setYoloOverride;
4457
4392
  }
4458
4393
  async compact(customInstruction) {
4459
4394
  await this.turnManager.triggerCompaction(customInstruction);
@@ -4478,18 +4413,15 @@ var DefaultSessionControl = class {
4478
4413
  this.turnManager.setPlanMode(enabled);
4479
4414
  }
4480
4415
  async setYolo(enabled) {
4416
+ if (this.setYoloOverride !== void 0) {
4417
+ await this.setYoloOverride(enabled);
4418
+ return;
4419
+ }
4481
4420
  const previousMode = this.turnManager.getPermissionMode();
4482
4421
  const newMode = enabled ? "bypassPermissions" : "default";
4483
4422
  if (previousMode === newMode) return;
4484
4423
  this.turnManager.setPermissionMode(newMode);
4485
- await this.sessionJournal.appendPermissionModeChanged({
4486
- type: "permission_mode_changed",
4487
- data: {
4488
- from: previousMode,
4489
- to: newMode,
4490
- reason: enabled ? "/yolo on" : "/yolo off"
4491
- }
4492
- });
4424
+ this.sessionJournal;
4493
4425
  }
4494
4426
  };
4495
4427
  //#endregion
@@ -4591,19 +4523,16 @@ var SessionMetaService = class {
4591
4523
  }
4592
4524
  }
4593
4525
  async applyPatch(patch, source, reason) {
4594
- await this.deps.sessionJournal.appendSessionMetaChanged({
4595
- type: "session_meta_changed",
4596
- patch: toWirePatch(patch),
4597
- source,
4598
- ...reason !== void 0 ? { reason } : {}
4599
- });
4600
- if (patch.title !== void 0) this.meta.title = patch.title;
4601
- if (patch.tags !== void 0) this.meta.tags = [...patch.tags];
4602
- if (patch.description !== void 0) this.meta.description = patch.description;
4603
- if (patch.archived !== void 0) this.meta.archived = patch.archived;
4604
- if (patch.color !== void 0) this.meta.color = patch.color;
4605
- if (patch.plan_slug !== void 0) this.meta.plan_slug = patch.plan_slug;
4606
- this.meta.last_updated = Date.now();
4526
+ const next = cloneMeta(this.meta);
4527
+ if (patch.title !== void 0) next.title = patch.title;
4528
+ if (patch.tags !== void 0) next.tags = [...patch.tags];
4529
+ if (patch.description !== void 0) next.description = patch.description;
4530
+ if (patch.archived !== void 0) next.archived = patch.archived;
4531
+ if (patch.color !== void 0) next.color = patch.color;
4532
+ if (patch.plan_slug !== void 0) next.plan_slug = patch.plan_slug;
4533
+ next.last_updated = Date.now();
4534
+ await this.flushStateJson(next);
4535
+ this.meta = next;
4607
4536
  this.deps.eventBus.emit({
4608
4537
  type: "session_meta.changed",
4609
4538
  data: {
@@ -4636,7 +4565,7 @@ var SessionMetaService = class {
4636
4565
  if (this.flushTimer !== null) return;
4637
4566
  this.flushTimer = setTimeout(() => {
4638
4567
  this.flushTimer = null;
4639
- this.flushStateJson().catch(() => {});
4568
+ this.flushStateJson(this.meta).catch(() => {});
4640
4569
  }, this.flushDebounceMs);
4641
4570
  this.flushTimer.unref?.();
4642
4571
  }
@@ -4651,25 +4580,27 @@ var SessionMetaService = class {
4651
4580
  * because createSession / closeSession / plan_mode / yolo all need
4652
4581
  * to migrate off direct state.json writes first.
4653
4582
  */
4654
- async flushStateJson() {
4655
- const next = {
4656
- ...await this.deps.stateCache.read() ?? {
4657
- session_id: this.meta.session_id,
4658
- created_at: this.meta.created_at,
4659
- updated_at: this.meta.last_updated
4583
+ async flushStateJson(meta = this.meta) {
4584
+ await this.deps.stateCache.update((existing) => ({
4585
+ ...existing ?? {
4586
+ session_id: meta.session_id,
4587
+ created_at: meta.created_at,
4588
+ updated_at: meta.last_updated
4660
4589
  },
4661
- session_id: this.meta.session_id,
4662
- created_at: this.meta.created_at,
4663
- updated_at: this.meta.last_updated,
4664
- ...this.meta.title !== void 0 ? { custom_title: this.meta.title } : {},
4665
- ...this.meta.tags !== void 0 ? { tags: [...this.meta.tags] } : {},
4666
- ...this.meta.description !== void 0 ? { description: this.meta.description } : {},
4667
- ...this.meta.archived !== void 0 ? { archived: this.meta.archived } : {},
4668
- ...this.meta.last_model !== void 0 ? { model: this.meta.last_model } : {},
4669
- ...this.meta.plan_slug !== void 0 ? { plan_slug: this.meta.plan_slug } : {},
4670
- ...this.meta.producer !== void 0 ? { producer: { ...this.meta.producer } } : {}
4671
- };
4672
- await this.deps.stateCache.write(next);
4590
+ session_id: meta.session_id,
4591
+ created_at: meta.created_at,
4592
+ updated_at: meta.last_updated,
4593
+ ...meta.title !== void 0 ? { custom_title: meta.title } : {},
4594
+ ...meta.tags !== void 0 ? { tags: [...meta.tags] } : {},
4595
+ ...meta.description !== void 0 ? { description: meta.description } : {},
4596
+ ...meta.archived !== void 0 ? { archived: meta.archived } : {},
4597
+ ...meta.color !== void 0 ? { color: meta.color } : {},
4598
+ ...meta.last_model !== void 0 ? { model: meta.last_model } : {},
4599
+ turn_count: meta.turn_count,
4600
+ ...meta.plan_slug !== void 0 ? { plan_slug: meta.plan_slug } : {},
4601
+ ...meta.producer !== void 0 ? { producer: { ...meta.producer } } : {},
4602
+ last_exit_code: "dirty"
4603
+ }));
4673
4604
  }
4674
4605
  };
4675
4606
  function cloneMeta(meta) {
@@ -5258,11 +5189,7 @@ async function runSubagentTurn(deps, agentId, request, signal) {
5258
5189
  parent_tool_call_id: request.parentToolCallId,
5259
5190
  run_in_background: request.runInBackground ?? false,
5260
5191
  system_prompt: childSystemPrompt ?? "",
5261
- model: childModel,
5262
- active_tools: childActiveTools,
5263
- permission_mode: "default",
5264
- plan_mode: false,
5265
- workspace_dir: deps.workDir ?? process.cwd()
5192
+ active_tools: childActiveTools
5266
5193
  };
5267
5194
  await childJournalWriter.append(subInitInput);
5268
5195
  childContext = new WiredContextState({
@@ -19848,6 +19775,49 @@ OpenAI.Containers = Containers;
19848
19775
  OpenAI.Skills = Skills$1;
19849
19776
  OpenAI.Videos = Videos;
19850
19777
  //#endregion
19778
+ //#region ../../packages/kimi-core/src/hooks/execution-pipeline.ts
19779
+ const ALLOW = Object.freeze({
19780
+ blockAction: false,
19781
+ additionalContext: []
19782
+ });
19783
+ var ExecutionHookBlockedError = class extends Error {
19784
+ event;
19785
+ reason;
19786
+ constructor(event, reason) {
19787
+ super(reason !== void 0 ? `${event} hook blocked: ${reason}` : `${event} hook blocked`);
19788
+ this.name = "ExecutionHookBlockedError";
19789
+ this.event = event;
19790
+ this.reason = reason;
19791
+ }
19792
+ };
19793
+ /**
19794
+ * Awaited execution-hook adapter.
19795
+ *
19796
+ * This sits on the execution side of the architecture, separate from the
19797
+ * wire-event publication pipeline. Pre hooks can block real work before it
19798
+ * happens; post hooks can still run in order without being able to rewrite
19799
+ * already-durable facts unless their caller explicitly interprets a result.
19800
+ */
19801
+ var ExecutionHookPipeline = class {
19802
+ hookEngine;
19803
+ constructor(options) {
19804
+ this.hookEngine = options.hookEngine;
19805
+ }
19806
+ async run(input, signal) {
19807
+ if (this.hookEngine === void 0) return ALLOW;
19808
+ return this.hookEngine.executeHooks(input.event, input, signal);
19809
+ }
19810
+ async runBlocking(input, signal) {
19811
+ const result = await this.run(input, signal);
19812
+ if (result.blockAction) throw new ExecutionHookBlockedError(input.event, result.reason);
19813
+ return result;
19814
+ }
19815
+ dispatch(input) {
19816
+ const controller = new AbortController();
19817
+ this.run(input, controller.signal).catch(() => {});
19818
+ }
19819
+ };
19820
+ //#endregion
19851
19821
  //#region ../../packages/kimi-core/src/soul-plus/capability-check.ts
19852
19822
  function checkLLMCapabilities(opts) {
19853
19823
  if (opts.model === "") return;
@@ -19944,7 +19914,10 @@ var TurnManager = class {
19944
19914
  runtimeSlot;
19945
19915
  compactionConfig;
19946
19916
  sessionId;
19917
+ executionHooks;
19947
19918
  activeToolExecutionScope;
19919
+ pendingLaunchController;
19920
+ pendingLaunchDrain;
19948
19921
  /**
19949
19922
  * Phase 18 A.13 — terminal reason per turn id for callers that
19950
19923
  * observe the turn lifecycle out-of-band (after the `end` event
@@ -19972,6 +19945,7 @@ var TurnManager = class {
19972
19945
  this.runtimeSlot = deps.runtimeSlot;
19973
19946
  this.compactionConfig = deps.compactionConfig;
19974
19947
  this.sessionId = deps.sessionId ?? "unknown";
19948
+ this.executionHooks = new ExecutionHookPipeline({ hookEngine: deps.hookEngine });
19975
19949
  }
19976
19950
  setPermissionMode(mode) {
19977
19951
  this.permissionMode = mode;
@@ -20154,6 +20128,13 @@ var TurnManager = class {
20154
20128
  };
20155
20129
  const scope = this.activeToolExecutionScope;
20156
20130
  this.deps.approvalRuntime?.cancelBySource(source);
20131
+ if (this.pendingLaunchTurnId === turnId && this.pendingLaunchController !== void 0) {
20132
+ this.pendingLaunchController.abort();
20133
+ try {
20134
+ await this.pendingLaunchDrain;
20135
+ } catch {}
20136
+ return;
20137
+ }
20157
20138
  scope?.discardStreaming?.("aborted");
20158
20139
  await this.deps.lifecycle.cancelTurn(turnId);
20159
20140
  scope?.drainPrefetched?.();
@@ -20178,8 +20159,16 @@ var TurnManager = class {
20178
20159
  if (mismatch !== void 0) throw mismatch;
20179
20160
  }
20180
20161
  const turnId = this.deps.lifecycle.allocateTurnId();
20162
+ const pendingLaunchController = new AbortController();
20181
20163
  this.pendingLaunchTurnId = turnId;
20164
+ this.pendingLaunchController = pendingLaunchController;
20165
+ let releasePendingLaunch;
20166
+ this.pendingLaunchDrain = new Promise((resolve) => {
20167
+ releasePendingLaunch = resolve;
20168
+ });
20182
20169
  try {
20170
+ await this.executePreTurnHook(turnId, input, pendingLaunchController.signal);
20171
+ pendingLaunchController.signal.throwIfAborted();
20183
20172
  await this.deps.sessionJournal.appendTurnBegin({
20184
20173
  type: "turn_begin",
20185
20174
  turn_id: turnId,
@@ -20210,17 +20199,23 @@ var TurnManager = class {
20210
20199
  };
20211
20200
  this.launchTurn(turnId, trigger);
20212
20201
  this.pendingLaunchTurnId = void 0;
20202
+ this.pendingLaunchController = void 0;
20203
+ this.pendingLaunchDrain = void 0;
20204
+ releasePendingLaunch();
20213
20205
  return {
20214
20206
  turn_id: turnId,
20215
20207
  status: "started"
20216
20208
  };
20217
20209
  } catch (error) {
20218
20210
  this.pendingLaunchTurnId = void 0;
20211
+ this.pendingLaunchController = void 0;
20212
+ this.pendingLaunchDrain = void 0;
20213
+ releasePendingLaunch();
20219
20214
  throw error;
20220
20215
  }
20221
20216
  }
20222
20217
  async handleCancel(req) {
20223
- const requestedId = req.data.turn_id ?? this.deps.lifecycle.getCurrentTurnId();
20218
+ const requestedId = req.data.turn_id ?? this.getCurrentTurnId();
20224
20219
  if (requestedId === void 0) return { ok: true };
20225
20220
  await this.abortTurn(requestedId, "dispatch-cancel");
20226
20221
  return { ok: true };
@@ -20258,16 +20253,27 @@ var TurnManager = class {
20258
20253
  const parsed = Number.parseInt(match[1] ?? "1", 10);
20259
20254
  return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
20260
20255
  }
20256
+ async executePreTurnHook(turnId, input, signal) {
20257
+ try {
20258
+ await this.executionHooks.runBlocking({
20259
+ event: "PreTurn",
20260
+ sessionId: this.sessionId,
20261
+ turnId,
20262
+ agentId: this.agentId,
20263
+ prompt: input.text,
20264
+ inputKind: "user"
20265
+ }, signal);
20266
+ } catch (error) {
20267
+ if (error instanceof ExecutionHookBlockedError) throw error;
20268
+ }
20269
+ }
20261
20270
  /**
20262
- * Fire-and-forget dispatch of a lifecycle hook event. Tool-scoped
20271
+ * Fire-and-forget dispatch for legacy lifecycle hook events. Tool-scoped
20263
20272
  * events go through the actor-local `ToolExecutionScope`; this helper
20264
- * only covers the lifecycle events TurnManager owns.
20273
+ * only covers compatibility events TurnManager owns.
20265
20274
  */
20266
20275
  dispatchLifecycleHook(input) {
20267
- const engine = this.deps.hookEngine;
20268
- if (engine === void 0) return;
20269
- const controller = new AbortController();
20270
- engine.executeHooks(input.event, input, controller.signal).catch(() => {});
20276
+ this.executionHooks.dispatch(input);
20271
20277
  }
20272
20278
  launchTurn(turnId, trigger) {
20273
20279
  const controller = this.deps.soulRegistry.getOrCreate("main").abortController;
@@ -20312,6 +20318,32 @@ var TurnManager = class {
20312
20318
  compactionConfig,
20313
20319
  contextWindow: compactionConfig.maxContextSize
20314
20320
  };
20321
+ if (this.deps.hookEngine !== void 0) soulConfig = {
20322
+ ...soulConfig,
20323
+ beforeStep: async (ctx, signal) => {
20324
+ const result = await this.executionHooks.run({
20325
+ event: "PreStep",
20326
+ sessionId: this.sessionId,
20327
+ turnId: ctx.turnId,
20328
+ stepNumber: ctx.stepNumber,
20329
+ agentId: this.agentId
20330
+ }, signal);
20331
+ if (result.blockAction) return {
20332
+ block: true,
20333
+ ...result.reason !== void 0 ? { reason: result.reason } : {}
20334
+ };
20335
+ },
20336
+ afterStep: async (ctx, signal) => {
20337
+ await this.executionHooks.run({
20338
+ event: "PostStep",
20339
+ sessionId: this.sessionId,
20340
+ turnId: ctx.turnId,
20341
+ stepNumber: ctx.stepNumber,
20342
+ agentId: this.agentId,
20343
+ stopReason: ctx.stopReason
20344
+ }, signal);
20345
+ }
20346
+ };
20315
20347
  const input = trigger.input;
20316
20348
  const runPromise = this.runTurn(turnId, input, soulConfig, runtimeForTurn, controller.signal);
20317
20349
  runPromise.catch(() => {});
@@ -20418,6 +20450,16 @@ var TurnManager = class {
20418
20450
  ...result.usage.cache_write !== void 0 ? { cache_write_tokens: result.usage.cache_write } : {}
20419
20451
  };
20420
20452
  await this.deps.sessionJournal.appendTurnEnd(turnEnd);
20453
+ try {
20454
+ await this.executionHooks.run({
20455
+ event: "PostTurn",
20456
+ sessionId: this.sessionId,
20457
+ turnId,
20458
+ agentId: this.agentId,
20459
+ reason,
20460
+ success: reason === "done"
20461
+ }, new AbortController().signal);
20462
+ } catch {}
20421
20463
  this.dispatchLifecycleHook({
20422
20464
  event: "Stop",
20423
20465
  sessionId: this.sessionId,
@@ -20670,6 +20712,7 @@ var SoulPlus = class {
20670
20712
  hostToolNames;
20671
20713
  planController;
20672
20714
  setPlanModeImpl;
20715
+ stateCache;
20673
20716
  /**
20674
20717
  * Phase 18 A.3–A.6 — lazy `SessionControlHandler` owned by the
20675
20718
  * facade. External hosts (SessionManager) still construct their own
@@ -20681,6 +20724,7 @@ var SoulPlus = class {
20681
20724
  sessionControlInstance;
20682
20725
  constructor(deps) {
20683
20726
  this.sessionId = deps.sessionId;
20727
+ this.stateCache = deps.stateCache;
20684
20728
  const eventBus = deps.eventBus;
20685
20729
  const toolRegistry = [...deps.tools];
20686
20730
  const enabledToolNames = deps.enabledToolNames !== void 0 ? new Set(deps.enabledToolNames) : void 0;
@@ -20703,7 +20747,6 @@ var SoulPlus = class {
20703
20747
  const approvalRuntime = deps.approvalRuntime;
20704
20748
  const sessionMeta = deps.stateCache !== void 0 && deps.initialMeta !== void 0 ? new SessionMetaService({
20705
20749
  sessionId: deps.sessionId,
20706
- sessionJournal,
20707
20750
  eventBus,
20708
20751
  stateCache: deps.stateCache,
20709
20752
  initialMeta: deps.initialMeta
@@ -20838,6 +20881,19 @@ var SoulPlus = class {
20838
20881
  const setPlanMode = async (enabled) => {
20839
20882
  if (enabled) {
20840
20883
  await planController.ensurePlanFilePath();
20884
+ await deps.stateCache?.update((current) => {
20885
+ const now = Date.now();
20886
+ return {
20887
+ ...current ?? {
20888
+ session_id: deps.sessionId,
20889
+ created_at: now,
20890
+ updated_at: now
20891
+ },
20892
+ plan_mode: true,
20893
+ updated_at: now,
20894
+ last_exit_code: "dirty"
20895
+ };
20896
+ });
20841
20897
  await contextState.applyConfigChange({
20842
20898
  type: "plan_mode_changed",
20843
20899
  enabled
@@ -20845,6 +20901,19 @@ var SoulPlus = class {
20845
20901
  turnManager.setPlanMode(enabled);
20846
20902
  return;
20847
20903
  }
20904
+ await deps.stateCache?.update((current) => {
20905
+ const now = Date.now();
20906
+ return {
20907
+ ...current ?? {
20908
+ session_id: deps.sessionId,
20909
+ created_at: now,
20910
+ updated_at: now
20911
+ },
20912
+ plan_mode: false,
20913
+ updated_at: now,
20914
+ last_exit_code: "dirty"
20915
+ };
20916
+ });
20848
20917
  await contextState.applyConfigChange({
20849
20918
  type: "plan_mode_changed",
20850
20919
  enabled
@@ -20862,6 +20931,7 @@ var SoulPlus = class {
20862
20931
  isPlanModeActive: () => turnManager.getPlanMode(),
20863
20932
  setPlanMode,
20864
20933
  isYoloMode: () => turnManager.getPermissionMode() === "bypassPermissions",
20934
+ shouldAutoApprove: () => true,
20865
20935
  requestApproval: enterPlanRequestApproval,
20866
20936
  getPlanFilePath: () => planController.getPlanFilePath()
20867
20937
  }));
@@ -21098,19 +21168,49 @@ var SoulPlus = class {
21098
21168
  turnManager: this.components.turnManager,
21099
21169
  contextState: this.journal.contextState,
21100
21170
  sessionJournal: this.journal.sessionJournal,
21101
- setPlanModeOverride: (enabled) => this.setPlanMode(enabled)
21171
+ setPlanModeOverride: (enabled) => this.setPlanMode(enabled),
21172
+ setYoloOverride: async (enabled) => {
21173
+ const permissionMode = enabled ? "bypassPermissions" : "default";
21174
+ await this.stateCache?.update((current) => {
21175
+ const now = Date.now();
21176
+ return {
21177
+ ...current ?? {
21178
+ session_id: this.sessionId,
21179
+ created_at: now,
21180
+ updated_at: now
21181
+ },
21182
+ permission_mode: permissionMode,
21183
+ updated_at: now,
21184
+ last_exit_code: "dirty"
21185
+ };
21186
+ });
21187
+ this.components.turnManager.setPermissionMode(permissionMode);
21188
+ }
21102
21189
  });
21103
21190
  return this.sessionControlInstance;
21104
21191
  }
21105
21192
  /**
21106
21193
  * Phase 18 A.3 — programmatic model change. Applies the config
21107
- * change event to ContextState (WAL `model_changed`) and emits a
21194
+ * change event to ContextState, persists state.json, and emits a
21108
21195
  * fresh `status.update` so downstream observers pick up the new
21109
21196
  * model without waiting for the next turn boundary.
21110
21197
  */
21111
21198
  async setModel(model) {
21112
21199
  const oldModel = this.journal.contextState.model;
21113
21200
  if (oldModel === model) return;
21201
+ await this.stateCache?.update((current) => {
21202
+ const now = Date.now();
21203
+ return {
21204
+ ...current ?? {
21205
+ session_id: this.sessionId,
21206
+ created_at: now,
21207
+ updated_at: now
21208
+ },
21209
+ model,
21210
+ updated_at: now,
21211
+ last_exit_code: "dirty"
21212
+ };
21213
+ });
21114
21214
  await this.journal.contextState.applyConfigChange({
21115
21215
  type: "model_changed",
21116
21216
  old_model: oldModel,
@@ -21127,8 +21227,7 @@ var SoulPlus = class {
21127
21227
  }
21128
21228
  /**
21129
21229
  * Phase 18 A.6 / Phase 21 §A — programmatic thinking-level change.
21130
- * Journals a `thinking_changed` WAL record (via
21131
- * ContextState.applyConfigChange) and emits two SoulEvents:
21230
+ * Persists state.json, updates ContextState, and emits two SoulEvents:
21132
21231
  * 1. `thinking.changed` — distinct event the wire bridge maps to a
21133
21232
  * `thinking.changed` wire event with the bridge's per-session
21134
21233
  * `seq` counter (replaces the old `seq: 0` direct send from
@@ -21137,6 +21236,19 @@ var SoulPlus = class {
21137
21236
  * can repaint without subscribing to the dedicated event.
21138
21237
  */
21139
21238
  async setThinking(level) {
21239
+ await this.stateCache?.update((current) => {
21240
+ const now = Date.now();
21241
+ return {
21242
+ ...current ?? {
21243
+ session_id: this.sessionId,
21244
+ created_at: now,
21245
+ updated_at: now
21246
+ },
21247
+ thinking_level: level,
21248
+ updated_at: now,
21249
+ last_exit_code: "dirty"
21250
+ };
21251
+ });
21140
21252
  await this.journal.contextState.applyConfigChange({
21141
21253
  type: "thinking_changed",
21142
21254
  level
@@ -21288,17 +21400,35 @@ var KosongAdapter = class {
21288
21400
  }));
21289
21401
  const onDelta = params.onDelta;
21290
21402
  const onThinkDelta = params.onThinkDelta;
21403
+ const onToolCallPart = params.onToolCallPart;
21291
21404
  const onAtomicPart = params.onAtomicPart;
21292
- const needMessagePart = onDelta !== void 0 || onThinkDelta !== void 0;
21405
+ const needMessagePart = onDelta !== void 0 || onThinkDelta !== void 0 || onToolCallPart !== void 0;
21406
+ const toolCallsByStreamIndex = /* @__PURE__ */ new Map();
21407
+ let lastToolCall;
21293
21408
  let result;
21294
21409
  try {
21295
21410
  result = await generate$1(activeProvider, params.systemPrompt, kosongTools, params.messages, needMessagePart ? { onMessagePart: async (part) => {
21296
21411
  if (part.type === "text" && onDelta !== void 0) onDelta(part.text);
21297
21412
  else if (part.type === "think" && onThinkDelta !== void 0) onThinkDelta(part.think);
21413
+ else if (part.type === "function") {
21414
+ lastToolCall = {
21415
+ id: part.id,
21416
+ name: part.function.name
21417
+ };
21418
+ if (part._streamIndex !== void 0) toolCallsByStreamIndex.set(part._streamIndex, lastToolCall);
21419
+ } else if (isToolCallPart(part) && onToolCallPart !== void 0) {
21420
+ const target = part.index !== void 0 ? toolCallsByStreamIndex.get(part.index) : lastToolCall;
21421
+ if (target !== void 0) onToolCallPart({
21422
+ type: "tool_call_part",
21423
+ tool_call_id: target.id,
21424
+ name: target.name,
21425
+ ...part.argumentsPart !== null ? { arguments_chunk: part.argumentsPart } : {}
21426
+ });
21427
+ }
21298
21428
  } } : void 0, { signal: params.signal });
21299
- } catch (err) {
21300
- if (isContextOverflowProviderError(err)) throw new ContextOverflowError(extractMessage(err));
21301
- throw err;
21429
+ } catch (error) {
21430
+ if (isContextOverflowProviderError(error)) throw new ContextOverflowError(extractMessage(error));
21431
+ throw error;
21302
21432
  }
21303
21433
  if (onAtomicPart !== void 0) {
21304
21434
  for (const part of result.message.content) if (part.type === "text" || part.type === "think") await onAtomicPart({
@@ -26470,7 +26600,7 @@ K(Un, Hn, Wn, Gn, (s, t) => {
26470
26600
  if (!t?.length) throw new TypeError("no paths specified to add to archive");
26471
26601
  });
26472
26602
  var fr = (process.env.__FAKE_PLATFORM__ || process.platform) === "win32", { O_CREAT: dr, O_NOFOLLOW: ar, O_TRUNC: ur, O_WRONLY: mr } = I.constants, pr = Number(process.env.__FAKE_FS_O_FILENAME__) || I.constants.UV_FS_O_FILEMAP || 0, Kn = fr && !!pr, Vn = 512 * 1024, $n = pr | ur | dr | mr, lr = !fr && typeof ar == "number" ? ar | ur | dr | mr : null, cs = lr !== null ? () => lr : Kn ? (s) => s < Vn ? $n : "w" : () => "w";
26473
- var fs$12 = (s, t, e) => {
26603
+ var fs$13 = (s, t, e) => {
26474
26604
  try {
26475
26605
  return Vt.lchownSync(s, t, e);
26476
26606
  } catch (i) {
@@ -26502,7 +26632,7 @@ var fs$12 = (s, t, e) => {
26502
26632
  for (let l of n) Xn(s, l, t, e, a);
26503
26633
  });
26504
26634
  }, qn = (s, t, e, i) => {
26505
- t.isDirectory() && us(path.resolve(s, t.name), e, i), fs$12(path.resolve(s, t.name), e, i);
26635
+ t.isDirectory() && us(path.resolve(s, t.name), e, i), fs$13(path.resolve(s, t.name), e, i);
26506
26636
  }, us = (s, t, e) => {
26507
26637
  let i;
26508
26638
  try {
@@ -26510,11 +26640,11 @@ var fs$12 = (s, t, e) => {
26510
26640
  } catch (r) {
26511
26641
  let n = r;
26512
26642
  if (n?.code === "ENOENT") return;
26513
- if (n?.code === "ENOTDIR" || n?.code === "ENOTSUP") return fs$12(s, t, e);
26643
+ if (n?.code === "ENOTDIR" || n?.code === "ENOTSUP") return fs$13(s, t, e);
26514
26644
  throw n;
26515
26645
  }
26516
26646
  for (let r of i) qn(s, r, t, e);
26517
- return fs$12(s, t, e);
26647
+ return fs$13(s, t, e);
26518
26648
  };
26519
26649
  var we = class extends Error {
26520
26650
  path;
@@ -26549,7 +26679,7 @@ var Qn = (s, t) => {
26549
26679
  E ? e(E) : x && a ? ds(x, o, h, (xe) => S(xe)) : n ? Vt.chmod(s, r, e) : e();
26550
26680
  };
26551
26681
  if (s === d) return Qn(s, S);
26552
- if (l) return jn.mkdir(s, {
26682
+ if (l) return fs.mkdir(s, {
26553
26683
  mode: r,
26554
26684
  recursive: !0
26555
26685
  }).then((E) => S(null, E ?? void 0), S);
@@ -27308,7 +27438,7 @@ var require_pend = /* @__PURE__ */ __commonJSMin(((exports, module) => {
27308
27438
  //#endregion
27309
27439
  //#region ../../node_modules/.pnpm/yauzl@3.3.0/node_modules/yauzl/fd-slicer.js
27310
27440
  var require_fd_slicer = /* @__PURE__ */ __commonJSMin(((exports) => {
27311
- var fs$11 = __require("fs");
27441
+ var fs$12 = __require("fs");
27312
27442
  var util$7 = __require("util");
27313
27443
  var stream$4 = __require("stream");
27314
27444
  var Readable = stream$4.Readable;
@@ -27333,7 +27463,7 @@ var require_fd_slicer = /* @__PURE__ */ __commonJSMin(((exports) => {
27333
27463
  FdSlicer.prototype.read = function(buffer, offset, length, position, callback) {
27334
27464
  var self = this;
27335
27465
  self.pend.go(function(cb) {
27336
- fs$11.read(self.fd, buffer, offset, length, position, function(err, bytesRead, buffer) {
27466
+ fs$12.read(self.fd, buffer, offset, length, position, function(err, bytesRead, buffer) {
27337
27467
  cb();
27338
27468
  callback(err, bytesRead, buffer);
27339
27469
  });
@@ -27342,7 +27472,7 @@ var require_fd_slicer = /* @__PURE__ */ __commonJSMin(((exports) => {
27342
27472
  FdSlicer.prototype.write = function(buffer, offset, length, position, callback) {
27343
27473
  var self = this;
27344
27474
  self.pend.go(function(cb) {
27345
- fs$11.write(self.fd, buffer, offset, length, position, function(err, written, buffer) {
27475
+ fs$12.write(self.fd, buffer, offset, length, position, function(err, written, buffer) {
27346
27476
  cb();
27347
27477
  callback(err, written, buffer);
27348
27478
  });
@@ -27362,7 +27492,7 @@ var require_fd_slicer = /* @__PURE__ */ __commonJSMin(((exports) => {
27362
27492
  self.refCount -= 1;
27363
27493
  if (self.refCount > 0) return;
27364
27494
  if (self.refCount < 0) throw new Error("invalid unref");
27365
- if (self.autoClose) fs$11.close(self.fd, onCloseDone);
27495
+ if (self.autoClose) fs$12.close(self.fd, onCloseDone);
27366
27496
  function onCloseDone(err) {
27367
27497
  if (err) self.emit("error", err);
27368
27498
  else self.emit("close");
@@ -27393,7 +27523,7 @@ var require_fd_slicer = /* @__PURE__ */ __commonJSMin(((exports) => {
27393
27523
  self.context.pend.go(function(cb) {
27394
27524
  if (self.destroyed) return cb();
27395
27525
  var buffer = Buffer.allocUnsafe(toRead);
27396
- fs$11.read(self.context.fd, buffer, 0, toRead, self.pos, function(err, bytesRead) {
27526
+ fs$12.read(self.context.fd, buffer, 0, toRead, self.pos, function(err, bytesRead) {
27397
27527
  if (err) self.destroy(err);
27398
27528
  else if (bytesRead === 0) {
27399
27529
  self.destroyed = true;
@@ -27439,7 +27569,7 @@ var require_fd_slicer = /* @__PURE__ */ __commonJSMin(((exports) => {
27439
27569
  }
27440
27570
  self.context.pend.go(function(cb) {
27441
27571
  if (self.destroyed) return cb();
27442
- fs$11.write(self.context.fd, buffer, 0, buffer.length, self.pos, function(err, bytes) {
27572
+ fs$12.write(self.context.fd, buffer, 0, buffer.length, self.pos, function(err, bytes) {
27443
27573
  if (err) {
27444
27574
  self.destroy();
27445
27575
  cb();
@@ -29313,6 +29443,28 @@ var SetTodoListTool = class {
29313
29443
  }));
29314
29444
  }
29315
29445
  };
29446
+ //#endregion
29447
+ //#region ../../packages/kimi-core/src/hooks/supported-events.ts
29448
+ const SUPPORTED_HOOK_EVENTS = [
29449
+ "PreTurn",
29450
+ "PostTurn",
29451
+ "PreStep",
29452
+ "PostStep",
29453
+ "PreToolUse",
29454
+ "PostToolUse",
29455
+ "OnToolFailure",
29456
+ "UserPromptSubmit",
29457
+ "Stop",
29458
+ "StopFailure",
29459
+ "Notification",
29460
+ "SubagentStart",
29461
+ "SubagentStop",
29462
+ "SessionStart",
29463
+ "SessionEnd",
29464
+ "PreCompact",
29465
+ "PostCompact"
29466
+ ];
29467
+ new Set(SUPPORTED_HOOK_EVENTS);
29316
29468
  const _rawWireErrorSchema = z.object({
29317
29469
  code: z.number(),
29318
29470
  message: z.string(),
@@ -29505,188 +29657,332 @@ function createWireEvent(options) {
29505
29657
  };
29506
29658
  }
29507
29659
  //#endregion
29660
+ //#region ../../packages/kimi-core/src/wire-protocol/event-pipeline.ts
29661
+ /**
29662
+ * WireEventPipeline is the ordered, awaited publication path from a translated
29663
+ * `WireEventDraft` to one or more event consumers.
29664
+ *
29665
+ * `enqueue()` intentionally returns void: upstream Soul/SoulPlus emitters must
29666
+ * not be back-pressured by transport, telemetry, or debug consumers. The
29667
+ * pipeline drains in the background and awaits each middleware/consumer
29668
+ * internally to preserve per-session event ordering.
29669
+ */
29670
+ var WireEventPipeline = class {
29671
+ sessionId;
29672
+ getEventFilter;
29673
+ middlewares;
29674
+ consumers;
29675
+ queue = [];
29676
+ idleWaiters = [];
29677
+ draining = false;
29678
+ seq = 0;
29679
+ constructor(options) {
29680
+ this.sessionId = options.sessionId;
29681
+ this.getEventFilter = options.getEventFilter;
29682
+ this.consumers = options.consumers ?? [createTransportWireEventConsumer({
29683
+ transport: options.transport,
29684
+ codec: options.codec
29685
+ })];
29686
+ this.middlewares = [
29687
+ createWireEventFilterMiddleware(this.getEventFilter),
29688
+ ...options.middlewares ?? [],
29689
+ this.createFrameMiddleware(),
29690
+ createWireEventConsumerMiddleware(this.consumers)
29691
+ ];
29692
+ }
29693
+ enqueue(draft) {
29694
+ this.queue.push(draft);
29695
+ if (!this.draining) this.drain();
29696
+ }
29697
+ /**
29698
+ * Test / controlled-host helper: resolves once the queue that is currently
29699
+ * known to the pipeline has drained. Normal production callers should use
29700
+ * fire-and-forget `enqueue()`.
29701
+ */
29702
+ async flush() {
29703
+ if (!this.draining && this.queue.length === 0) return;
29704
+ await new Promise((resolve) => {
29705
+ this.idleWaiters.push(resolve);
29706
+ });
29707
+ }
29708
+ async drain() {
29709
+ this.draining = true;
29710
+ let restart = false;
29711
+ try {
29712
+ while (this.queue.length > 0) {
29713
+ const draft = this.queue.shift();
29714
+ if (draft === void 0) continue;
29715
+ await this.processOne(draft);
29716
+ }
29717
+ } finally {
29718
+ this.draining = false;
29719
+ if (this.queue.length > 0) restart = true;
29720
+ else {
29721
+ const waiters = this.idleWaiters.splice(0);
29722
+ for (const resolve of waiters) resolve();
29723
+ }
29724
+ }
29725
+ if (restart) this.drain();
29726
+ }
29727
+ async processOne(draft) {
29728
+ const ctx = {
29729
+ sessionId: this.sessionId,
29730
+ draft
29731
+ };
29732
+ try {
29733
+ await composeWireEventMiddlewares(this.middlewares)(ctx);
29734
+ } catch {}
29735
+ }
29736
+ createFrameMiddleware() {
29737
+ return async (ctx, next) => {
29738
+ ctx.event = createWireEvent({
29739
+ method: ctx.draft.method,
29740
+ sessionId: ctx.sessionId,
29741
+ seq: this.seq++,
29742
+ ...ctx.draft.requestId !== void 0 ? { requestId: ctx.draft.requestId } : {},
29743
+ ...ctx.draft.turnId !== void 0 ? { turnId: ctx.draft.turnId } : {},
29744
+ agentType: ctx.draft.agentType ?? "main",
29745
+ ...ctx.draft.source !== void 0 ? { source: ctx.draft.source } : {},
29746
+ data: ctx.draft.data
29747
+ });
29748
+ await next();
29749
+ };
29750
+ }
29751
+ };
29752
+ function createWireEventFilterMiddleware(getEventFilter) {
29753
+ return async (ctx, next) => {
29754
+ if (getEventFilter !== void 0) {
29755
+ const filter = getEventFilter();
29756
+ if (filter !== void 0 && !filter.has(ctx.draft.method)) return;
29757
+ }
29758
+ await next();
29759
+ };
29760
+ }
29761
+ function createTransportWireEventConsumer(options) {
29762
+ const codec = options.codec ?? new WireCodec();
29763
+ return {
29764
+ name: "transport",
29765
+ async consume(event) {
29766
+ try {
29767
+ await options.transport.send(codec.encode(event));
29768
+ } catch {}
29769
+ }
29770
+ };
29771
+ }
29772
+ function createWireEventConsumerMiddleware(consumers) {
29773
+ return async (ctx) => {
29774
+ if (ctx.event === void 0) return;
29775
+ const snapshot = [...consumers];
29776
+ for (const consumer of snapshot) try {
29777
+ await consumer.consume(ctx.event, {
29778
+ sessionId: ctx.sessionId,
29779
+ draft: ctx.draft
29780
+ });
29781
+ } catch {}
29782
+ };
29783
+ }
29784
+ function composeWireEventMiddlewares(middlewares) {
29785
+ return async (ctx) => {
29786
+ let index = -1;
29787
+ const dispatch = async (i) => {
29788
+ if (i <= index) throw new Error("wire event middleware called next() multiple times");
29789
+ index = i;
29790
+ const middleware = middlewares[i];
29791
+ if (middleware === void 0) return;
29792
+ await middleware(ctx, () => dispatch(i + 1));
29793
+ };
29794
+ await dispatch(0);
29795
+ };
29796
+ }
29797
+ //#endregion
29798
+ //#region ../../packages/kimi-core/src/wire-protocol/event-translators.ts
29799
+ function draft(event, ctx, method, data) {
29800
+ return {
29801
+ method,
29802
+ data,
29803
+ ...ctx.currentTurnId !== void 0 ? { turnId: ctx.currentTurnId } : {},
29804
+ ...event.source !== void 0 ? { source: event.source } : {}
29805
+ };
29806
+ }
29807
+ const BUS_EVENT_TRANSLATORS = {
29808
+ "step.begin": (event, ctx) => {
29809
+ return draft(event, ctx, "step.begin", { step: event.step });
29810
+ },
29811
+ "step.end": (event, ctx) => {
29812
+ return draft(event, ctx, "step.end", { step: event.step });
29813
+ },
29814
+ "step.interrupted": (event, ctx) => {
29815
+ return draft(event, ctx, "step.interrupted", {
29816
+ step: event.step,
29817
+ reason: event.reason
29818
+ });
29819
+ },
29820
+ "content.delta": (event, ctx) => {
29821
+ return draft(event, ctx, "content.delta", {
29822
+ type: "text",
29823
+ text: event.delta
29824
+ });
29825
+ },
29826
+ "thinking.delta": (event, ctx) => {
29827
+ return draft(event, ctx, "content.delta", {
29828
+ type: "thinking",
29829
+ thinking: event.delta
29830
+ });
29831
+ },
29832
+ tool_call_part: (event, ctx) => {
29833
+ return draft(event, ctx, "tool.call.delta", {
29834
+ tool_call_id: event.tool_call_id,
29835
+ ...event.name !== void 0 ? { name: event.name } : {},
29836
+ arguments_part: event.arguments_chunk ?? null
29837
+ });
29838
+ },
29839
+ "tool.call": (event, ctx) => {
29840
+ return draft(event, ctx, "tool.call", {
29841
+ id: event.toolCallId,
29842
+ name: event.name,
29843
+ args: event.args
29844
+ });
29845
+ },
29846
+ "tool.progress": (event, ctx) => draft(event, ctx, "tool.progress", {
29847
+ id: event.toolCallId,
29848
+ update: event.update
29849
+ }),
29850
+ "tool.result": (event, ctx) => {
29851
+ return draft(event, ctx, "tool.result", {
29852
+ tool_call_id: event.toolCallId,
29853
+ output: event.output,
29854
+ ...event.isError !== void 0 ? { is_error: event.isError } : {}
29855
+ });
29856
+ },
29857
+ "compaction.begin": (event, ctx) => {
29858
+ return draft(event, ctx, "compaction.begin", {});
29859
+ },
29860
+ "compaction.end": (event, ctx) => {
29861
+ return draft(event, ctx, "compaction.end", {
29862
+ ...event.tokensBefore !== void 0 ? { tokens_before: event.tokensBefore } : {},
29863
+ ...event.tokensAfter !== void 0 ? { tokens_after: event.tokensAfter } : {}
29864
+ });
29865
+ },
29866
+ "session.error": (event, ctx) => {
29867
+ return draft(event, ctx, "session.error", {
29868
+ error: event.error,
29869
+ ...event.error_type !== void 0 ? { error_type: event.error_type } : {},
29870
+ ...event.retry_after_ms !== void 0 ? { retry_after_ms: event.retry_after_ms } : {},
29871
+ ...event.details !== void 0 ? { details: event.details } : {}
29872
+ });
29873
+ },
29874
+ "hook.triggered": (event, ctx) => draft(event, ctx, "hook.triggered", {
29875
+ event: event.event,
29876
+ matchers: event.matchers,
29877
+ matched_count: event.matched_count
29878
+ }),
29879
+ "hook.resolved": (event, ctx) => draft(event, ctx, "hook.resolved", {
29880
+ hook_id: event.hook_id,
29881
+ outcome: event.outcome
29882
+ }),
29883
+ "status.update": (event, ctx) => draft(event, ctx, "status.update", event.data),
29884
+ "session_meta.changed": (event, ctx) => draft(event, ctx, "session_meta.changed", event.data),
29885
+ "thinking.changed": (event, ctx) => draft(event, ctx, "thinking.changed", { level: event.level }),
29886
+ "skill.invoked": (event, ctx) => draft(event, ctx, "skill.invoked", event.data),
29887
+ "skill.completed": (event, ctx) => draft(event, ctx, "skill.completed", event.data),
29888
+ "mcp.loading": (event, ctx) => draft(event, ctx, "mcp.loading", event.data),
29889
+ "mcp.connected": (event, ctx) => draft(event, ctx, "mcp.connected", event.data),
29890
+ "mcp.disconnected": (event, ctx) => draft(event, ctx, "mcp.disconnected", event.data),
29891
+ "mcp.error": (event, ctx) => draft(event, ctx, "mcp.error", event.data),
29892
+ "mcp.tools_changed": (event, ctx) => draft(event, ctx, "mcp.tools_changed", event.data),
29893
+ "mcp.resources_changed": (event, ctx) => draft(event, ctx, "mcp.resources_changed", event.data),
29894
+ "mcp.auth_required": (event, ctx) => draft(event, ctx, "mcp.auth_required", event.data),
29895
+ "status.update.mcp_status": (event, ctx) => draft(event, ctx, "status.update.mcp_status", event.data),
29896
+ "subagent.spawned": (event, ctx) => draft(event, ctx, "subagent.spawned", event.data),
29897
+ "subagent.completed": (event, ctx) => draft(event, ctx, "subagent.completed", event.data),
29898
+ "subagent.failed": (event, ctx) => draft(event, ctx, "subagent.failed", event.data),
29899
+ "model.changed": () => {},
29900
+ "turn.end": () => {}
29901
+ };
29902
+ function translateBusEvent(event, currentTurnId) {
29903
+ const translator = BUS_EVENT_TRANSLATORS[event.type];
29904
+ return translator(event, currentTurnId !== void 0 ? { currentTurnId } : {});
29905
+ }
29906
+ function translateNotificationEvent(notif) {
29907
+ return {
29908
+ method: "notification",
29909
+ data: notif
29910
+ };
29911
+ }
29912
+ function translateTurnLifecycleEvent(event, currentTurnId) {
29913
+ if (event.kind === "begin") return {
29914
+ draft: {
29915
+ method: "turn.begin",
29916
+ data: {
29917
+ turn_id: event.turnId,
29918
+ user_input: event.userInputParts ?? event.userInput,
29919
+ input_kind: event.inputKind
29920
+ },
29921
+ turnId: event.turnId
29922
+ },
29923
+ nextTurnId: event.turnId
29924
+ };
29925
+ return {
29926
+ draft: {
29927
+ method: "turn.end",
29928
+ data: {
29929
+ turn_id: event.turnId,
29930
+ reason: event.reason,
29931
+ success: event.success,
29932
+ ...event.usage !== void 0 ? { usage: {
29933
+ input_tokens: event.usage.input,
29934
+ output_tokens: event.usage.output,
29935
+ ...event.usage.cache_read !== void 0 ? { cache_read_tokens: event.usage.cache_read } : {},
29936
+ ...event.usage.cache_write !== void 0 ? { cache_write_tokens: event.usage.cache_write } : {}
29937
+ } } : {}
29938
+ },
29939
+ turnId: event.turnId
29940
+ },
29941
+ ...currentTurnId === event.turnId && currentTurnId !== void 0 ? {} : currentTurnId !== void 0 ? { nextTurnId: currentTurnId } : {}
29942
+ };
29943
+ }
29944
+ //#endregion
29508
29945
  //#region ../../packages/kimi-core/src/wire-protocol/event-bridge.ts
29509
29946
  /**
29510
- * WireEventBridge — SoulEvent + TurnLifecycleEvent → WireEvent translator
29947
+ * WireEventBridge — SoulEvent + TurnLifecycleEvent → WireEventDraft adapter
29511
29948
  * (Phase 17 §A.1).
29512
29949
  *
29513
- * Subscribes to two input channels (SessionEventBus + TurnLifecycleTracker)
29514
- * and forwards translated frames through a provided transport `send`.
29515
- * Each bridge instance owns its own `seq` counter and `currentTurnId`
29516
- * pointer multi-session safe (fixes the single-ref aliasing in the
29517
- * Phase 10 test helper).
29950
+ * Subscribes to three input channels (SessionEventBus + notification channel
29951
+ * + TurnLifecycleTracker) and forwards translated drafts through a
29952
+ * per-session WireEventPipeline. Each bridge instance owns its own
29953
+ * `currentTurnId` pointer; the pipeline owns the per-session wire `seq`.
29518
29954
  *
29519
29955
  * Invariants (§5.0):
29520
- * - L4: listeners return `void`; the bridge never awaits `transport.send`
29956
+ * - L4: listeners return `void`; the bridge never awaits pipeline drain
29521
29957
  * - L5: listener does not touch wire.jsonl — translation is write-only
29522
29958
  * toward the transport
29523
29959
  */
29524
- const codec = new WireCodec();
29525
29960
  function installWireEventBridge(opts) {
29526
29961
  const { server, eventBus, addTurnLifecycleListener, sessionId, getEventFilter } = opts;
29527
- let seq = 0;
29528
29962
  let currentTurnId;
29529
- const sendWire = (method, data, turnId, source) => {
29530
- if (getEventFilter !== void 0) {
29531
- const filter = getEventFilter();
29532
- if (filter !== void 0 && !filter.has(method)) return;
29533
- }
29534
- const frame = codec.encode(createWireEvent({
29535
- method,
29536
- sessionId,
29537
- seq: seq++,
29538
- ...turnId !== void 0 ? { turnId } : {},
29539
- agentType: "main",
29540
- ...source !== void 0 ? { source } : {},
29541
- data
29542
- }));
29543
- server.send(frame).catch(() => {});
29544
- };
29963
+ const pipeline = new WireEventPipeline({
29964
+ sessionId,
29965
+ transport: server,
29966
+ ...getEventFilter !== void 0 ? { getEventFilter } : {}
29967
+ });
29545
29968
  const soulListener = (event) => {
29546
- switch (event.type) {
29547
- case "step.begin":
29548
- sendWire("step.begin", { step: event.step }, currentTurnId, event.source);
29549
- return;
29550
- case "step.end":
29551
- sendWire("step.end", { step: event.step }, currentTurnId, event.source);
29552
- return;
29553
- case "step.interrupted":
29554
- sendWire("step.interrupted", {
29555
- step: event.step,
29556
- reason: event.reason
29557
- }, currentTurnId, event.source);
29558
- return;
29559
- case "content.delta":
29560
- sendWire("content.delta", {
29561
- type: "text",
29562
- text: event.delta
29563
- }, currentTurnId, event.source);
29564
- return;
29565
- case "thinking.delta":
29566
- sendWire("content.delta", {
29567
- type: "thinking",
29568
- thinking: event.delta
29569
- }, currentTurnId, event.source);
29570
- return;
29571
- case "tool_call_part":
29572
- sendWire("content.delta", {
29573
- type: "tool_call_part",
29574
- tool_call_id: event.tool_call_id,
29575
- ...event.name !== void 0 ? { name: event.name } : {},
29576
- ...event.arguments_chunk !== void 0 ? { arguments_chunk: event.arguments_chunk } : {}
29577
- }, currentTurnId, event.source);
29578
- return;
29579
- case "tool.call":
29580
- sendWire("tool.call", {
29581
- id: event.toolCallId,
29582
- name: event.name,
29583
- args: event.args
29584
- }, currentTurnId, event.source);
29585
- return;
29586
- case "tool.progress":
29587
- sendWire("tool.progress", {
29588
- id: event.toolCallId,
29589
- update: event.update
29590
- }, currentTurnId, event.source);
29591
- return;
29592
- case "tool.result":
29593
- sendWire("tool.result", {
29594
- tool_call_id: event.toolCallId,
29595
- output: event.output,
29596
- ...event.isError !== void 0 ? { is_error: event.isError } : {}
29597
- }, currentTurnId, event.source);
29598
- return;
29599
- case "compaction.begin":
29600
- sendWire("compaction.begin", {}, currentTurnId, event.source);
29601
- return;
29602
- case "compaction.end":
29603
- sendWire("compaction.end", {
29604
- ...event.tokensBefore !== void 0 ? { tokens_before: event.tokensBefore } : {},
29605
- ...event.tokensAfter !== void 0 ? { tokens_after: event.tokensAfter } : {}
29606
- }, currentTurnId, event.source);
29607
- return;
29608
- case "session.error":
29609
- sendWire("session.error", {
29610
- error: event.error,
29611
- ...event.error_type !== void 0 ? { error_type: event.error_type } : {},
29612
- ...event.retry_after_ms !== void 0 ? { retry_after_ms: event.retry_after_ms } : {},
29613
- ...event.details !== void 0 ? { details: event.details } : {}
29614
- }, currentTurnId, event.source);
29615
- return;
29616
- case "hook.triggered":
29617
- sendWire("hook.triggered", {
29618
- event: event.event,
29619
- matchers: event.matchers,
29620
- matched_count: event.matched_count
29621
- }, currentTurnId, event.source);
29622
- return;
29623
- case "hook.resolved":
29624
- sendWire("hook.resolved", {
29625
- hook_id: event.hook_id,
29626
- outcome: event.outcome
29627
- }, currentTurnId, event.source);
29628
- return;
29629
- case "status.update":
29630
- sendWire("status.update", event.data, currentTurnId, event.source);
29631
- return;
29632
- case "session_meta.changed":
29633
- sendWire("session_meta.changed", event.data, currentTurnId, event.source);
29634
- return;
29635
- case "thinking.changed":
29636
- sendWire("thinking.changed", { level: event.level }, currentTurnId, event.source);
29637
- return;
29638
- case "skill.invoked":
29639
- sendWire("skill.invoked", event.data, currentTurnId, event.source);
29640
- return;
29641
- case "skill.completed":
29642
- sendWire("skill.completed", event.data, currentTurnId, event.source);
29643
- return;
29644
- case "mcp.loading":
29645
- case "mcp.connected":
29646
- case "mcp.disconnected":
29647
- case "mcp.error":
29648
- case "mcp.tools_changed":
29649
- case "mcp.resources_changed":
29650
- case "mcp.auth_required":
29651
- case "status.update.mcp_status":
29652
- sendWire(event.type, event.data, currentTurnId, event.source);
29653
- return;
29654
- case "subagent.spawned":
29655
- case "subagent.completed":
29656
- case "subagent.failed":
29657
- sendWire(event.type, event.data, currentTurnId, event.source);
29658
- return;
29659
- default:
29660
- }
29969
+ const draft = translateBusEvent(event, currentTurnId);
29970
+ if (draft !== void 0) pipeline.enqueue(draft);
29971
+ };
29972
+ const notificationListener = (notif) => {
29973
+ pipeline.enqueue(translateNotificationEvent(notif));
29661
29974
  };
29662
29975
  const turnListener = (event) => {
29663
- if (event.kind === "begin") {
29664
- currentTurnId = event.turnId;
29665
- sendWire("turn.begin", {
29666
- turn_id: event.turnId,
29667
- user_input: event.userInputParts ?? event.userInput,
29668
- input_kind: event.inputKind
29669
- }, event.turnId);
29670
- return;
29671
- }
29672
- const turnId = event.turnId;
29673
- sendWire("turn.end", {
29674
- turn_id: turnId,
29675
- reason: event.reason,
29676
- success: event.success,
29677
- ...event.usage !== void 0 ? { usage: {
29678
- input_tokens: event.usage.input,
29679
- output_tokens: event.usage.output,
29680
- ...event.usage.cache_read !== void 0 ? { cache_read_tokens: event.usage.cache_read } : {},
29681
- ...event.usage.cache_write !== void 0 ? { cache_write_tokens: event.usage.cache_write } : {}
29682
- } } : {}
29683
- }, turnId);
29684
- if (currentTurnId === turnId) currentTurnId = void 0;
29976
+ const translated = translateTurnLifecycleEvent(event, currentTurnId);
29977
+ currentTurnId = translated.nextTurnId;
29978
+ pipeline.enqueue(translated.draft);
29685
29979
  };
29686
29980
  const unsubTurn = addTurnLifecycleListener(turnListener);
29687
29981
  eventBus.on(soulListener);
29982
+ eventBus.subscribeNotifications(notificationListener);
29688
29983
  return { dispose() {
29689
29984
  eventBus.off(soulListener);
29985
+ eventBus.unsubscribeNotifications(notificationListener);
29690
29986
  unsubTurn();
29691
29987
  } };
29692
29988
  }
@@ -30116,6 +30412,48 @@ function errorMessage(error) {
30116
30412
  return error instanceof Error ? error.message : String(error);
30117
30413
  }
30118
30414
  //#endregion
30415
+ //#region ../../packages/kimi-core/src/session/state-cache.ts
30416
+ /**
30417
+ * StateCache — state.json derived cache (§9续).
30418
+ *
30419
+ * Reads / writes `state.json` in a session directory. Used by
30420
+ * SessionManager to persist session metadata and runtime state (model,
30421
+ * permission mode, plan mode, last turn index) outside the transcript.
30422
+ *
30423
+ * Writes go through `atomicWrite` (write-tmp-fsync-rename, Decision #104)
30424
+ * so a crash mid-write never leaves a half-truncated state.json that
30425
+ * subsequent reads would treat as "missing". Read-modify-write callers
30426
+ * still need their own concurrency guard for "merge then write" races
30427
+ * across processes — see SessionManager.renameSession.
30428
+ */
30429
+ var StateCache = class {
30430
+ writeQueue = Promise.resolve();
30431
+ constructor(statePath) {
30432
+ this.statePath = statePath;
30433
+ }
30434
+ async read() {
30435
+ try {
30436
+ const raw = await readFile(this.statePath, "utf-8");
30437
+ if (raw.length === 0) return null;
30438
+ return JSON.parse(raw);
30439
+ } catch {
30440
+ return null;
30441
+ }
30442
+ }
30443
+ async write(state) {
30444
+ await atomicWrite(this.statePath, JSON.stringify(state, null, 2));
30445
+ }
30446
+ async update(mutator) {
30447
+ const nextWrite = this.writeQueue.then(async () => {
30448
+ const next = mutator(await this.read());
30449
+ await this.write(next);
30450
+ return next;
30451
+ });
30452
+ this.writeQueue = nextWrite.then(() => {}, () => {});
30453
+ return nextWrite;
30454
+ }
30455
+ };
30456
+ //#endregion
30119
30457
  //#region ../../packages/kimi-core/src/session/session-application-service.ts
30120
30458
  function withCompactionConfigFallback(bundle, fallback) {
30121
30459
  if (bundle.compactionConfig !== void 0 || fallback === void 0) return bundle;
@@ -30192,7 +30530,7 @@ var DefaultSessionApplicationService = class {
30192
30530
  eventBus,
30193
30531
  ...hookEngine !== void 0 ? { hookEngine } : {}
30194
30532
  });
30195
- const initialModel = this.deps.defaultModelProvider();
30533
+ const initialModel = (await new StateCache(this.deps.pathConfig.statePath(sessionId)).read())?.model ?? this.deps.defaultModelProvider();
30196
30534
  const runtimeBundle = await this.deps.runtimeBundleProvider?.({
30197
30535
  sessionId,
30198
30536
  model: initialModel
@@ -30268,22 +30606,13 @@ var DefaultSessionApplicationService = class {
30268
30606
  }
30269
30607
  async resumeSnapshot(sessionId) {
30270
30608
  const managed = this.getManaged(sessionId);
30271
- let turnCount = 0;
30272
- let lastTurnId;
30273
- try {
30274
- const bodyLines = (await readFile(this.deps.pathConfig.wirePath(sessionId), "utf8")).split("\n").filter((line) => line.length > 0).slice(1);
30275
- for (const line of bodyLines) try {
30276
- const rec = JSON.parse(line);
30277
- if (rec.type === "turn_begin") {
30278
- turnCount += 1;
30279
- if (rec.turn_id !== void 0) lastTurnId = rec.turn_id;
30280
- }
30281
- } catch {}
30282
- } catch {}
30609
+ const state = await managed.stateCache.read();
30610
+ const turnCount = state?.turn_count ?? 0;
30611
+ const lastTurnId = state?.last_turn_id;
30283
30612
  managed.eventBus.emit({
30284
30613
  type: "status.update",
30285
30614
  data: {
30286
- model: this.deps.defaultModelProvider(),
30615
+ model: managed.contextState.model,
30287
30616
  plan_mode: managed.soulPlus.getTurnManager().getPlanMode(),
30288
30617
  yolo: managed.soulPlus.getTurnManager().getPermissionMode() === "bypassPermissions"
30289
30618
  }
@@ -30349,7 +30678,21 @@ var DefaultSessionApplicationService = class {
30349
30678
  }
30350
30679
  async setYolo(sessionId, enabled) {
30351
30680
  const managed = this.getManaged(sessionId);
30352
- await managed.sessionControl.setYolo(enabled);
30681
+ const permissionMode = enabled ? "bypassPermissions" : "default";
30682
+ await managed.stateCache.update((current) => {
30683
+ const now = Date.now();
30684
+ return {
30685
+ ...current ?? {
30686
+ session_id: sessionId,
30687
+ created_at: now,
30688
+ updated_at: now
30689
+ },
30690
+ permission_mode: permissionMode,
30691
+ updated_at: now,
30692
+ last_exit_code: "dirty"
30693
+ };
30694
+ });
30695
+ managed.soulPlus.getTurnManager().setPermissionMode(permissionMode);
30353
30696
  if (this.deps.approvalStateStore !== void 0) await this.deps.approvalStateStore.setYolo(enabled);
30354
30697
  managed.soulPlus.getTurnManager().emitStatusUpdate({
30355
30698
  input: 0,
@@ -30384,6 +30727,19 @@ var DefaultSessionApplicationService = class {
30384
30727
  compactionConfig
30385
30728
  });
30386
30729
  }
30730
+ await managed.stateCache.update((current) => {
30731
+ const now = Date.now();
30732
+ return {
30733
+ ...current ?? {
30734
+ session_id: sessionId,
30735
+ created_at: now,
30736
+ updated_at: now
30737
+ },
30738
+ model,
30739
+ updated_at: now,
30740
+ last_exit_code: "dirty"
30741
+ };
30742
+ });
30387
30743
  await managed.soulPlus.setModel(model);
30388
30744
  }
30389
30745
  async setSystemPrompt(sessionId, prompt) {
@@ -30393,7 +30749,21 @@ var DefaultSessionApplicationService = class {
30393
30749
  });
30394
30750
  }
30395
30751
  async setThinking(sessionId, level) {
30396
- await this.getManaged(sessionId).soulPlus.setThinking(level);
30752
+ const managed = this.getManaged(sessionId);
30753
+ await managed.stateCache.update((current) => {
30754
+ const now = Date.now();
30755
+ return {
30756
+ ...current ?? {
30757
+ session_id: sessionId,
30758
+ created_at: now,
30759
+ updated_at: now
30760
+ },
30761
+ thinking_level: level,
30762
+ updated_at: now,
30763
+ last_exit_code: "dirty"
30764
+ };
30765
+ });
30766
+ await managed.soulPlus.setThinking(level);
30397
30767
  }
30398
30768
  async addSystemReminder(sessionId, content) {
30399
30769
  await this.getManaged(sessionId).soulPlus.addSystemReminder(content);
@@ -30448,7 +30818,7 @@ var ChangeListenerRegistry = class {
30448
30818
  }
30449
30819
  };
30450
30820
  /**
30451
- * Production adapter that reads / writes `auto_approve_actions` + `yolo`
30821
+ * Production adapter that reads / writes `auto_approve_actions` + `permission_mode`
30452
30822
  * on the session's `state.json` file via the existing `StateCache`
30453
30823
  * service. The rest of the state.json fields are preserved on write —
30454
30824
  * we only rewrite the approval-state fields.
@@ -30476,7 +30846,7 @@ var SessionStateApprovalStateStore = class {
30476
30846
  const raw = state?.auto_approve_actions;
30477
30847
  const actions = raw === void 0 ? /* @__PURE__ */ new Set() : new Set(raw);
30478
30848
  this.cachedActions = new Set(actions);
30479
- this.cachedYolo = state?.yolo ?? false;
30849
+ this.cachedYolo = permissionModeFromState(state) === "bypassPermissions";
30480
30850
  return actions;
30481
30851
  }
30482
30852
  async save(actions) {
@@ -30487,7 +30857,7 @@ var SessionStateApprovalStateStore = class {
30487
30857
  async getYolo() {
30488
30858
  if (this.cachedYolo !== void 0) return this.cachedYolo;
30489
30859
  const state = await this.stateCache.read();
30490
- this.cachedYolo = state?.yolo ?? false;
30860
+ this.cachedYolo = permissionModeFromState(state) === "bypassPermissions";
30491
30861
  this.cachedActions = new Set(state?.auto_approve_actions ?? []);
30492
30862
  return this.cachedYolo;
30493
30863
  }
@@ -30505,7 +30875,7 @@ var SessionStateApprovalStateStore = class {
30505
30875
  const current = await this.stateCache.read();
30506
30876
  const now = this.now();
30507
30877
  const nextActions = patch.actions !== void 0 ? [...patch.actions] : current?.auto_approve_actions !== void 0 ? [...current.auto_approve_actions] : void 0;
30508
- const nextYolo = patch.yolo !== void 0 ? patch.yolo : current?.yolo;
30878
+ const nextMode = patch.yolo !== void 0 ? patch.yolo ? "bypassPermissions" : "default" : permissionModeFromState(current);
30509
30879
  const next = {
30510
30880
  ...current ? {
30511
30881
  ...current,
@@ -30516,7 +30886,7 @@ var SessionStateApprovalStateStore = class {
30516
30886
  updated_at: now
30517
30887
  },
30518
30888
  ...nextActions !== void 0 ? { auto_approve_actions: nextActions } : {},
30519
- ...nextYolo !== void 0 ? { yolo: nextYolo } : {}
30889
+ permission_mode: nextMode
30520
30890
  };
30521
30891
  await this.stateCache.write(next);
30522
30892
  }
@@ -30527,6 +30897,11 @@ var SessionStateApprovalStateStore = class {
30527
30897
  });
30528
30898
  }
30529
30899
  };
30900
+ function permissionModeFromState(state) {
30901
+ const mode = state?.permission_mode;
30902
+ if (mode === "acceptEdits" || mode === "bypassPermissions") return mode;
30903
+ return "default";
30904
+ }
30530
30905
  //#endregion
30531
30906
  //#region ../../packages/kimi-core/src/wire-protocol/reverse-rpc.ts
30532
30907
  /**
@@ -30686,6 +31061,10 @@ function buildHookRequestData(subscriptionId, input) {
30686
31061
  base["tool_call_id"] = input.toolCall.id;
30687
31062
  base["args"] = input.toolCall.args;
30688
31063
  }
31064
+ if (input.event === "PreTurn" || input.event === "UserPromptSubmit") base["prompt"] = input.prompt;
31065
+ if (input.event === "PostTurn" || input.event === "Stop") base["reason"] = input.reason;
31066
+ if (input.event === "PreStep" || input.event === "PostStep") base["step_number"] = input.stepNumber;
31067
+ if (input.event === "PostStep") base["stop_reason"] = input.stopReason;
30689
31068
  return base;
30690
31069
  }
30691
31070
  //#endregion
@@ -30728,21 +31107,6 @@ function buildHookRequestData(subscriptionId, input) {
30728
31107
  * - `registerDefaultWireHandlers` returns a handle so the host can
30729
31108
  * plug per-session state (event filter for now) into the bridge.
30730
31109
  */
30731
- const HOOK_SUPPORTED_EVENTS = [
30732
- "PreToolUse",
30733
- "PostToolUse",
30734
- "OnToolFailure",
30735
- "UserPromptSubmit",
30736
- "Stop",
30737
- "StopFailure",
30738
- "Notification",
30739
- "SubagentStart",
30740
- "SubagentStop",
30741
- "SessionStart",
30742
- "SessionEnd",
30743
- "PreCompact",
30744
- "PostCompact"
30745
- ];
30746
31110
  const SUPPORTED_WIRE_EVENTS = [
30747
31111
  "turn.begin",
30748
31112
  "turn.end",
@@ -30981,7 +31345,7 @@ function registerDefaultWireHandlers(deps) {
30981
31345
  events: [...SUPPORTED_WIRE_EVENTS],
30982
31346
  methods: [...SUPPORTED_WIRE_METHODS],
30983
31347
  hooks: {
30984
- supported_events: HOOK_SUPPORTED_EVENTS,
31348
+ supported_events: SUPPORTED_HOOK_EVENTS,
30985
31349
  configured: initialHooks.map((h) => ({
30986
31350
  event: h.event,
30987
31351
  ...typeof h.matcher === "string" && h.matcher.length > 0 ? { matcher: h.matcher } : {}
@@ -32063,12 +32427,6 @@ async function repairJournal(options) {
32063
32427
  }
32064
32428
  //#endregion
32065
32429
  //#region ../../packages/kimi-core/src/session/replay-projector.ts
32066
- /** Whitelist of valid PermissionMode values for safe runtime validation. */
32067
- const VALID_PERMISSION_MODES = new Set([
32068
- "default",
32069
- "acceptEdits",
32070
- "bypassPermissions"
32071
- ]);
32072
32430
  function isContentPart(value) {
32073
32431
  if (typeof value !== "object" || value === null) return false;
32074
32432
  const part = value;
@@ -32084,26 +32442,26 @@ function cloneContentPart(part) {
32084
32442
  * Project replayed WireRecords into the initial state needed to hydrate a
32085
32443
  * resumed `WiredContextState`.
32086
32444
  *
32087
- * Phase 23 — the `sessionInitialized` baseline is the truth source for
32088
- * startup config (system_prompt / model / active_tools / permission_mode /
32089
- * plan_mode / workspace_dir). Subsequent `*_changed` records in `records`
32090
- * overlay the baseline.
32445
+ * `sessionInitialized` is the truth source for wire-owned startup context
32446
+ * (system_prompt / active_tools). Runtime/session config (model,
32447
+ * permission_mode, plan_mode, workspace_dir, thinking_level) comes from
32448
+ * `runtimeBaseline`, which is built from state.json by SessionManager.
32091
32449
  *
32092
32450
  * @param records — ordered records from `replayWire().records`
32093
32451
  * @param sessionInitialized — the line-2 baseline from `replayWire().sessionInitialized`
32094
32452
  * @returns Projected state suitable for `WiredContextState` constructor.
32095
32453
  */
32096
- function projectReplayState(records, sessionInitialized) {
32454
+ function projectReplayState(records, sessionInitialized, runtimeBaseline) {
32097
32455
  let messages = [];
32098
- let model = sessionInitialized.model;
32456
+ let model = runtimeBaseline?.model ?? sessionInitialized.model ?? "";
32099
32457
  let systemPrompt = sessionInitialized.system_prompt;
32100
32458
  const activeTools = new Set(sessionInitialized.active_tools);
32101
32459
  let lastSeq = 0;
32102
- let permissionMode = sessionInitialized.permission_mode;
32460
+ let permissionMode = runtimeBaseline?.permissionMode ?? sessionInitialized.permission_mode ?? "default";
32103
32461
  let tokenCount = 0;
32104
- let planMode = sessionInitialized.plan_mode;
32105
- let thinkingLevel = sessionInitialized.thinking_level;
32106
- const workspaceDir = sessionInitialized.workspace_dir;
32462
+ let planMode = runtimeBaseline?.planMode ?? sessionInitialized.plan_mode ?? false;
32463
+ let thinkingLevel = runtimeBaseline?.thinkingLevel ?? sessionInitialized.thinking_level;
32464
+ const workspaceDir = runtimeBaseline?.workspaceDir ?? sessionInitialized.workspace_dir ?? process.cwd();
32107
32465
  const sessionMetaPatch = { turn_count: 0 };
32108
32466
  let todos = [];
32109
32467
  const pendingTodoWrites = /* @__PURE__ */ new Map();
@@ -32184,21 +32542,9 @@ function projectReplayState(records, sessionInitialized) {
32184
32542
  }];
32185
32543
  tokenCount = r.post_compact_tokens;
32186
32544
  break;
32187
- case "model_changed":
32188
- model = r.new_model;
32189
- sessionMetaPatch.last_model = r.new_model;
32190
- break;
32191
32545
  case "turn_begin":
32192
32546
  sessionMetaPatch.turn_count += 1;
32193
32547
  break;
32194
- case "session_meta_changed":
32195
- if (r.patch.title !== void 0) sessionMetaPatch.title = r.patch.title;
32196
- if (r.patch.tags !== void 0) sessionMetaPatch.tags = [...r.patch.tags];
32197
- if (r.patch.description !== void 0) sessionMetaPatch.description = r.patch.description;
32198
- if (r.patch.archived !== void 0) sessionMetaPatch.archived = r.patch.archived;
32199
- if (r.patch.color !== void 0) sessionMetaPatch.color = r.patch.color;
32200
- if (r.patch.plan_slug !== void 0) sessionMetaPatch.plan_slug = r.patch.plan_slug;
32201
- break;
32202
32548
  case "system_prompt_changed":
32203
32549
  systemPrompt = r.new_prompt;
32204
32550
  break;
@@ -32209,15 +32555,6 @@ function projectReplayState(records, sessionInitialized) {
32209
32555
  } else if (r.operation === "register") for (const t of r.tools) activeTools.add(t);
32210
32556
  else if (r.operation === "remove") for (const t of r.tools) activeTools.delete(t);
32211
32557
  break;
32212
- case "permission_mode_changed":
32213
- if (VALID_PERMISSION_MODES.has(r.data.to)) permissionMode = r.data.to;
32214
- break;
32215
- case "plan_mode_changed":
32216
- planMode = r.enabled;
32217
- break;
32218
- case "thinking_changed":
32219
- thinkingLevel = r.level;
32220
- break;
32221
32558
  case "context_cleared":
32222
32559
  flushOpenStep();
32223
32560
  messages = [];
@@ -32362,38 +32699,6 @@ var SessionTodoStore = class {
32362
32699
  }
32363
32700
  };
32364
32701
  //#endregion
32365
- //#region ../../packages/kimi-core/src/session/state-cache.ts
32366
- /**
32367
- * StateCache — state.json derived cache (§9续).
32368
- *
32369
- * Reads / writes `state.json` in a session directory. Used by
32370
- * SessionManager to persist quick-access session metadata (model,
32371
- * status, last turn timestamp) without replaying the full wire.jsonl.
32372
- *
32373
- * Writes go through `atomicWrite` (write-tmp-fsync-rename, Decision #104)
32374
- * so a crash mid-write never leaves a half-truncated state.json that
32375
- * subsequent reads would treat as "missing". Read-modify-write callers
32376
- * still need their own concurrency guard for "merge then write" races
32377
- * across processes — see SessionManager.renameSession.
32378
- */
32379
- var StateCache = class {
32380
- constructor(statePath) {
32381
- this.statePath = statePath;
32382
- }
32383
- async read() {
32384
- try {
32385
- const raw = await readFile(this.statePath, "utf-8");
32386
- if (raw.length === 0) return null;
32387
- return JSON.parse(raw);
32388
- } catch {
32389
- return null;
32390
- }
32391
- }
32392
- async write(state) {
32393
- await atomicWrite(this.statePath, JSON.stringify(state, null, 2));
32394
- }
32395
- };
32396
- //#endregion
32397
32702
  //#region ../../packages/kimi-core/src/session/usage-aggregator.ts
32398
32703
  /**
32399
32704
  * Token usage aggregator — replays a wire.jsonl and sums usage from
@@ -32553,6 +32858,17 @@ function normalizeRuntimeSlot(slot, fallback) {
32553
32858
  });
32554
32859
  return runtimeSlot;
32555
32860
  }
32861
+ function normalizePermissionMode(value) {
32862
+ if (value === "acceptEdits" || value === "bypassPermissions") return value;
32863
+ return "default";
32864
+ }
32865
+ function createFallbackState(sessionId, now) {
32866
+ return {
32867
+ session_id: sessionId,
32868
+ created_at: now,
32869
+ updated_at: now
32870
+ };
32871
+ }
32556
32872
  var SessionManager = class {
32557
32873
  paths;
32558
32874
  producer;
@@ -32590,6 +32906,29 @@ var SessionManager = class {
32590
32906
  this.stateWriteMutex.set(sessionId, next.then(() => {}, () => {}));
32591
32907
  return next;
32592
32908
  }
32909
+ attachStateTracker(sessionId, stateCache, turnManager) {
32910
+ return turnManager.addTurnLifecycleListener((event) => {
32911
+ const now = Date.now();
32912
+ stateCache.update((current) => {
32913
+ const base = current ?? createFallbackState(sessionId, now);
32914
+ if (event.kind === "begin") return {
32915
+ ...base,
32916
+ last_turn_id: event.turnId,
32917
+ last_turn_time: now,
32918
+ updated_at: now,
32919
+ last_exit_code: "dirty"
32920
+ };
32921
+ return {
32922
+ ...base,
32923
+ turn_count: (base.turn_count ?? 0) + 1,
32924
+ last_turn_id: event.turnId,
32925
+ last_turn_time: now,
32926
+ updated_at: now,
32927
+ last_exit_code: "dirty"
32928
+ };
32929
+ }).catch(() => {});
32930
+ });
32931
+ }
32593
32932
  async createSession(options) {
32594
32933
  const sessionId = options.sessionId ?? `ses_${randomUUID().replaceAll("-", "").slice(0, 12)}`;
32595
32934
  if (this.sessions.has(sessionId)) throw new Error(`Session already exists: ${sessionId}`);
@@ -32623,11 +32962,7 @@ var SessionManager = class {
32623
32962
  agent_type: "main",
32624
32963
  session_id: sessionId,
32625
32964
  system_prompt: options.systemPrompt ?? "",
32626
- model: options.model,
32627
- active_tools: sessionTools.map((t) => t.name),
32628
- permission_mode: options.permissionMode ?? "default",
32629
- plan_mode: false,
32630
- workspace_dir: options.workspaceDir
32965
+ active_tools: sessionTools.map((t) => t.name)
32631
32966
  };
32632
32967
  await journalWriter.append(mainInitInput);
32633
32968
  const sessionJournal = new WiredSessionJournalImpl(journalWriter);
@@ -32643,10 +32978,12 @@ var SessionManager = class {
32643
32978
  await stateCache.write({
32644
32979
  session_id: sessionId,
32645
32980
  model: options.model,
32646
- status: "active",
32647
32981
  created_at: now,
32648
32982
  updated_at: now,
32649
32983
  workspace_dir: options.workspaceDir,
32984
+ permission_mode: options.permissionMode ?? "default",
32985
+ plan_mode: false,
32986
+ turn_count: 0,
32650
32987
  last_exit_code: "dirty",
32651
32988
  producer: this.producer,
32652
32989
  todos: []
@@ -32703,6 +33040,7 @@ var SessionManager = class {
32703
33040
  ...options.logger !== void 0 ? { logger: options.logger } : {}
32704
33041
  });
32705
33042
  turnManagerRef = soulPlus.getTurnManager();
33043
+ const disposeStateTracking = this.attachStateTracker(sessionId, stateCache, turnManagerRef);
32706
33044
  await soulPlus.init();
32707
33045
  if (options.permissionMode !== void 0) turnManagerRef.setPermissionMode(options.permissionMode);
32708
33046
  const managed = {
@@ -32712,7 +33050,20 @@ var SessionManager = class {
32712
33050
  turnManager: turnManagerRef,
32713
33051
  contextState,
32714
33052
  sessionJournal,
32715
- setPlanModeOverride: (enabled) => soulPlus.setPlanMode(enabled)
33053
+ setPlanModeOverride: (enabled) => soulPlus.setPlanMode(enabled),
33054
+ setYoloOverride: async (enabled) => {
33055
+ const permissionMode = enabled ? "bypassPermissions" : "default";
33056
+ await stateCache.update((current) => {
33057
+ const now = Date.now();
33058
+ return {
33059
+ ...current ?? createFallbackState(sessionId, now),
33060
+ permission_mode: permissionMode,
33061
+ updated_at: now,
33062
+ last_exit_code: "dirty"
33063
+ };
33064
+ });
33065
+ turnManagerRef.setPermissionMode(permissionMode);
33066
+ }
32716
33067
  }),
32717
33068
  contextState,
32718
33069
  eventBus,
@@ -32721,6 +33072,7 @@ var SessionManager = class {
32721
33072
  journalWriter,
32722
33073
  runtimeSlot,
32723
33074
  lifecycleStateMachine,
33075
+ disposeStateTracking,
32724
33076
  todoStore
32725
33077
  };
32726
33078
  this.sessions.set(sessionId, managed);
@@ -32743,7 +33095,18 @@ var SessionManager = class {
32743
33095
  throw new Error(`Failed to replay session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
32744
33096
  }
32745
33097
  if (replayResult.sessionInitialized.agent_type !== "main") throw new MalformedWireError("agent-type-mismatch", `resumeSession expected agent_type='main' on wire line 2 for "${sessionId}", got agent_type='${replayResult.sessionInitialized.agent_type}'`);
32746
- const projected = projectReplayState(replayResult.records, replayResult.sessionInitialized);
33098
+ const stateCache = new StateCache(this.paths.statePath(sessionId));
33099
+ const stateData = await stateCache.read();
33100
+ if (stateData === null) throw new Error(`resumeSession: state.json missing or unreadable for session "${sessionId}"`);
33101
+ if (stateData.model === void 0 || stateData.model.length === 0) throw new Error(`resumeSession: state.json missing model for session "${sessionId}"`);
33102
+ const statePermissionMode = normalizePermissionMode(stateData.permission_mode);
33103
+ const projected = projectReplayState(replayResult.records, replayResult.sessionInitialized, {
33104
+ model: stateData.model,
33105
+ permissionMode: statePermissionMode,
33106
+ planMode: stateData.plan_mode ?? false,
33107
+ workspaceDir: stateData.workspace_dir ?? process.cwd(),
33108
+ ...stateData.thinking_level !== void 0 ? { thinkingLevel: stateData.thinking_level } : {}
33109
+ });
32747
33110
  const lifecycleStateMachine = new SessionLifecycleStateMachine();
32748
33111
  const lifecycleGate = new SoulLifecycleGate(lifecycleStateMachine);
32749
33112
  let contextStateRef;
@@ -32778,10 +33141,7 @@ var SessionManager = class {
32778
33141
  sessionJournal,
32779
33142
  currentTurnId: () => turnManagerRef?.getCurrentTurnId() ?? "recovery"
32780
33143
  });
32781
- const stateCache = new StateCache(this.paths.statePath(sessionId));
32782
- const stateData = await stateCache.read();
32783
- const isClean = stateData?.last_exit_code === "clean";
32784
- const initialTodos = isClean ? cloneTodos(stateData?.todos ?? projected.todos) : cloneTodos(projected.todos);
33144
+ const initialTodos = stateData.last_exit_code === "clean" ? cloneTodos(stateData.todos ?? projected.todos) : cloneTodos(projected.todos);
32785
33145
  const todoStore = new SessionTodoStore(stateCache, initialTodos);
32786
33146
  const sessionTools = bindTodoStore(options.tools, todoStore);
32787
33147
  const runtimeSlot = normalizeRuntimeSlot(options.runtimeSlot, {
@@ -32791,32 +33151,25 @@ var SessionManager = class {
32791
33151
  ...options.compactionConfig !== void 0 ? { compactionConfig: options.compactionConfig } : {}
32792
33152
  });
32793
33153
  const runtimeBundle = runtimeSlot.current();
32794
- if (stateData !== null) await stateCache.write({
33154
+ await stateCache.write({
32795
33155
  ...stateData,
32796
- status: "active",
32797
33156
  updated_at: Date.now(),
32798
33157
  todos: initialTodos,
32799
33158
  last_exit_code: "dirty"
32800
33159
  });
32801
- const replayedMeta = projected.sessionMetaPatch;
32802
33160
  const now = Date.now();
32803
- const pickTitle = isClean ? stateData?.custom_title ?? replayedMeta.title : replayedMeta.title ?? stateData?.custom_title;
32804
- const pickTags = isClean ? stateData?.tags ?? replayedMeta.tags : replayedMeta.tags ?? stateData?.tags;
32805
- const pickDescription = isClean ? stateData?.description ?? replayedMeta.description : replayedMeta.description ?? stateData?.description;
32806
- const pickArchived = isClean ? stateData?.archived ?? replayedMeta.archived : replayedMeta.archived ?? stateData?.archived;
32807
- const pickLastModel = isClean ? stateData?.model ?? replayedMeta.last_model : replayedMeta.last_model ?? stateData?.model;
32808
- const pickPlanSlug = isClean ? stateData?.plan_slug ?? replayedMeta.plan_slug : replayedMeta.plan_slug ?? stateData?.plan_slug;
32809
33161
  const initialMeta = {
32810
33162
  session_id: sessionId,
32811
- created_at: stateData?.created_at ?? now,
32812
- turn_count: replayedMeta.turn_count,
32813
- last_updated: stateData?.updated_at ?? now,
32814
- ...pickTitle !== void 0 ? { title: pickTitle } : {},
32815
- ...pickTags !== void 0 ? { tags: [...pickTags] } : {},
32816
- ...pickDescription !== void 0 ? { description: pickDescription } : {},
32817
- ...pickArchived !== void 0 ? { archived: pickArchived } : {},
32818
- ...pickLastModel !== void 0 ? { last_model: pickLastModel } : {},
32819
- ...pickPlanSlug !== void 0 ? { plan_slug: pickPlanSlug } : {},
33163
+ created_at: stateData.created_at ?? now,
33164
+ turn_count: stateData.turn_count ?? 0,
33165
+ last_updated: stateData.updated_at ?? now,
33166
+ ...stateData.custom_title !== void 0 ? { title: stateData.custom_title } : {},
33167
+ ...stateData.tags !== void 0 ? { tags: [...stateData.tags] } : {},
33168
+ ...stateData.description !== void 0 ? { description: stateData.description } : {},
33169
+ ...stateData.archived !== void 0 ? { archived: stateData.archived } : {},
33170
+ ...stateData.color !== void 0 ? { color: stateData.color } : {},
33171
+ last_model: stateData.model,
33172
+ ...stateData.plan_slug !== void 0 ? { plan_slug: stateData.plan_slug } : {},
32820
33173
  producer: replayResult.producer,
32821
33174
  last_exit_code: "dirty"
32822
33175
  };
@@ -32865,11 +33218,12 @@ var SessionManager = class {
32865
33218
  subagentStore,
32866
33219
  agentTypeRegistry: options.agentTypeRegistry,
32867
33220
  sessionDir,
32868
- workDir: stateData?.workspace_dir ?? process.cwd()
33221
+ workDir: stateData.workspace_dir ?? process.cwd()
32869
33222
  } : {},
32870
33223
  ...options.logger !== void 0 ? { logger: options.logger } : {}
32871
33224
  });
32872
33225
  turnManagerRef = soulPlus.getTurnManager();
33226
+ const disposeStateTracking = this.attachStateTracker(sessionId, stateCache, turnManagerRef);
32873
33227
  await soulPlus.init();
32874
33228
  turnManagerRef.setPermissionMode(effectivePermissionMode);
32875
33229
  if (projected.thinkingLevel !== void 0) turnManagerRef.setThinkingLevel(projected.thinkingLevel);
@@ -32896,7 +33250,20 @@ var SessionManager = class {
32896
33250
  turnManager: turnManagerRef,
32897
33251
  contextState,
32898
33252
  sessionJournal,
32899
- setPlanModeOverride: (enabled) => soulPlus.setPlanMode(enabled)
33253
+ setPlanModeOverride: (enabled) => soulPlus.setPlanMode(enabled),
33254
+ setYoloOverride: async (enabled) => {
33255
+ const permissionMode = enabled ? "bypassPermissions" : "default";
33256
+ await stateCache.update((current) => {
33257
+ const now = Date.now();
33258
+ return {
33259
+ ...current ?? createFallbackState(sessionId, now),
33260
+ permission_mode: permissionMode,
33261
+ updated_at: now,
33262
+ last_exit_code: "dirty"
33263
+ };
33264
+ });
33265
+ turnManagerRef.setPermissionMode(permissionMode);
33266
+ }
32900
33267
  }),
32901
33268
  contextState,
32902
33269
  eventBus,
@@ -32905,6 +33272,7 @@ var SessionManager = class {
32905
33272
  journalWriter,
32906
33273
  runtimeSlot,
32907
33274
  lifecycleStateMachine,
33275
+ disposeStateTracking,
32908
33276
  todoStore
32909
33277
  };
32910
33278
  this.sessions.set(sessionId, managed);
@@ -32926,7 +33294,6 @@ var SessionManager = class {
32926
33294
  session_id: state.session_id,
32927
33295
  created_at: state.created_at,
32928
33296
  ...state.model !== void 0 ? { model: state.model } : {},
32929
- ...state.status !== void 0 ? { status: state.status } : {},
32930
33297
  ...state.workspace_dir !== void 0 ? { workspace_dir: state.workspace_dir } : {},
32931
33298
  ...state.custom_title !== void 0 ? { title: state.custom_title } : {},
32932
33299
  ...lastActivity !== void 0 ? { last_activity: lastActivity } : {},
@@ -32970,7 +33337,8 @@ var SessionManager = class {
32970
33337
  await cache.write({
32971
33338
  ...existing,
32972
33339
  custom_title: trimmed,
32973
- updated_at: Date.now()
33340
+ updated_at: Date.now(),
33341
+ last_exit_code: "dirty"
32974
33342
  });
32975
33343
  this.usageAggregator.invalidate(this.paths.wirePath(sessionId));
32976
33344
  });
@@ -32995,7 +33363,8 @@ var SessionManager = class {
32995
33363
  await cache.write({
32996
33364
  ...existing,
32997
33365
  tags: next,
32998
- updated_at: Date.now()
33366
+ updated_at: Date.now(),
33367
+ last_exit_code: "dirty"
32999
33368
  });
33000
33369
  this.usageAggregator.invalidate(this.paths.wirePath(sessionId));
33001
33370
  });
@@ -33079,7 +33448,29 @@ var SessionManager = class {
33079
33448
  keepUpToLine = turnBeginIndices[totalTurns - nTurnsBack];
33080
33449
  newTurnCount = totalTurns - nTurnsBack;
33081
33450
  }
33082
- await atomicWrite(wirePath, `${lines.slice(0, keepUpToLine).join("\n")}\n`);
33451
+ const kept = lines.slice(0, keepUpToLine);
33452
+ await atomicWrite(wirePath, `${kept.join("\n")}\n`);
33453
+ let lastTurnId;
33454
+ let lastTurnTime;
33455
+ for (let i = kept.length - 1; i >= 0; i--) try {
33456
+ const rec = JSON.parse(kept[i] ?? "");
33457
+ if (rec.type === "turn_begin" && rec.turn_id !== void 0) {
33458
+ lastTurnId = rec.turn_id;
33459
+ if (typeof rec.time === "number") lastTurnTime = rec.time;
33460
+ break;
33461
+ }
33462
+ } catch {}
33463
+ await new StateCache(this.paths.statePath(sessionId)).update((current) => {
33464
+ const now = Date.now();
33465
+ return {
33466
+ ...current ?? createFallbackState(sessionId, now),
33467
+ turn_count: newTurnCount,
33468
+ last_turn_id: lastTurnId,
33469
+ last_turn_time: lastTurnTime,
33470
+ updated_at: now,
33471
+ last_exit_code: "dirty"
33472
+ };
33473
+ });
33083
33474
  this.usageAggregator.invalidate(wirePath);
33084
33475
  return { new_turn_count: newTurnCount };
33085
33476
  }
@@ -33091,18 +33482,8 @@ var SessionManager = class {
33091
33482
  async getSessionStatus(sessionId) {
33092
33483
  const live = this.sessions.get(sessionId);
33093
33484
  if (live !== void 0) return live.lifecycleStateMachine.state;
33094
- const state = await new StateCache(this.paths.statePath(sessionId)).read();
33095
- if (state === null) throw new Error(`getSessionStatus: session "${sessionId}" not found`);
33096
- const validStatuses = new Set([
33097
- "idle",
33098
- "active",
33099
- "completing",
33100
- "compacting",
33101
- "destroying",
33102
- "closed"
33103
- ]);
33104
- if (state.status !== void 0 && validStatuses.has(state.status)) return state.status;
33105
- return "idle";
33485
+ if (await new StateCache(this.paths.statePath(sessionId)).read() === null) throw new Error(`getSessionStatus: session "${sessionId}" not found`);
33486
+ return "closed";
33106
33487
  }
33107
33488
  /**
33108
33489
  * Return aggregated token usage for a session.
@@ -33141,33 +33522,43 @@ var SessionManager = class {
33141
33522
  await managed.todoStore?.flushPending();
33142
33523
  const existing = await managed.stateCache.read();
33143
33524
  const now = Date.now();
33144
- const currentPlanMode = managed.soulPlus.getTurnManager().getPlanMode();
33525
+ const turnManager = managed.soulPlus.getTurnManager();
33526
+ const currentPlanMode = turnManager.getPlanMode();
33527
+ const currentPermissionMode = turnManager.getPermissionMode();
33528
+ const currentThinkingLevel = turnManager.getThinkingLevel();
33145
33529
  const currentTodos = managed.todoStore?.getTodos() ?? [];
33146
33530
  const meta = sessionMetaService?.get();
33147
33531
  const stateToFlush = existing ? {
33148
33532
  ...existing,
33149
- status: "closed",
33150
33533
  updated_at: now,
33534
+ model: managed.contextState.model,
33535
+ permission_mode: currentPermissionMode,
33151
33536
  ...meta?.title !== void 0 ? { custom_title: meta.title } : {},
33152
33537
  ...meta?.tags !== void 0 ? { tags: [...meta.tags] } : {},
33153
33538
  ...meta?.description !== void 0 ? { description: meta.description } : {},
33154
33539
  ...meta?.archived !== void 0 ? { archived: meta.archived } : {},
33155
- ...meta?.last_model !== void 0 ? { model: meta.last_model } : {},
33540
+ ...meta?.color !== void 0 ? { color: meta.color } : {},
33541
+ turn_count: meta?.turn_count ?? existing.turn_count ?? 0,
33156
33542
  ...meta?.plan_slug !== void 0 ? { plan_slug: meta.plan_slug } : {},
33543
+ ...currentThinkingLevel !== void 0 ? { thinking_level: currentThinkingLevel } : {},
33157
33544
  ...currentPlanMode ? { plan_mode: true } : { plan_mode: false },
33158
33545
  todos: cloneTodos(currentTodos),
33159
33546
  last_exit_code: "clean"
33160
33547
  } : {
33161
33548
  session_id: sessionId,
33162
- status: "closed",
33163
33549
  created_at: now,
33164
33550
  updated_at: now,
33551
+ model: managed.contextState.model,
33552
+ permission_mode: currentPermissionMode,
33165
33553
  plan_mode: currentPlanMode,
33554
+ turn_count: meta?.turn_count ?? 0,
33555
+ ...currentThinkingLevel !== void 0 ? { thinking_level: currentThinkingLevel } : {},
33166
33556
  todos: cloneTodos(currentTodos),
33167
33557
  last_exit_code: "clean"
33168
33558
  };
33169
33559
  await managed.stateCache.write(stateToFlush);
33170
33560
  sessionMetaService?.dispose();
33561
+ managed.disposeStateTracking?.();
33171
33562
  await managed.journalWriter.close();
33172
33563
  this.sessions.delete(sessionId);
33173
33564
  });
@@ -38852,7 +39243,7 @@ var require_node_loaders = /* @__PURE__ */ __commonJSMin(((exports, module) => {
38852
39243
  };
38853
39244
  return _setPrototypeOf(o, p);
38854
39245
  }
38855
- var fs$9 = __require("fs");
39246
+ var fs$10 = __require("fs");
38856
39247
  var path$10 = __require("path");
38857
39248
  var Loader = require_loader();
38858
39249
  var PrecompiledLoader = require_precompiled_loader().PrecompiledLoader;
@@ -38876,7 +39267,7 @@ var require_node_loaders = /* @__PURE__ */ __commonJSMin(((exports, module) => {
38876
39267
  } catch (e) {
38877
39268
  throw new Error("watch requires chokidar to be installed");
38878
39269
  }
38879
- var paths = _this.searchPaths.filter(fs$9.existsSync);
39270
+ var paths = _this.searchPaths.filter(fs$10.existsSync);
38880
39271
  var watcher = chokidar.watch(paths);
38881
39272
  watcher.on("all", function(event, fullname) {
38882
39273
  fullname = path$10.resolve(fullname);
@@ -38895,7 +39286,7 @@ var require_node_loaders = /* @__PURE__ */ __commonJSMin(((exports, module) => {
38895
39286
  for (var i = 0; i < paths.length; i++) {
38896
39287
  var basePath = path$10.resolve(paths[i]);
38897
39288
  var p = path$10.resolve(paths[i], name);
38898
- if (p.indexOf(basePath) === 0 && fs$9.existsSync(p)) {
39289
+ if (p.indexOf(basePath) === 0 && fs$10.existsSync(p)) {
38899
39290
  fullpath = p;
38900
39291
  break;
38901
39292
  }
@@ -38903,7 +39294,7 @@ var require_node_loaders = /* @__PURE__ */ __commonJSMin(((exports, module) => {
38903
39294
  if (!fullpath) return null;
38904
39295
  this.pathsToNames[fullpath] = name;
38905
39296
  var source = {
38906
- src: fs$9.readFileSync(fullpath, "utf-8"),
39297
+ src: fs$10.readFileSync(fullpath, "utf-8"),
38907
39298
  path: fullpath,
38908
39299
  noCache: this.noCache
38909
39300
  };
@@ -38951,7 +39342,7 @@ var require_node_loaders = /* @__PURE__ */ __commonJSMin(((exports, module) => {
38951
39342
  }
38952
39343
  this.pathsToNames[fullpath] = name;
38953
39344
  var source = {
38954
- src: fs$9.readFileSync(fullpath, "utf-8"),
39345
+ src: fs$10.readFileSync(fullpath, "utf-8"),
38955
39346
  path: fullpath,
38956
39347
  noCache: this.noCache
38957
39348
  };
@@ -39695,7 +40086,7 @@ var require_precompile_global = /* @__PURE__ */ __commonJSMin(((exports, module)
39695
40086
  //#endregion
39696
40087
  //#region ../../node_modules/.pnpm/nunjucks@3.2.4/node_modules/nunjucks/src/precompile.js
39697
40088
  var require_precompile = /* @__PURE__ */ __commonJSMin(((exports, module) => {
39698
- var fs$8 = __require("fs");
40089
+ var fs$9 = __require("fs");
39699
40090
  var path$8 = __require("path");
39700
40091
  var _prettifyError = require_lib$1()._prettifyError;
39701
40092
  var compiler = require_compiler();
@@ -39720,27 +40111,27 @@ var require_precompile = /* @__PURE__ */ __commonJSMin(((exports, module) => {
39720
40111
  var env = opts.env || new Environment([]);
39721
40112
  var wrapper = opts.wrapper || precompileGlobal;
39722
40113
  if (opts.isString) return precompileString(input, opts);
39723
- var pathStats = fs$8.existsSync(input) && fs$8.statSync(input);
40114
+ var pathStats = fs$9.existsSync(input) && fs$9.statSync(input);
39724
40115
  var precompiled = [];
39725
40116
  var templates = [];
39726
40117
  function addTemplates(dir) {
39727
- fs$8.readdirSync(dir).forEach(function(file) {
40118
+ fs$9.readdirSync(dir).forEach(function(file) {
39728
40119
  var filepath = path$8.join(dir, file);
39729
40120
  var subpath = filepath.substr(path$8.join(input, "/").length);
39730
- var stat = fs$8.statSync(filepath);
40121
+ var stat = fs$9.statSync(filepath);
39731
40122
  if (stat && stat.isDirectory()) {
39732
40123
  subpath += "/";
39733
40124
  if (!match(subpath, opts.exclude)) addTemplates(filepath);
39734
40125
  } else if (match(subpath, opts.include)) templates.push(filepath);
39735
40126
  });
39736
40127
  }
39737
- if (pathStats.isFile()) precompiled.push(_precompile(fs$8.readFileSync(input, "utf-8"), opts.name || input, env));
40128
+ if (pathStats.isFile()) precompiled.push(_precompile(fs$9.readFileSync(input, "utf-8"), opts.name || input, env));
39738
40129
  else if (pathStats.isDirectory()) {
39739
40130
  addTemplates(input);
39740
40131
  for (var i = 0; i < templates.length; i++) {
39741
40132
  var name = templates[i].replace(path$8.join(input, "/"), "");
39742
40133
  try {
39743
- precompiled.push(_precompile(fs$8.readFileSync(templates[i], "utf-8"), name, env));
40134
+ precompiled.push(_precompile(fs$9.readFileSync(templates[i], "utf-8"), name, env));
39744
40135
  } catch (e) {
39745
40136
  if (opts.force) console.error(e);
39746
40137
  else throw e;
@@ -51014,7 +51405,7 @@ var require_util = /* @__PURE__ */ __commonJSMin(((exports) => {
51014
51405
  exports.removeUndefinedValuesInObject = removeUndefinedValuesInObject;
51015
51406
  exports.isValidFile = isValidFile;
51016
51407
  exports.getWellKnownCertificateConfigFileLocation = getWellKnownCertificateConfigFileLocation;
51017
- const fs$7 = __require("fs");
51408
+ const fs$8 = __require("fs");
51018
51409
  const os$1 = __require("os");
51019
51410
  const path$6 = __require("path");
51020
51411
  const WELL_KNOWN_CERTIFICATE_CONFIG_FILE = "certificate_config.json";
@@ -51134,7 +51525,7 @@ var require_util = /* @__PURE__ */ __commonJSMin(((exports) => {
51134
51525
  */
51135
51526
  async function isValidFile(filePath) {
51136
51527
  try {
51137
- return (await fs$7.promises.lstat(filePath)).isFile();
51528
+ return (await fs$8.promises.lstat(filePath)).isFile();
51138
51529
  } catch (e) {
51139
51530
  return false;
51140
51531
  }
@@ -52847,10 +53238,10 @@ var require_getCredentials = /* @__PURE__ */ __commonJSMin(((exports) => {
52847
53238
  Object.defineProperty(exports, "__esModule", { value: true });
52848
53239
  exports.getCredentials = getCredentials;
52849
53240
  const path$5 = __require("path");
52850
- const fs$6 = __require("fs");
53241
+ const fs$7 = __require("fs");
52851
53242
  const util_1$1 = __require("util");
52852
53243
  const errorWithCode_1 = require_errorWithCode();
52853
- const readFile = fs$6.readFile ? (0, util_1$1.promisify)(fs$6.readFile) : async () => {
53244
+ const readFile = fs$7.readFile ? (0, util_1$1.promisify)(fs$7.readFile) : async () => {
52854
53245
  throw new errorWithCode_1.ErrorWithCode("use key rather than keyFile.", "MISSING_CREDENTIALS");
52855
53246
  };
52856
53247
  var ExtensionFiles;
@@ -54397,10 +54788,10 @@ var require_filesubjecttokensupplier = /* @__PURE__ */ __commonJSMin(((exports)
54397
54788
  Object.defineProperty(exports, "__esModule", { value: true });
54398
54789
  exports.FileSubjectTokenSupplier = void 0;
54399
54790
  const util_1 = __require("util");
54400
- const fs$5 = __require("fs");
54401
- const readFile = (0, util_1.promisify)(fs$5.readFile ?? (() => {}));
54402
- const realpath = (0, util_1.promisify)(fs$5.realpath ?? (() => {}));
54403
- const lstat = (0, util_1.promisify)(fs$5.lstat ?? (() => {}));
54791
+ const fs$6 = __require("fs");
54792
+ const readFile = (0, util_1.promisify)(fs$6.readFile ?? (() => {}));
54793
+ const realpath = (0, util_1.promisify)(fs$6.realpath ?? (() => {}));
54794
+ const lstat = (0, util_1.promisify)(fs$6.lstat ?? (() => {}));
54404
54795
  /**
54405
54796
  * Internal subject token supplier implementation used when a file location
54406
54797
  * is configured in the credential configuration used to build an {@link IdentityPoolClient}
@@ -54502,7 +54893,7 @@ var require_certificatesubjecttokensupplier = /* @__PURE__ */ __commonJSMin(((ex
54502
54893
  Object.defineProperty(exports, "__esModule", { value: true });
54503
54894
  exports.CertificateSubjectTokenSupplier = exports.InvalidConfigurationError = exports.CertificateSourceUnavailableError = exports.CERTIFICATE_CONFIGURATION_ENV_VARIABLE = void 0;
54504
54895
  const util_1 = require_util();
54505
- const fs$4 = __require("fs");
54896
+ const fs$5 = __require("fs");
54506
54897
  const crypto_1 = __require("crypto");
54507
54898
  const https$1 = __require("https");
54508
54899
  exports.CERTIFICATE_CONFIGURATION_ENV_VARIABLE = "GOOGLE_API_CERTIFICATE_CONFIG";
@@ -54597,7 +54988,7 @@ var require_certificatesubjecttokensupplier = /* @__PURE__ */ __commonJSMin(((ex
54597
54988
  const configPath = this.certificateConfigPath;
54598
54989
  let fileContents;
54599
54990
  try {
54600
- fileContents = await fs$4.promises.readFile(configPath, "utf8");
54991
+ fileContents = await fs$5.promises.readFile(configPath, "utf8");
54601
54992
  } catch (err) {
54602
54993
  throw new CertificateSourceUnavailableError(`Failed to read certificate config file at: ${configPath}`);
54603
54994
  }
@@ -54622,13 +55013,13 @@ var require_certificatesubjecttokensupplier = /* @__PURE__ */ __commonJSMin(((ex
54622
55013
  async #getKeyAndCert(certPath, keyPath) {
54623
55014
  let cert, key;
54624
55015
  try {
54625
- cert = await fs$4.promises.readFile(certPath);
55016
+ cert = await fs$5.promises.readFile(certPath);
54626
55017
  new crypto_1.X509Certificate(cert);
54627
55018
  } catch (err) {
54628
55019
  throw new CertificateSourceUnavailableError(`Failed to read certificate file at ${certPath}: ${err instanceof Error ? err.message : String(err)}`);
54629
55020
  }
54630
55021
  try {
54631
- key = await fs$4.promises.readFile(keyPath);
55022
+ key = await fs$5.promises.readFile(keyPath);
54632
55023
  (0, crypto_1.createPrivateKey)(key);
54633
55024
  } catch (err) {
54634
55025
  throw new CertificateSourceUnavailableError(`Failed to read private key file at ${keyPath}: ${err instanceof Error ? err.message : String(err)}`);
@@ -54647,7 +55038,7 @@ var require_certificatesubjecttokensupplier = /* @__PURE__ */ __commonJSMin(((ex
54647
55038
  const leafCert = new crypto_1.X509Certificate(leafCertBuffer);
54648
55039
  if (!this.trustChainPath) return JSON.stringify([leafCert.raw.toString("base64")]);
54649
55040
  try {
54650
- const chainCerts = ((await fs$4.promises.readFile(this.trustChainPath, "utf8")).match(/-----BEGIN CERTIFICATE-----[^-]+-----END CERTIFICATE-----/g) ?? []).map((pem, index) => {
55041
+ const chainCerts = ((await fs$5.promises.readFile(this.trustChainPath, "utf8")).match(/-----BEGIN CERTIFICATE-----[^-]+-----END CERTIFICATE-----/g) ?? []).map((pem, index) => {
54651
55042
  try {
54652
55043
  return new crypto_1.X509Certificate(pem);
54653
55044
  } catch (err) {
@@ -55307,7 +55698,7 @@ var require_pluggable_auth_handler = /* @__PURE__ */ __commonJSMin(((exports) =>
55307
55698
  exports.PluggableAuthHandler = exports.ExecutableError = void 0;
55308
55699
  const executable_response_1 = require_executable_response();
55309
55700
  const childProcess = __require("child_process");
55310
- const fs$3 = __require("fs");
55701
+ const fs$4 = __require("fs");
55311
55702
  /**
55312
55703
  * Error thrown from the executable run by PluggableAuthClient.
55313
55704
  */
@@ -55384,12 +55775,12 @@ var require_pluggable_auth_handler = /* @__PURE__ */ __commonJSMin(((exports) =>
55384
55775
  if (!this.outputFile || this.outputFile.length === 0) return;
55385
55776
  let filePath;
55386
55777
  try {
55387
- filePath = await fs$3.promises.realpath(this.outputFile);
55778
+ filePath = await fs$4.promises.realpath(this.outputFile);
55388
55779
  } catch {
55389
55780
  return;
55390
55781
  }
55391
- if (!(await fs$3.promises.lstat(filePath)).isFile()) return;
55392
- const responseString = await fs$3.promises.readFile(filePath, { encoding: "utf8" });
55782
+ if (!(await fs$4.promises.lstat(filePath)).isFile()) return;
55783
+ const responseString = await fs$4.promises.readFile(filePath, { encoding: "utf8" });
55393
55784
  if (responseString === "") return;
55394
55785
  try {
55395
55786
  const responseJson = JSON.parse(responseString);
@@ -55811,7 +56202,7 @@ var require_googleauth = /* @__PURE__ */ __commonJSMin(((exports) => {
55811
56202
  Object.defineProperty(exports, "__esModule", { value: true });
55812
56203
  exports.GoogleAuth = exports.GoogleAuthExceptionMessages = void 0;
55813
56204
  const child_process_1 = __require("child_process");
55814
- const fs$2 = __require("fs");
56205
+ const fs$3 = __require("fs");
55815
56206
  const gaxios_1 = require_src$3();
55816
56207
  const gcpMetadata = require_src$1();
55817
56208
  const os = __require("os");
@@ -56059,7 +56450,7 @@ var require_googleauth = /* @__PURE__ */ __commonJSMin(((exports) => {
56059
56450
  }
56060
56451
  if (location) {
56061
56452
  location = path$4.join(location, "gcloud", "application_default_credentials.json");
56062
- if (!fs$2.existsSync(location)) location = null;
56453
+ if (!fs$3.existsSync(location)) location = null;
56063
56454
  }
56064
56455
  if (!location) return null;
56065
56456
  return await this._getApplicationCredentialsFromFilePath(location, options);
@@ -56073,13 +56464,13 @@ var require_googleauth = /* @__PURE__ */ __commonJSMin(((exports) => {
56073
56464
  async _getApplicationCredentialsFromFilePath(filePath, options = {}) {
56074
56465
  if (!filePath || filePath.length === 0) throw new Error("The file path is invalid.");
56075
56466
  try {
56076
- filePath = fs$2.realpathSync(filePath);
56077
- if (!fs$2.lstatSync(filePath).isFile()) throw new Error();
56467
+ filePath = fs$3.realpathSync(filePath);
56468
+ if (!fs$3.lstatSync(filePath).isFile()) throw new Error();
56078
56469
  } catch (err) {
56079
56470
  if (err instanceof Error) err.message = `The file at ${filePath} does not exist, or it is not a file. ${err.message}`;
56080
56471
  throw err;
56081
56472
  }
56082
- const readStream = fs$2.createReadStream(filePath);
56473
+ const readStream = fs$3.createReadStream(filePath);
56083
56474
  return this.fromStream(readStream, options);
56084
56475
  }
56085
56476
  /**
@@ -56346,7 +56737,7 @@ var require_googleauth = /* @__PURE__ */ __commonJSMin(((exports) => {
56346
56737
  if (this.jsonContent) return this._cacheClientFromJSON(this.jsonContent, this.clientOptions);
56347
56738
  else if (this.keyFilename) {
56348
56739
  const filePath = path$4.resolve(this.keyFilename);
56349
- const stream = fs$2.createReadStream(filePath);
56740
+ const stream = fs$3.createReadStream(filePath);
56350
56741
  return await this.fromStreamAsync(stream, this.clientOptions);
56351
56742
  } else if (this.apiKey) {
56352
56743
  const client = await this.fromAPIKey(this.apiKey, this.clientOptions);
@@ -74856,7 +75247,7 @@ var NodeUploader = class {
74856
75247
  type: void 0
74857
75248
  };
74858
75249
  if (typeof file === "string") {
74859
- fileStat.size = (await fs$1.stat(file)).size;
75250
+ fileStat.size = (await fs$2.stat(file)).size;
74860
75251
  fileStat.type = this.inferMimeType(file);
74861
75252
  return fileStat;
74862
75253
  } else return await getBlobStat(file);
@@ -74989,7 +75380,7 @@ var NodeUploader = class {
74989
75380
  let fileHandle;
74990
75381
  const fileName = path$1$1.basename(file);
74991
75382
  try {
74992
- fileHandle = await fs$1.open(file, "r");
75383
+ fileHandle = await fs$2.open(file, "r");
74993
75384
  if (!fileHandle) throw new Error(`Failed to open file`);
74994
75385
  fileSize = (await fileHandle.stat()).size;
74995
75386
  while (offset < fileSize) {
@@ -91423,7 +91814,7 @@ var require_clone = /* @__PURE__ */ __commonJSMin(((exports, module) => {
91423
91814
  //#endregion
91424
91815
  //#region ../../node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/graceful-fs.js
91425
91816
  var require_graceful_fs = /* @__PURE__ */ __commonJSMin(((exports, module) => {
91426
- var fs = __require("fs");
91817
+ var fs$1 = __require("fs");
91427
91818
  var polyfills = require_polyfills();
91428
91819
  var legacy = require_legacy_streams();
91429
91820
  var clone = require_clone();
@@ -91452,36 +91843,36 @@ var require_graceful_fs = /* @__PURE__ */ __commonJSMin(((exports, module) => {
91452
91843
  m = "GFS4: " + m.split(/\n/).join("\nGFS4: ");
91453
91844
  console.error(m);
91454
91845
  };
91455
- if (!fs[gracefulQueue]) {
91456
- publishQueue(fs, global[gracefulQueue] || []);
91457
- fs.close = (function(fs$close) {
91846
+ if (!fs$1[gracefulQueue]) {
91847
+ publishQueue(fs$1, global[gracefulQueue] || []);
91848
+ fs$1.close = (function(fs$close) {
91458
91849
  function close(fd, cb) {
91459
- return fs$close.call(fs, fd, function(err) {
91850
+ return fs$close.call(fs$1, fd, function(err) {
91460
91851
  if (!err) resetQueue();
91461
91852
  if (typeof cb === "function") cb.apply(this, arguments);
91462
91853
  });
91463
91854
  }
91464
91855
  Object.defineProperty(close, previousSymbol, { value: fs$close });
91465
91856
  return close;
91466
- })(fs.close);
91467
- fs.closeSync = (function(fs$closeSync) {
91857
+ })(fs$1.close);
91858
+ fs$1.closeSync = (function(fs$closeSync) {
91468
91859
  function closeSync(fd) {
91469
- fs$closeSync.apply(fs, arguments);
91860
+ fs$closeSync.apply(fs$1, arguments);
91470
91861
  resetQueue();
91471
91862
  }
91472
91863
  Object.defineProperty(closeSync, previousSymbol, { value: fs$closeSync });
91473
91864
  return closeSync;
91474
- })(fs.closeSync);
91865
+ })(fs$1.closeSync);
91475
91866
  if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || "")) process.on("exit", function() {
91476
- debug(fs[gracefulQueue]);
91477
- __require("assert").equal(fs[gracefulQueue].length, 0);
91867
+ debug(fs$1[gracefulQueue]);
91868
+ __require("assert").equal(fs$1[gracefulQueue].length, 0);
91478
91869
  });
91479
91870
  }
91480
- if (!global[gracefulQueue]) publishQueue(global, fs[gracefulQueue]);
91481
- module.exports = patch(clone(fs));
91482
- if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) {
91483
- module.exports = patch(fs);
91484
- fs.__patched = true;
91871
+ if (!global[gracefulQueue]) publishQueue(global, fs$1[gracefulQueue]);
91872
+ module.exports = patch(clone(fs$1));
91873
+ if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs$1.__patched) {
91874
+ module.exports = patch(fs$1);
91875
+ fs$1.__patched = true;
91485
91876
  }
91486
91877
  function patch(fs) {
91487
91878
  polyfills(fs);
@@ -91736,23 +92127,23 @@ var require_graceful_fs = /* @__PURE__ */ __commonJSMin(((exports, module) => {
91736
92127
  }
91737
92128
  function enqueue(elem) {
91738
92129
  debug("ENQUEUE", elem[0].name, elem[1]);
91739
- fs[gracefulQueue].push(elem);
92130
+ fs$1[gracefulQueue].push(elem);
91740
92131
  retry();
91741
92132
  }
91742
92133
  var retryTimer;
91743
92134
  function resetQueue() {
91744
92135
  var now = Date.now();
91745
- for (var i = 0; i < fs[gracefulQueue].length; ++i) if (fs[gracefulQueue][i].length > 2) {
91746
- fs[gracefulQueue][i][3] = now;
91747
- fs[gracefulQueue][i][4] = now;
92136
+ for (var i = 0; i < fs$1[gracefulQueue].length; ++i) if (fs$1[gracefulQueue][i].length > 2) {
92137
+ fs$1[gracefulQueue][i][3] = now;
92138
+ fs$1[gracefulQueue][i][4] = now;
91748
92139
  }
91749
92140
  retry();
91750
92141
  }
91751
92142
  function retry() {
91752
92143
  clearTimeout(retryTimer);
91753
92144
  retryTimer = void 0;
91754
- if (fs[gracefulQueue].length === 0) return;
91755
- var elem = fs[gracefulQueue].shift();
92145
+ if (fs$1[gracefulQueue].length === 0) return;
92146
+ var elem = fs$1[gracefulQueue].shift();
91756
92147
  var fn = elem[0];
91757
92148
  var args = elem[1];
91758
92149
  var err = elem[2];
@@ -91771,7 +92162,7 @@ var require_graceful_fs = /* @__PURE__ */ __commonJSMin(((exports, module) => {
91771
92162
  if (sinceAttempt >= Math.min(sinceStart * 1.2, 100)) {
91772
92163
  debug("RETRY", fn.name, args);
91773
92164
  fn.apply(null, args.concat([startTime]));
91774
- } else fs[gracefulQueue].push(elem);
92165
+ } else fs$1[gracefulQueue].push(elem);
91775
92166
  }
91776
92167
  if (retryTimer === void 0) retryTimer = setTimeout(retry, 0);
91777
92168
  }
@@ -94290,7 +94681,7 @@ function shellQuote(path) {
94290
94681
  }
94291
94682
  //#endregion
94292
94683
  //#region src/tui/components/messages/assistant-message.ts
94293
- const BULLET$1 = " ";
94684
+ const BULLET$1 = " ";
94294
94685
  const INDENT$2 = " ";
94295
94686
  var AssistantMessageComponent = class {
94296
94687
  contentContainer;
@@ -94363,7 +94754,7 @@ var ImageThumbnail = class extends Container {
94363
94754
  };
94364
94755
  //#endregion
94365
94756
  //#region src/tui/components/messages/thinking.ts
94366
- const BULLET = " ";
94757
+ const BULLET = " ";
94367
94758
  const INDENT$1 = " ";
94368
94759
  const PREVIEW_LINES$1 = 3;
94369
94760
  var ThinkingComponent = class {
@@ -94563,6 +94954,19 @@ function extractApprovedPlan(output) {
94563
94954
  if (markerIndex < 0) return "";
94564
94955
  return output.slice(markerIndex + 17).trim();
94565
94956
  }
94957
+ function compactPreview(value) {
94958
+ const oneLine = value.replaceAll(/\s+/g, " ").trim();
94959
+ if (oneLine.length <= 160) return oneLine;
94960
+ return oneLine.slice(0, 157) + "...";
94961
+ }
94962
+ function parseArgsPreview(value) {
94963
+ if (value.trim().length === 0) return {};
94964
+ try {
94965
+ const parsed = JSON.parse(value);
94966
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) return parsed;
94967
+ } catch {}
94968
+ return { arguments: value };
94969
+ }
94566
94970
  function extractKeyArgument(toolName, args) {
94567
94971
  const candidates = {
94568
94972
  Shell: ["command"],
@@ -94650,6 +95054,12 @@ var ToolCallComponent = class extends Container {
94650
95054
  this.headerText.setText(this.buildHeader());
94651
95055
  this.rebuildContent();
94652
95056
  }
95057
+ updateToolCall(toolCall) {
95058
+ this.toolCall = toolCall;
95059
+ this.headerText.setText(this.buildHeader());
95060
+ this.rebuildBody();
95061
+ this.ui?.requestRender();
95062
+ }
94653
95063
  applySubagentReplay(subagent) {
94654
95064
  if (subagent === void 0) return;
94655
95065
  this.subagentAgentId = subagent.id;
@@ -94688,9 +95098,23 @@ var ToolCallComponent = class extends Container {
94688
95098
  this.ui?.requestRender();
94689
95099
  }
94690
95100
  appendSubToolCall(call) {
95101
+ const existing = this.ongoingSubCalls.get(call.id);
94691
95102
  this.ongoingSubCalls.set(call.id, {
94692
95103
  name: call.name,
94693
- args: call.args
95104
+ args: call.args,
95105
+ ...existing?.streamingArguments !== void 0 ? { streamingArguments: existing.streamingArguments } : {}
95106
+ });
95107
+ this.rebuildContent();
95108
+ this.ui?.requestRender();
95109
+ }
95110
+ appendSubToolCallDelta(delta) {
95111
+ const existing = this.ongoingSubCalls.get(delta.id);
95112
+ const nextArgsText = `${existing?.streamingArguments ?? ""}${delta.argumentsPart ?? ""}`;
95113
+ const parsed = parseArgsPreview(nextArgsText);
95114
+ this.ongoingSubCalls.set(delta.id, {
95115
+ name: delta.name ?? existing?.name ?? "Tool",
95116
+ args: parsed,
95117
+ streamingArguments: nextArgsText
94694
95118
  });
94695
95119
  this.rebuildContent();
94696
95120
  this.ui?.requestRender();
@@ -94717,8 +95141,8 @@ var ToolCallComponent = class extends Container {
94717
95141
  const isFinished = result !== void 0;
94718
95142
  const isError = result?.is_error ?? false;
94719
95143
  let bullet;
94720
- if (isFinished) bullet = isError ? chalk.hex(colors.error)("✗ ") : chalk.hex(colors.success)(" ");
94721
- else bullet = this.blinkOn ? chalk.white(" ") : " ";
95144
+ if (isFinished) bullet = isError ? chalk.hex(colors.error)("✗ ") : chalk.hex(colors.success)(" ");
95145
+ else bullet = this.blinkOn ? chalk.white(" ") : " ";
94722
95146
  if (toolCall.name === "ExitPlanMode") return chalk.hex(colors.primary).bold("Current plan");
94723
95147
  if (toolCall.name === "AskUserQuestion") {
94724
95148
  const label = isFinished ? isError ? "Could not collect your input" : "Collected your answers" : "Waiting for your input";
@@ -94736,6 +95160,13 @@ var ToolCallComponent = class extends Container {
94736
95160
  this.buildContent();
94737
95161
  this.buildSubagentBlock();
94738
95162
  }
95163
+ rebuildBody() {
95164
+ while (this.children.length > 2) this.children.pop();
95165
+ this.buildCallPreview();
95166
+ this.callPreviewEndIndex = this.children.length;
95167
+ this.buildContent();
95168
+ this.buildSubagentBlock();
95169
+ }
94739
95170
  buildSubagentBlock() {
94740
95171
  if (this.subagentAgentId === void 0 && this.ongoingSubCalls.size === 0 && this.finishedSubCalls.length === 0 && this.subagentText.length === 0) return;
94741
95172
  const dim = chalk.dim;
@@ -94757,6 +95188,7 @@ var ToolCallComponent = class extends Container {
94757
95188
  const nameCol = chalk.hex(this.colors.primary)(call.name);
94758
95189
  const argCol = keyArg ? dim(` (${keyArg})`) : "";
94759
95190
  this.addChild(new Text(` ${dim("…")} Using ${nameCol}${argCol}`, 0, 0));
95191
+ if (call.streamingArguments !== void 0 && call.streamingArguments.length > 0) this.addChild(new Text(` ${dim(compactPreview(call.streamingArguments))}`, 0, 0));
94760
95192
  }
94761
95193
  if (this.subagentText.length > 0) {
94762
95194
  const tailLines = this.subagentText.split("\n").slice(-3);
@@ -94773,15 +95205,19 @@ var ToolCallComponent = class extends Container {
94773
95205
  this.buildPlanPreview();
94774
95206
  return;
94775
95207
  }
95208
+ if (this.toolCall.streamingArguments !== void 0) {
95209
+ this.addChild(new Text(chalk.dim(compactPreview(this.toolCall.streamingArguments)), 2, 0));
95210
+ return;
95211
+ }
94776
95212
  if (name === "Write" || name === "WriteFile") {
94777
95213
  const content = str(this.toolCall.args["content"]);
94778
95214
  if (content.length === 0) return;
94779
95215
  const allLines = highlightLines(content, langFromPath(str(this.toolCall.args["file_path"] ?? this.toolCall.args["path"])));
94780
95216
  const shown = allLines.slice(0, CALL_PREVIEW_LINES);
94781
95217
  const remaining = allLines.length - shown.length;
94782
- for (let i = 0; i < shown.length; i++) {
95218
+ for (const [i, line] of shown.entries()) {
94783
95219
  const lineNum = chalk.dim(String(i + 1).padStart(4) + " ");
94784
- this.addChild(new Text(lineNum + shown[i], 2, 0));
95220
+ this.addChild(new Text(lineNum + line, 2, 0));
94785
95221
  }
94786
95222
  if (remaining > 0) this.addChild(new Text(chalk.dim(`... (${String(remaining)} more lines, ${String(allLines.length)} total)`), 2, 0));
94787
95223
  } else if (name === "Edit" || name === "EditFile") {
@@ -94815,15 +95251,14 @@ var ToolCallComponent = class extends Container {
94815
95251
  return;
94816
95252
  }
94817
95253
  if (this.toolCall.name === "SetTodoList" && !result.is_error) return;
94818
- if (this.toolCall.name === "AskUserQuestion" && !result.is_error) {
94819
- if (this.renderAskUserQuestionResult(result.output)) return;
94820
- }
95254
+ if (this.toolCall.name === "AskUserQuestion" && !result.is_error && this.renderAskUserQuestionResult(result.output)) return;
95255
+ const tint = result.is_error ? chalk.hex(this.colors.error) : chalk.dim;
94821
95256
  const lines = result.output.split("\n");
94822
- if (this.expanded) this.addChild(new Text(chalk.dim(result.output), 2, 0));
95257
+ if (this.expanded) this.addChild(new Text(tint(result.output), 2, 0));
94823
95258
  else {
94824
95259
  const shown = lines.slice(0, PREVIEW_LINES);
94825
95260
  const remaining = lines.length - shown.length;
94826
- this.addChild(new Text(chalk.dim(shown.join("\n")), 2, 0));
95261
+ this.addChild(new Text(tint(shown.join("\n")), 2, 0));
94827
95262
  if (remaining > 0) this.addChild(new Text(chalk.dim(`... (${String(remaining)} more lines, ctrl+o to expand)`), 2, 0));
94828
95263
  }
94829
95264
  }
@@ -95200,6 +95635,15 @@ function handleSubagentSourceEvent(ectx, payload) {
95200
95635
  });
95201
95636
  return;
95202
95637
  }
95638
+ if (payload.method === "tool.call.delta") {
95639
+ const d = payload.data;
95640
+ if (typeof d.tool_call_id === "string") tc.appendSubToolCallDelta({
95641
+ id: `${payload.source_id}:${d.tool_call_id}`,
95642
+ ...typeof d.name === "string" ? { name: d.name } : {},
95643
+ argumentsPart: typeof d.arguments_part === "string" ? d.arguments_part : null
95644
+ });
95645
+ return;
95646
+ }
95203
95647
  if (payload.method === "tool.result") {
95204
95648
  const d = payload.data;
95205
95649
  if (typeof d.tool_call_id === "string") tc.finishSubToolCall({
@@ -96540,6 +96984,7 @@ function createTUIState(options) {
96540
96984
  assistantStreamActive: false,
96541
96985
  thinkingDraft: "",
96542
96986
  activeToolCalls: /* @__PURE__ */ new Map(),
96987
+ streamingToolCallArguments: /* @__PURE__ */ new Map(),
96543
96988
  toastTimers: /* @__PURE__ */ new Map(),
96544
96989
  pendingExit: null,
96545
96990
  queuedMessages: [],
@@ -96701,6 +97146,7 @@ function sendMessageInternal(state, addEntry, input, options) {
96701
97146
  state.assistantStreamActive = false;
96702
97147
  state.thinkingDraft = "";
96703
97148
  state.activeToolCalls.clear();
97149
+ state.streamingToolCallArguments.clear();
96704
97150
  state.livePane = {
96705
97151
  ...state.livePane,
96706
97152
  mode: "waiting",
@@ -97319,6 +97765,7 @@ function releaseSessionSideEffects(state) {
97319
97765
  state.queuedMessages = [];
97320
97766
  state.queueIdCounter = 0;
97321
97767
  state.activeToolCalls.clear();
97768
+ state.streamingToolCallArguments?.clear();
97322
97769
  state.currentTurnId = void 0;
97323
97770
  state.assistantDraft = "";
97324
97771
  state.assistantStreamActive = false;
@@ -97370,8 +97817,8 @@ var CompactionComponent = class extends Container {
97370
97817
  this.stopBlink();
97371
97818
  }
97372
97819
  buildHeader() {
97373
- if (this.done) return `${chalk.hex(this.colors.success)(" ")}${chalk.hex(this.colors.success).bold("Compaction complete")}${this.tokensBefore !== void 0 && this.tokensAfter !== void 0 ? chalk.dim(` (${String(this.tokensBefore)} → ${String(this.tokensAfter)} tokens)`) : ""}`;
97374
- return `${this.blinkOn ? chalk.white(" ") : " "}${chalk.hex(this.colors.primary).bold("Compacting context...")}`;
97820
+ if (this.done) return `${chalk.hex(this.colors.success)(" ")}${chalk.hex(this.colors.success).bold("Compaction complete")}${this.tokensBefore !== void 0 && this.tokensAfter !== void 0 ? chalk.dim(` (${String(this.tokensBefore)} → ${String(this.tokensAfter)} tokens)`) : ""}`;
97821
+ return `${this.blinkOn ? chalk.white(" ") : " "}${chalk.hex(this.colors.primary).bold("Compacting context...")}`;
97375
97822
  }
97376
97823
  startBlink() {
97377
97824
  this.blinkTimer = setInterval(() => {
@@ -97577,6 +98024,7 @@ async function dispatchSlashCommand(input, state, buildCtx, addEntry) {
97577
98024
  //#endregion
97578
98025
  //#region src/tui/handlers/turn-lifecycle.ts
97579
98026
  function handleTurnBegin(ectx, _data) {
98027
+ ectx.state.streamingToolCallArguments.clear();
97580
98028
  ectx.patchLivePane({
97581
98029
  mode: "waiting",
97582
98030
  thinkingText: "",
@@ -97594,6 +98042,7 @@ function handleTurnBegin(ectx, _data) {
97594
98042
  function handleTurnEnd(ectx, _data, sendQueued) {
97595
98043
  const todos = ectx.state.todoPanel.getTodos();
97596
98044
  if (todos.length > 0 && todos.every((t) => t.status === "done")) ectx.setTodoList([]);
98045
+ ectx.state.streamingToolCallArguments.clear();
97597
98046
  finalizeTurn(ectx, sendQueued);
97598
98047
  }
97599
98048
  function handleStepBegin(ectx) {
@@ -97645,24 +98094,18 @@ function handleContentDelta(ectx, data) {
97645
98094
  streamingPhase: "composing",
97646
98095
  streamingStartTime: Date.now()
97647
98096
  });
97648
- return;
97649
- }
97650
- if (data.type === "tool_call_part") {
97651
- if (ectx.state.thinkingDraft.length > 0) flushThinkingToTranscript(ectx, "idle");
97652
- ectx.patchLivePane({
97653
- mode: "idle",
97654
- pendingToolCall: null,
97655
- pendingApproval: null,
97656
- pendingQuestion: null
97657
- });
97658
- ectx.setAppState({
97659
- streamingPhase: "composing",
97660
- streamingStartTime: Date.now()
97661
- });
97662
98097
  }
97663
98098
  }
97664
98099
  //#endregion
97665
98100
  //#region src/tui/handlers/tool.ts
98101
+ function parseStreamingArgs(argumentsText) {
98102
+ if (argumentsText.trim().length === 0) return {};
98103
+ try {
98104
+ const parsed = JSON.parse(argumentsText);
98105
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) return parsed;
98106
+ } catch {}
98107
+ return { arguments: argumentsText };
98108
+ }
97666
98109
  function isTodoItemShape(value) {
97667
98110
  if (typeof value !== "object" || value === null) return false;
97668
98111
  const rec = value;
@@ -97676,15 +98119,53 @@ function handleToolCall(ectx, data) {
97676
98119
  args: data.args,
97677
98120
  description: data.description
97678
98121
  };
98122
+ const existing = ectx.state.activeToolCalls.get(data.id);
97679
98123
  ectx.state.activeToolCalls.set(data.id, toolCall);
97680
- flushTurnBuffers(ectx, "tool");
97681
- ectx.onToolCallStart(toolCall);
98124
+ ectx.state.streamingToolCallArguments.delete(data.id);
98125
+ const existingComponent = ectx.state.pendingToolComponents.get(data.id);
98126
+ if (existingComponent !== void 0) existingComponent.updateToolCall(toolCall);
98127
+ else if (existing === void 0) {
98128
+ flushTurnBuffers(ectx, "tool");
98129
+ ectx.onToolCallStart(toolCall);
98130
+ }
98131
+ ectx.patchLivePane({
98132
+ mode: "tool",
98133
+ pendingToolCall: toolCall,
98134
+ pendingApproval: null,
98135
+ pendingQuestion: null
98136
+ });
98137
+ }
98138
+ function handleToolCallDelta(ectx, data) {
98139
+ if (typeof data.tool_call_id !== "string" || data.tool_call_id.length === 0) return;
98140
+ const id = data.tool_call_id;
98141
+ const existing = ectx.state.streamingToolCallArguments.get(id);
98142
+ const argumentsText = `${existing?.argumentsText ?? ""}${data.arguments_part ?? ""}`;
98143
+ const name = data.name ?? existing?.name ?? ectx.state.activeToolCalls.get(id)?.name ?? "Tool";
98144
+ ectx.state.streamingToolCallArguments.set(id, {
98145
+ name,
98146
+ argumentsText
98147
+ });
98148
+ const toolCall = {
98149
+ id,
98150
+ name,
98151
+ args: parseStreamingArgs(argumentsText),
98152
+ streamingArguments: argumentsText
98153
+ };
98154
+ ectx.state.activeToolCalls.set(id, toolCall);
98155
+ if (ectx.state.thinkingDraft.length > 0) flushTurnBuffers(ectx, "tool");
98156
+ const existingComponent = ectx.state.pendingToolComponents.get(id);
98157
+ if (existingComponent !== void 0) existingComponent.updateToolCall(toolCall);
98158
+ else ectx.onToolCallStart(toolCall);
97682
98159
  ectx.patchLivePane({
97683
98160
  mode: "tool",
97684
98161
  pendingToolCall: toolCall,
97685
98162
  pendingApproval: null,
97686
98163
  pendingQuestion: null
97687
98164
  });
98165
+ ectx.setAppState({
98166
+ streamingPhase: "composing",
98167
+ streamingStartTime: Date.now()
98168
+ });
97688
98169
  }
97689
98170
  function handleToolResult(ectx, data) {
97690
98171
  const matchedCall = ectx.state.activeToolCalls.get(data.tool_call_id);
@@ -97698,7 +98179,7 @@ function handleToolResult(ectx, data) {
97698
98179
  if (matchedCall.name === "SetTodoList" && !data.is_error) {
97699
98180
  const rawTodos = matchedCall.args.todos;
97700
98181
  if (Array.isArray(rawTodos)) {
97701
- const sanitized = rawTodos.filter(isTodoItemShape).map((t) => ({
98182
+ const sanitized = rawTodos.filter((todo) => isTodoItemShape(todo)).map((t) => ({
97702
98183
  title: t.title,
97703
98184
  status: t.status
97704
98185
  }));
@@ -97707,6 +98188,7 @@ function handleToolResult(ectx, data) {
97707
98188
  }
97708
98189
  }
97709
98190
  ectx.state.activeToolCalls.delete(data.tool_call_id);
98191
+ ectx.state.streamingToolCallArguments.delete(data.tool_call_id);
97710
98192
  ectx.patchLivePane({
97711
98193
  mode: "waiting",
97712
98194
  pendingToolCall: null
@@ -97823,6 +98305,9 @@ function dispatchEvent(event, ectx, sendQueued) {
97823
98305
  case "tool.call":
97824
98306
  handleToolCall(ectx, event.data);
97825
98307
  break;
98308
+ case "tool.call.delta":
98309
+ handleToolCallDelta(ectx, event.data);
98310
+ break;
97826
98311
  case "tool.result":
97827
98312
  handleToolResult(ectx, event.data);
97828
98313
  break;
@@ -99092,7 +99577,7 @@ var ApprovalPanelComponent = class extends Container {
99092
99577
  focused = false;
99093
99578
  selectedIndex = 0;
99094
99579
  feedbackMode = false;
99095
- feedbackText = "";
99580
+ feedbackInput = new Input();
99096
99581
  expanded = false;
99097
99582
  onResponse;
99098
99583
  request;
@@ -99100,6 +99585,13 @@ var ApprovalPanelComponent = class extends Container {
99100
99585
  super();
99101
99586
  this.request = request;
99102
99587
  this.onResponse = onResponse;
99588
+ this.feedbackInput.onSubmit = (value) => {
99589
+ this.submit(this.selectedIndex, value);
99590
+ };
99591
+ this.feedbackInput.onEscape = () => {
99592
+ this.feedbackMode = false;
99593
+ this.feedbackInput.setValue("");
99594
+ };
99103
99595
  }
99104
99596
  submit(index, feedback = "") {
99105
99597
  const option = this.choiceAt(index);
@@ -99120,21 +99612,16 @@ var ApprovalPanelComponent = class extends Container {
99120
99612
  }
99121
99613
  onToggleToolExpand;
99122
99614
  handleInput(data) {
99615
+ if (matchesKey(data, Key.ctrl("c")) || matchesKey(data, Key.ctrl("d"))) {
99616
+ this.onResponse({ response: "rejected" });
99617
+ return;
99618
+ }
99123
99619
  if (matchesKey(data, Key.ctrl("o"))) {
99124
99620
  this.expanded = !this.expanded;
99125
99621
  this.onToggleToolExpand?.();
99126
99622
  return;
99127
99623
  }
99128
99624
  if (this.feedbackMode) {
99129
- if (matchesKey(data, Key.enter)) {
99130
- this.submit(this.selectedIndex, this.feedbackText);
99131
- return;
99132
- }
99133
- if (matchesKey(data, Key.escape)) {
99134
- this.feedbackMode = false;
99135
- this.feedbackText = "";
99136
- return;
99137
- }
99138
99625
  if (matchesKey(data, Key.up)) {
99139
99626
  this.feedbackMode = false;
99140
99627
  this.selectedIndex = (this.selectedIndex - 1 + this.choiceCount()) % this.choiceCount();
@@ -99145,16 +99632,7 @@ var ApprovalPanelComponent = class extends Container {
99145
99632
  this.selectedIndex = (this.selectedIndex + 1) % this.choiceCount();
99146
99633
  return;
99147
99634
  }
99148
- if (matchesKey(data, Key.backspace)) {
99149
- this.feedbackText = this.feedbackText.slice(0, -1);
99150
- return;
99151
- }
99152
- const printable = decodeKittyPrintable(data);
99153
- if (printable !== void 0) {
99154
- this.feedbackText += printable;
99155
- return;
99156
- }
99157
- if (data.length > 0 && !data.startsWith("\x1B")) this.feedbackText += data;
99635
+ this.feedbackInput.handleInput(data);
99158
99636
  return;
99159
99637
  }
99160
99638
  if (this.choiceCount() === 0) return;
@@ -99177,6 +99655,7 @@ var ApprovalPanelComponent = class extends Container {
99177
99655
  render(width) {
99178
99656
  this.clear();
99179
99657
  this.ensureValidSelection();
99658
+ this.feedbackInput.focused = this.focused && this.feedbackMode;
99180
99659
  const { data } = this.request;
99181
99660
  const horizontalBar = borderColor("─".repeat(width));
99182
99661
  const indent = (s) => ` ${s}`;
@@ -99201,12 +99680,12 @@ var ApprovalPanelComponent = class extends Container {
99201
99680
  const isSelected = idx === this.selectedIndex;
99202
99681
  const num = idx + 1;
99203
99682
  const labelWithNum = `${String(num)}. ${option.label}`;
99204
- if (this.feedbackMode && option.requires_feedback === true && isSelected) lines.push(indent(`${selectColor.bold("▶")} ${selectColor.bold(labelWithNum)} ${this.feedbackText}${selectColor("█")}`));
99683
+ if (this.feedbackMode && option.requires_feedback === true && isSelected) lines.push(indent(this.renderInlineFeedbackLine(width - 2, labelWithNum)));
99205
99684
  else if (isSelected) lines.push(indent(`${selectColor.bold("▶")} ${selectColor.bold(labelWithNum)}`));
99206
99685
  else lines.push(indent(chalk.gray(` ${labelWithNum}`)));
99207
99686
  }
99208
99687
  lines.push("");
99209
- if (this.feedbackMode) lines.push(indent(chalk.dim("Type feedback, then press Enter to submit.")));
99688
+ if (this.feedbackMode) lines.push(indent(chalk.dim("Type feedback · submit.")));
99210
99689
  else {
99211
99690
  const expandHint = hasDiff ? ` · ctrl+o ${this.expanded ? "collapse" : "expand"}` : "";
99212
99691
  lines.push(indent(chalk.dim(`↑/↓ select · ${buildNumericHint(data.choices.length)} choose · ↵ confirm${expandHint}`)));
@@ -99228,6 +99707,16 @@ var ApprovalPanelComponent = class extends Container {
99228
99707
  }
99229
99708
  if (this.selectedIndex < 0 || this.selectedIndex >= count) this.selectedIndex = Math.max(0, Math.min(this.selectedIndex, count - 1));
99230
99709
  }
99710
+ renderInlineFeedbackLine(width, labelWithNum) {
99711
+ const prefix = `${selectColor.bold("▶")} ${selectColor.bold(labelWithNum)} `;
99712
+ const inputWidth = Math.max(4, width - visibleWidth(prefix) + 2);
99713
+ const inputLine = this.feedbackInput.render(inputWidth)[0] ?? "> ";
99714
+ return prefix + (inputLine.startsWith("> ") ? inputLine.slice(2) : inputLine);
99715
+ }
99716
+ invalidate() {
99717
+ super.invalidate();
99718
+ this.feedbackInput.invalidate();
99719
+ }
99231
99720
  };
99232
99721
  function buildNumericHint(count) {
99233
99722
  if (count <= 0) return "↵";
@@ -99314,6 +99803,7 @@ var QuestionDialogComponent = class extends Container {
99314
99803
  colors;
99315
99804
  onAnswer;
99316
99805
  maxVisibleOptions;
99806
+ otherInput = new Input();
99317
99807
  currentTab = 0;
99318
99808
  submitActionIdx = 0;
99319
99809
  editingOther = false;
@@ -99336,6 +99826,9 @@ var QuestionDialogComponent = class extends Container {
99336
99826
  this.onAnswer = onAnswer;
99337
99827
  this.colors = colors;
99338
99828
  this.maxVisibleOptions = maxVisibleOptions;
99829
+ this.otherInput.onSubmit = (value) => {
99830
+ this.commitOtherInput(value);
99831
+ };
99339
99832
  const total = request.data.questions.length;
99340
99833
  this.cursors = Array.from({ length: total }, () => 0);
99341
99834
  this.singleSelections = Array.from({ length: total }, () => void 0);
@@ -99349,6 +99842,10 @@ var QuestionDialogComponent = class extends Container {
99349
99842
  this.onAnswer([]);
99350
99843
  return;
99351
99844
  }
99845
+ if (matchesKey(data, Key.ctrl("c")) || matchesKey(data, Key.ctrl("d"))) {
99846
+ this.onAnswer([]);
99847
+ return;
99848
+ }
99352
99849
  if (this.isEditingOther()) {
99353
99850
  this.handleOtherInput(data);
99354
99851
  return;
@@ -99390,58 +99887,32 @@ var QuestionDialogComponent = class extends Container {
99390
99887
  this.activateQuestionOption(numIdx);
99391
99888
  return;
99392
99889
  }
99393
- if (printable === " " || matchesKey(data, Key.space)) {
99394
- if (question.multi_select) this.activateQuestionOption(this.currentCursor());
99395
- return;
99396
- }
99890
+ if ((printable === " " || matchesKey(data, Key.space)) && question.multi_select) this.activateQuestionOption(this.currentCursor());
99397
99891
  }
99398
99892
  handleOtherInput(data) {
99399
99893
  const questionIdx = this.currentQuestionIndex();
99400
99894
  if (questionIdx === void 0) return;
99401
- if (matchesKey(data, Key.left)) {
99402
- this.editingOther = false;
99403
- this.gotoTab(this.currentTab - 1);
99404
- return;
99405
- }
99406
- if (matchesKey(data, Key.right) || matchesKey(data, Key.tab)) {
99895
+ if (matchesKey(data, Key.tab)) {
99896
+ this.syncOtherDraft(questionIdx);
99407
99897
  this.editingOther = false;
99408
99898
  this.gotoTab(this.currentTab + 1);
99409
99899
  return;
99410
99900
  }
99411
99901
  if (matchesKey(data, Key.up)) {
99902
+ this.syncOtherDraft(questionIdx);
99412
99903
  this.editingOther = false;
99413
99904
  this.moveQuestionCursor(-1);
99414
99905
  return;
99415
99906
  }
99416
99907
  if (matchesKey(data, Key.down)) {
99908
+ this.syncOtherDraft(questionIdx);
99417
99909
  this.editingOther = false;
99418
99910
  this.moveQuestionCursor(1);
99419
99911
  return;
99420
99912
  }
99421
- if (matchesKey(data, Key.enter)) {
99422
- this.commitOtherInput();
99423
- return;
99424
- }
99425
- if (matchesKey(data, Key.backspace)) {
99426
- this.otherDrafts[questionIdx] = this.otherDrafts[questionIdx]?.slice(0, -1) ?? "";
99427
- this.reviewMessage = void 0;
99428
- return;
99429
- }
99430
- const printable = decodeKittyPrintable(data);
99431
- if (printable !== void 0) {
99432
- this.otherDrafts[questionIdx] = (this.otherDrafts[questionIdx] ?? "") + printable;
99433
- this.reviewMessage = void 0;
99434
- return;
99435
- }
99436
- if (data === " " || matchesKey(data, Key.space)) {
99437
- this.otherDrafts[questionIdx] = (this.otherDrafts[questionIdx] ?? "") + " ";
99438
- this.reviewMessage = void 0;
99439
- return;
99440
- }
99441
- if (data.length > 0 && !data.startsWith("\x1B")) {
99442
- this.otherDrafts[questionIdx] = (this.otherDrafts[questionIdx] ?? "") + data;
99443
- this.reviewMessage = void 0;
99444
- }
99913
+ this.otherInput.handleInput(data);
99914
+ this.syncOtherDraft(questionIdx);
99915
+ this.reviewMessage = void 0;
99445
99916
  }
99446
99917
  handleSubmitInput(data) {
99447
99918
  if (matchesKey(data, Key.up)) {
@@ -99475,7 +99946,6 @@ var QuestionDialogComponent = class extends Container {
99475
99946
  if (printable === "2") {
99476
99947
  this.submitActionIdx = 1;
99477
99948
  this.executeSubmitAction(1);
99478
- return;
99479
99949
  }
99480
99950
  }
99481
99951
  gotoTab(target) {
@@ -99524,15 +99994,17 @@ var QuestionDialogComponent = class extends Container {
99524
99994
  enterOtherInput(questionIdx) {
99525
99995
  this.cursors[questionIdx] = this.otherOptionIndex(questionIdx);
99526
99996
  this.editingOther = true;
99997
+ this.otherInput.setValue(this.otherDraftValue(questionIdx));
99527
99998
  this.reviewMessage = void 0;
99528
99999
  }
99529
- commitOtherInput() {
100000
+ commitOtherInput(rawValue) {
99530
100001
  const questionIdx = this.currentQuestionIndex();
99531
100002
  if (questionIdx === void 0) return;
99532
100003
  const question = this.request.data.questions[questionIdx];
99533
100004
  if (question === void 0) return;
99534
- const value = this.otherDrafts[questionIdx]?.trim() ?? "";
100005
+ const value = (rawValue ?? this.otherInput.getValue()).trim();
99535
100006
  if (value.length === 0) return;
100007
+ this.otherInput.setValue(value);
99536
100008
  this.otherDrafts[questionIdx] = value;
99537
100009
  this.committedOtherValues[questionIdx] = value;
99538
100010
  if (question.multi_select) this.multiSelections[questionIdx]?.add(this.otherOptionIndex(questionIdx));
@@ -99599,6 +100071,7 @@ var QuestionDialogComponent = class extends Container {
99599
100071
  this.onAnswer(out);
99600
100072
  }
99601
100073
  render(width) {
100074
+ this.otherInput.focused = this.focused && this.isEditingOther();
99602
100075
  return this.isSubmitTab() ? this.renderSubmitTab(width) : this.renderQuestionTab(width);
99603
100076
  }
99604
100077
  renderQuestionTab(width) {
@@ -99611,10 +100084,11 @@ var QuestionDialogComponent = class extends Container {
99611
100084
  const dim = chalk.hex(colors.textDim);
99612
100085
  const success = chalk.hex(colors.success);
99613
100086
  const renderWidth = Math.max(1, width);
99614
- const lines = [];
99615
- lines.push(accent("─".repeat(renderWidth)));
99616
- lines.push(accent.bold(" question"));
99617
- lines.push("");
100087
+ const lines = [
100088
+ accent("─".repeat(renderWidth)),
100089
+ accent.bold(" question"),
100090
+ ""
100091
+ ];
99618
100092
  this.pushTabs(lines);
99619
100093
  lines.push("");
99620
100094
  lines.push(accent(` ? ${question.question}`));
@@ -99635,10 +100109,15 @@ var QuestionDialogComponent = class extends Container {
99635
100109
  const singleSelection = this.singleSelections[questionIdx];
99636
100110
  for (let i = visibleStart; i < visibleEnd; i++) {
99637
100111
  const option = options[i];
100112
+ if (option === void 0) continue;
99638
100113
  const num = i + 1;
99639
100114
  const isCursor = i === cursor;
99640
100115
  const isOther = option.kind === "other";
99641
100116
  const isSelected = question.multi_select ? multiSet.has(i) : singleSelection === i;
100117
+ if (this.isEditingOther() && isCursor && isOther) {
100118
+ lines.push(this.renderEditingOtherLine(renderWidth, questionIdx, option, num, isSelected));
100119
+ continue;
100120
+ }
99642
100121
  const label = this.renderOptionLabel(questionIdx, option, isCursor);
99643
100122
  let line;
99644
100123
  if (question.multi_select) {
@@ -99666,10 +100145,11 @@ var QuestionDialogComponent = class extends Container {
99666
100145
  const text = chalk.hex(colors.text);
99667
100146
  const warning = chalk.hex(colors.warning);
99668
100147
  const renderWidth = Math.max(1, width);
99669
- const lines = [];
99670
- lines.push(accent("─".repeat(renderWidth)));
99671
- lines.push(accent.bold(" question"));
99672
- lines.push("");
100148
+ const lines = [
100149
+ accent("─".repeat(renderWidth)),
100150
+ accent.bold(" question"),
100151
+ ""
100152
+ ];
99673
100153
  this.pushTabs(lines);
99674
100154
  lines.push("");
99675
100155
  lines.push(text.bold(` ${REVIEW_TITLE}`));
@@ -99678,6 +100158,7 @@ var QuestionDialogComponent = class extends Container {
99678
100158
  lines.push("");
99679
100159
  for (let i = 0; i < this.request.data.questions.length; i++) {
99680
100160
  const question = this.request.data.questions[i];
100161
+ if (question === void 0) continue;
99681
100162
  const answer = this.answers[i];
99682
100163
  lines.push(` ${dim("Q")} ${question.question}`);
99683
100164
  if (answer !== void 0 && answer.length > 0) lines.push(` ${accent("→")} ${text(answer)}`);
@@ -99688,6 +100169,7 @@ var QuestionDialogComponent = class extends Container {
99688
100169
  lines.push("");
99689
100170
  for (let i = 0; i < SUBMIT_ACTIONS.length; i++) {
99690
100171
  const label = SUBMIT_ACTIONS[i];
100172
+ if (label === void 0) continue;
99691
100173
  const num = i + 1;
99692
100174
  if (i === this.submitActionIdx) lines.push(accent(` → [${String(num)}] ${label}`));
99693
100175
  else lines.push(dim(` [${String(num)}] ${label}`));
@@ -99703,6 +100185,7 @@ var QuestionDialogComponent = class extends Container {
99703
100185
  const tabs = [];
99704
100186
  for (let i = 0; i < this.request.data.questions.length; i++) {
99705
100187
  const question = this.request.data.questions[i];
100188
+ if (question === void 0) continue;
99706
100189
  const label = question.header !== void 0 && question.header.length > 0 ? question.header : `Q${String(i + 1)}`;
99707
100190
  if (i === this.currentTab) tabs.push(active(` ${label} `));
99708
100191
  else if (this.isAnswered(i)) tabs.push(chalk.green(`(✓) ${label}`));
@@ -99714,14 +100197,17 @@ var QuestionDialogComponent = class extends Container {
99714
100197
  lines.push(` ${tabs.join(" ")}`);
99715
100198
  }
99716
100199
  buildQuestionHint(dim, questionIdx) {
99717
- if (this.isEditingOther()) {
99718
- const parts = ["type answer", "↵ save"];
99719
- if (this.totalTabs() > 1) parts.push("←/→/tab switch");
99720
- parts.push("esc dismiss");
99721
- return dim(` ${parts.join(" ")}`);
99722
- }
100200
+ if (this.isEditingOther()) return dim(` ${[
100201
+ "type answer",
100202
+ " save",
100203
+ ...this.totalTabs() > 1 ? ["tab switch"] : [],
100204
+ "esc dismiss"
100205
+ ].join(" ")}`);
99723
100206
  const optionCount = Math.min(this.displayOptions(questionIdx).length, NUMBER_KEYS.length);
99724
- const parts = ["▲/▼ select", `${optionCount <= 1 ? "1" : `1-${String(optionCount)}`} / ↵ ${this.request.data.questions[questionIdx].multi_select ? "toggle" : "choose"}`];
100207
+ const numberHint = optionCount <= 1 ? "1" : `1-${String(optionCount)}`;
100208
+ const question = this.request.data.questions[questionIdx];
100209
+ if (question === void 0) return dim(" esc dismiss");
100210
+ const parts = ["▲/▼ select", `${numberHint} / ↵ ${question.multi_select ? "toggle" : "choose"}`];
99725
100211
  if (this.totalTabs() > 1) parts.push("←/→/tab switch");
99726
100212
  parts.push("esc dismiss");
99727
100213
  return dim(` ${parts.join(" ")}`);
@@ -99783,11 +100269,33 @@ var QuestionDialogComponent = class extends Container {
99783
100269
  }
99784
100270
  renderOptionLabel(questionIdx, option, isCursor) {
99785
100271
  if (option.kind !== "other") return option.label;
99786
- const value = this.otherDrafts[questionIdx] || this.committedOtherValues[questionIdx];
100272
+ const value = this.otherDraftValue(questionIdx);
99787
100273
  if (this.isEditingOther() && isCursor) return `${option.label}: ${value ?? ""}█`;
99788
100274
  if (value !== void 0 && value.length > 0) return `${option.label}: ${value}`;
99789
100275
  return option.label;
99790
100276
  }
100277
+ renderEditingOtherLine(width, questionIdx, option, num, isSelected) {
100278
+ const question = this.request.data.questions[questionIdx];
100279
+ if (question === void 0) return option.label;
100280
+ let prefix;
100281
+ if (question.multi_select) {
100282
+ const body = ` [${isSelected ? "✓" : " "}] ${option.label}: `;
100283
+ prefix = isSelected ? chalk.hex(this.colors.success).bold(body) : chalk.hex(this.colors.primary)(body);
100284
+ } else {
100285
+ const body = ` → [${String(num)}] ${option.label}: `;
100286
+ prefix = isSelected && this.isAnswered(questionIdx) ? chalk.hex(this.colors.success).bold(body) : chalk.hex(this.colors.primary)(body);
100287
+ }
100288
+ const inputWidth = Math.max(4, width - visibleWidth(prefix) + 2);
100289
+ const inputLine = this.otherInput.render(inputWidth)[0] ?? "> ";
100290
+ const inlineInput = inputLine.startsWith("> ") ? inputLine.slice(2) : inputLine;
100291
+ return prefix + inlineInput;
100292
+ }
100293
+ otherDraftValue(questionIdx) {
100294
+ return this.otherDrafts[questionIdx] || this.committedOtherValues[questionIdx] || "";
100295
+ }
100296
+ syncOtherDraft(questionIdx) {
100297
+ this.otherDrafts[questionIdx] = this.otherInput.getValue();
100298
+ }
99791
100299
  isAnswered(questionIdx) {
99792
100300
  const answer = this.answers[questionIdx];
99793
100301
  return answer !== void 0 && answer.length > 0;
@@ -99796,6 +100304,10 @@ var QuestionDialogComponent = class extends Container {
99796
100304
  for (let i = 0; i < this.request.data.questions.length; i++) if (!this.isAnswered(i)) return true;
99797
100305
  return false;
99798
100306
  }
100307
+ invalidate() {
100308
+ super.invalidate();
100309
+ this.otherInput.invalidate();
100310
+ }
99799
100311
  };
99800
100312
  //#endregion
99801
100313
  //#region src/tui/reverse-rpc/question/ui.ts