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

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 +1203 -612
  2. package/package.json +2 -2
package/dist/main.mjs CHANGED
@@ -1026,14 +1026,7 @@ var BaseContextState = class {
1026
1026
  this.journalWriter = opts.journalWriter;
1027
1027
  this.projector = opts.projector ?? new DefaultConversationProjector();
1028
1028
  this.currentTurnId = opts.currentTurnId;
1029
- this.memory = new ContextStateMemory({
1030
- initialModel: opts.initialModel,
1031
- ...opts.initialSystemPrompt !== void 0 ? { initialSystemPrompt: opts.initialSystemPrompt } : {},
1032
- ...opts.initialActiveTools !== void 0 ? { initialActiveTools: opts.initialActiveTools } : {},
1033
- initialHistory: opts.initialHistory,
1034
- ...opts.initialTokenCount !== void 0 ? { initialTokenCount: opts.initialTokenCount } : {},
1035
- ...opts.initialThinkingLevel !== void 0 ? { initialThinkingLevel: opts.initialThinkingLevel } : {}
1036
- });
1029
+ this.memory = new ContextStateMemory(opts);
1037
1030
  }
1038
1031
  get model() {
1039
1032
  return this.memory.model;
@@ -1061,8 +1054,12 @@ var BaseContextState = class {
1061
1054
  activeTools: this.memory.activeTools
1062
1055
  });
1063
1056
  }
1064
- drainSteerMessages() {
1065
- return this.memory.drainSteerMessages();
1057
+ async applySteerMessages() {
1058
+ const steers = this.memory.drainSteerMessages();
1059
+ if (steers.length === 0) return false;
1060
+ this.assertNotBroken();
1061
+ for (const steer of steers) await this.appendUserMessage(steer);
1062
+ return true;
1066
1063
  }
1067
1064
  pushSteer(input) {
1068
1065
  this.memory.pushSteer(input);
@@ -1109,10 +1106,6 @@ var BaseContextState = class {
1109
1106
  await this.journalWriter.append(buildToolCallRecord(input, data));
1110
1107
  this.memory.appendOpenStepToolCall(input.stepUuid, toolCallDataToKosongToolCall(input.data));
1111
1108
  }
1112
- async addUserMessages(steers) {
1113
- this.assertNotBroken();
1114
- for (const steer of steers) await this.appendUserMessage(steer);
1115
- }
1116
1109
  async appendNotification(data) {
1117
1110
  this.assertNotBroken();
1118
1111
  await this.journalWriter.append(buildNotificationRecord(data));
@@ -1160,32 +1153,13 @@ var BaseContextState = class {
1160
1153
  this.memory.resetToSummary(summary);
1161
1154
  }
1162
1155
  };
1163
- var WiredContextState = class extends BaseContextState {
1164
- constructor(opts) {
1165
- super({
1166
- journalWriter: opts.journalWriter,
1167
- initialModel: opts.initialModel,
1168
- ...opts.initialSystemPrompt !== void 0 ? { initialSystemPrompt: opts.initialSystemPrompt } : {},
1169
- ...opts.initialActiveTools !== void 0 ? { initialActiveTools: opts.initialActiveTools } : {},
1170
- currentTurnId: opts.currentTurnId,
1171
- projector: opts.projector,
1172
- initialHistory: opts.initialHistory,
1173
- ...opts.initialTokenCount !== void 0 ? { initialTokenCount: opts.initialTokenCount } : {},
1174
- ...opts.initialThinkingLevel !== void 0 ? { initialThinkingLevel: opts.initialThinkingLevel } : {}
1175
- });
1176
- }
1177
- };
1156
+ var WiredContextState = class extends BaseContextState {};
1178
1157
  var InMemoryContextState = class extends BaseContextState {
1179
1158
  constructor(opts) {
1180
1159
  super({
1181
- journalWriter: new NoopJournalWriter(),
1182
- initialModel: opts.initialModel,
1183
- ...opts.initialSystemPrompt !== void 0 ? { initialSystemPrompt: opts.initialSystemPrompt } : {},
1184
- ...opts.initialActiveTools !== void 0 ? { initialActiveTools: opts.initialActiveTools } : {},
1160
+ ...opts,
1185
1161
  currentTurnId: opts.currentTurnId ?? (() => "embedded"),
1186
- projector: opts.projector,
1187
- initialHistory: opts.initialHistory,
1188
- ...opts.initialThinkingLevel !== void 0 ? { initialThinkingLevel: opts.initialThinkingLevel } : {}
1162
+ journalWriter: new NoopJournalWriter()
1189
1163
  });
1190
1164
  }
1191
1165
  };
@@ -2226,6 +2200,7 @@ function zodToSchema(schema) {
2226
2200
  }
2227
2201
  }
2228
2202
  function buildLLMVisibleTools(tools, activeTools) {
2203
+ if (tools === void 0) return [];
2229
2204
  return (activeTools === void 0 ? tools : tools.filter((t) => activeTools.includes(t.name))).map((t) => ({
2230
2205
  name: t.name,
2231
2206
  description: t.description,
@@ -2558,7 +2533,7 @@ async function executePendingCalls(step, pending, deferred) {
2558
2533
  for (const callback of postContentCallbacks) await callback();
2559
2534
  }
2560
2535
  function findTool(tools, name) {
2561
- return tools.find((tool) => tool.name === name);
2536
+ return tools?.find((tool) => tool.name === name);
2562
2537
  }
2563
2538
  function emitToolResultEvent(emit, toolCallId, output, isError) {
2564
2539
  emit({
@@ -2640,10 +2615,7 @@ async function executeSoulStep(deps) {
2640
2615
  type: "step.begin",
2641
2616
  step: currentStep
2642
2617
  });
2643
- const drained = context.drainSteerMessages();
2644
- if (drained.length > 0) await context.addUserMessages(drained);
2645
2618
  signal.throwIfAborted();
2646
- context.beforeStep?.();
2647
2619
  const model = overrides?.model ?? context.model;
2648
2620
  const visibleTools = buildLLMVisibleTools(config.tools, overrides?.activeTools);
2649
2621
  const messages = context.buildMessages();
@@ -2763,7 +2735,7 @@ async function runSoulTurn(config, context, runtime, sink, signal, overrides) {
2763
2735
  });
2764
2736
  if (stepResult.stopReason === "tool_use") continue;
2765
2737
  stopReason = stepResult.stopReason;
2766
- break;
2738
+ if (!(await config.beforeTurnCompletes?.())?.continue) break;
2767
2739
  }
2768
2740
  } catch (error) {
2769
2741
  if (isAbortError$3(error) || signal.aborted) {
@@ -3253,6 +3225,25 @@ var SoulRegistry = class {
3253
3225
  }
3254
3226
  };
3255
3227
  //#endregion
3228
+ //#region ../../packages/kimi-core/src/soul/types.ts
3229
+ function mergeSoulConfig(target, source) {
3230
+ if (source.tools) target.tools = target.tools ? [...target.tools, ...source.tools] : source.tools;
3231
+ if (source.maxSteps !== void 0) target.maxSteps = source.maxSteps;
3232
+ if (source.beforeToolCall) target.beforeToolCall = mergeCallback(target.beforeToolCall, source.beforeToolCall);
3233
+ if (source.afterToolCall) target.afterToolCall = mergeCallback(target.afterToolCall, source.afterToolCall);
3234
+ if (source.beforeStep) target.beforeStep = mergeCallback(target.beforeStep, source.beforeStep);
3235
+ if (source.afterStep) target.afterStep = mergeCallback(target.afterStep, source.afterStep);
3236
+ if (source.compactionConfig !== void 0) target.compactionConfig = source.compactionConfig;
3237
+ if (source.contextWindow !== void 0) target.contextWindow = source.contextWindow;
3238
+ function mergeCallback(f1, f2) {
3239
+ if (!f1) return f2;
3240
+ if (!f2) return f1;
3241
+ return (async (...args) => {
3242
+ return await f1(...args) ?? await f2(...args);
3243
+ });
3244
+ }
3245
+ }
3246
+ //#endregion
3256
3247
  //#region ../../packages/kimi-core/src/soul-plus/subagent/git-context.ts
3257
3248
  /**
3258
3249
  * Git context collection for explore subagents.
@@ -3476,7 +3467,7 @@ async function runSubagentTurn(deps, agentId, request, signal) {
3476
3467
  agent_id: agentId,
3477
3468
  agent_name: request.agentName,
3478
3469
  parent_tool_call_id: request.parentToolCallId,
3479
- ...request.parentToolCallUuid !== void 0 ? { parent_tool_call_uuid: request.parentToolCallUuid } : {},
3470
+ parent_tool_call_uuid: request.parentToolCallUuid,
3480
3471
  ...request.parentAgentId !== void 0 && request.parentAgentId !== "" && request.parentAgentId !== "agent_main" ? { parent_agent_id: request.parentAgentId } : {},
3481
3472
  run_in_background: request.runInBackground ?? false
3482
3473
  }
@@ -3782,9 +3773,9 @@ var ContextStateToolTranscriptRecorder = class {
3782
3773
  tool_call_id: input.toolCall.id,
3783
3774
  tool_name: input.toolCall.name,
3784
3775
  args: input.args,
3785
- ...input.display?.activityDescription !== void 0 ? { activity_description: input.display.activityDescription } : {},
3786
- ...input.display?.userFacingName !== void 0 ? { user_facing_name: input.display.userFacingName } : {},
3787
- ...input.display?.inputDisplay !== void 0 ? { input_display: input.display.inputDisplay } : {}
3776
+ activity_description: input.display?.activityDescription,
3777
+ user_facing_name: input.display?.userFacingName,
3778
+ input_display: input.display?.inputDisplay
3788
3779
  }
3789
3780
  });
3790
3781
  return { wireUuid };
@@ -4455,7 +4446,7 @@ var DefaultToolCallUseCase = class {
4455
4446
  approvalRuntime: this.deps.policy.approvalRuntime,
4456
4447
  approvalSource,
4457
4448
  turnId: context.turnId,
4458
- ...context.approvalTimeoutMs !== void 0 ? { approvalTimeoutMs: context.approvalTimeoutMs } : {}
4449
+ approvalTimeoutMs: context.approvalTimeoutMs
4459
4450
  });
4460
4451
  return async (btcCtx, signal) => {
4461
4452
  const hookResult = await this.deps.policy.hookEngine.executeHooks("PreToolUse", {
@@ -4543,7 +4534,7 @@ function approvalSourceForActor(context) {
4543
4534
  if (context.actor.kind === "subagent") return {
4544
4535
  kind: "subagent",
4545
4536
  agent_id: context.actor.agentId,
4546
- ...context.actor.subagentType !== void 0 ? { subagent_type: context.actor.subagentType } : {}
4537
+ subagent_type: context.actor.subagentType
4547
4538
  };
4548
4539
  return {
4549
4540
  kind: "soul",
@@ -5617,7 +5608,7 @@ async function requestEnterPlanModeApproval(approvalRuntime, turnState, toolCall
5617
5608
  detail: reason
5618
5609
  },
5619
5610
  source: approvalSourceForPlanMode(turnState),
5620
- ...turnState.getCurrentTurnId() !== void 0 ? { turnId: turnState.getCurrentTurnId() } : {}
5611
+ turnId: turnState.getCurrentTurnId()
5621
5612
  }, signal);
5622
5613
  return {
5623
5614
  approved: response.approved,
@@ -7440,6 +7431,15 @@ function mergeInPlace(target, source) {
7440
7431
  }
7441
7432
  return false;
7442
7433
  }
7434
+ /**
7435
+ * Extract the concatenated text from a message's content parts.
7436
+ *
7437
+ * @param message The message to extract text from.
7438
+ * @param sep Separator between text parts. Defaults to empty string.
7439
+ */
7440
+ function extractText(message, sep = "") {
7441
+ return message.content.filter((part) => part.type === "text").map((part) => part.text).join(sep);
7442
+ }
7443
7443
  //#endregion
7444
7444
  //#region ../../packages/kosong/src/capability.ts
7445
7445
  /**
@@ -7447,7 +7447,7 @@ function mergeInPlace(target, source) {
7447
7447
  * given model. Frozen so accidental mutation at one call site cannot leak
7448
7448
  * into another.
7449
7449
  */
7450
- const UNKNOWN_CAPABILITY$1 = Object.freeze({
7450
+ const UNKNOWN_CAPABILITY = Object.freeze({
7451
7451
  image_in: false,
7452
7452
  video_in: false,
7453
7453
  audio_in: false,
@@ -7460,16 +7460,47 @@ const UNKNOWN_CAPABILITY$1 = Object.freeze({
7460
7460
  /**
7461
7461
  * Base error for all chat provider errors.
7462
7462
  */
7463
- var ChatProviderError$1 = class extends Error {
7463
+ var ChatProviderError = class extends Error {
7464
7464
  constructor(message) {
7465
7465
  super(message);
7466
7466
  this.name = "ChatProviderError";
7467
7467
  }
7468
7468
  };
7469
7469
  /**
7470
+ * Network-level connection failure.
7471
+ */
7472
+ var APIConnectionError$3 = class extends ChatProviderError {
7473
+ constructor(message) {
7474
+ super(message);
7475
+ this.name = "APIConnectionError";
7476
+ }
7477
+ };
7478
+ /**
7479
+ * Request timed out.
7480
+ */
7481
+ var APITimeoutError = class extends ChatProviderError {
7482
+ constructor(message) {
7483
+ super(message);
7484
+ this.name = "APITimeoutError";
7485
+ }
7486
+ };
7487
+ /**
7488
+ * HTTP status error from the API.
7489
+ */
7490
+ var APIStatusError = class extends ChatProviderError {
7491
+ statusCode;
7492
+ requestId;
7493
+ constructor(statusCode, message, requestId) {
7494
+ super(message);
7495
+ this.name = "APIStatusError";
7496
+ this.statusCode = statusCode;
7497
+ this.requestId = requestId ?? null;
7498
+ }
7499
+ };
7500
+ /**
7470
7501
  * The API returned an empty response (no content, no tool calls).
7471
7502
  */
7472
- var APIEmptyResponseError = class extends ChatProviderError$1 {
7503
+ var APIEmptyResponseError = class extends ChatProviderError {
7473
7504
  constructor(message) {
7474
7505
  super(message);
7475
7506
  this.name = "APIEmptyResponseError";
@@ -13462,7 +13493,7 @@ var APIError$2 = class APIError$2 extends OpenAIError {
13462
13493
  return "(no status code or body)";
13463
13494
  }
13464
13495
  static generate(status, errorResponse, message, headers) {
13465
- if (!status || !headers) return new APIConnectionError$3({
13496
+ if (!status || !headers) return new APIConnectionError$2({
13466
13497
  message,
13467
13498
  cause: castToError$2(errorResponse)
13468
13499
  });
@@ -13483,13 +13514,13 @@ var APIUserAbortError$2 = class extends APIError$2 {
13483
13514
  super(void 0, void 0, message || "Request was aborted.", void 0);
13484
13515
  }
13485
13516
  };
13486
- var APIConnectionError$3 = class extends APIError$2 {
13517
+ var APIConnectionError$2 = class extends APIError$2 {
13487
13518
  constructor({ message, cause }) {
13488
13519
  super(void 0, void 0, message || "Connection error.", void 0);
13489
13520
  if (cause) this.cause = cause;
13490
13521
  }
13491
13522
  };
13492
- var APIConnectionTimeoutError$2 = class extends APIConnectionError$3 {
13523
+ var APIConnectionTimeoutError$2 = class extends APIConnectionError$2 {
13493
13524
  constructor({ message } = {}) {
13494
13525
  super({ message: message ?? "Request timed out." });
13495
13526
  }
@@ -19915,7 +19946,7 @@ var OpenAI = class {
19915
19946
  }));
19916
19947
  if (response instanceof OAuthError$1 || response instanceof SubjectTokenProviderError) throw response;
19917
19948
  if (isTimeout) throw new APIConnectionTimeoutError$2();
19918
- throw new APIConnectionError$3({ cause: response });
19949
+ throw new APIConnectionError$2({ cause: response });
19919
19950
  }
19920
19951
  const responseInfo = `[${requestLogID}${retryLogStr}${[...response.headers.entries()].filter(([name]) => name === "x-request-id").map(([name, value]) => ", " + name + ": " + JSON.stringify(value)).join("")}] ${req.method} ${url} ${response.ok ? "succeeded" : "failed"} with status ${response.status} in ${headersTime - startTime}ms`;
19921
19952
  if (!response.ok) {
@@ -20155,7 +20186,7 @@ OpenAI.OpenAI = _a$3;
20155
20186
  OpenAI.DEFAULT_TIMEOUT = 6e5;
20156
20187
  OpenAI.OpenAIError = OpenAIError;
20157
20188
  OpenAI.APIError = APIError$2;
20158
- OpenAI.APIConnectionError = APIConnectionError$3;
20189
+ OpenAI.APIConnectionError = APIConnectionError$2;
20159
20190
  OpenAI.APIConnectionTimeoutError = APIConnectionTimeoutError$2;
20160
20191
  OpenAI.APIUserAbortError = APIUserAbortError$2;
20161
20192
  OpenAI.NotFoundError = NotFoundError$2;
@@ -20191,6 +20222,187 @@ OpenAI.Containers = Containers;
20191
20222
  OpenAI.Skills = Skills$1;
20192
20223
  OpenAI.Videos = Videos;
20193
20224
  //#endregion
20225
+ //#region ../../packages/kosong/src/providers/openai-common.ts
20226
+ /**
20227
+ * Convert a kosong `ContentPart` to OpenAI-compatible content part.
20228
+ * Returns `null` for think parts (handled separately as reasoning_content).
20229
+ */
20230
+ function convertContentPart(part) {
20231
+ switch (part.type) {
20232
+ case "text": return {
20233
+ type: "text",
20234
+ text: part.text
20235
+ };
20236
+ case "think": return null;
20237
+ case "image_url": return {
20238
+ type: "image_url",
20239
+ image_url: part.imageUrl.id === void 0 ? { url: part.imageUrl.url } : {
20240
+ url: part.imageUrl.url,
20241
+ id: part.imageUrl.id
20242
+ }
20243
+ };
20244
+ case "audio_url": return {
20245
+ type: "audio_url",
20246
+ audio_url: part.audioUrl.id === void 0 ? { url: part.audioUrl.url } : {
20247
+ url: part.audioUrl.url,
20248
+ id: part.audioUrl.id
20249
+ }
20250
+ };
20251
+ case "video_url": return {
20252
+ type: "video_url",
20253
+ video_url: part.videoUrl.id === void 0 ? { url: part.videoUrl.url } : {
20254
+ url: part.videoUrl.url,
20255
+ id: part.videoUrl.id
20256
+ }
20257
+ };
20258
+ default: throw new Error(`Unknown content part type: ${part.type}`);
20259
+ }
20260
+ }
20261
+ /**
20262
+ * Convert a kosong `Tool` to OpenAI tool format.
20263
+ */
20264
+ function toolToOpenAI(tool) {
20265
+ return {
20266
+ type: "function",
20267
+ function: {
20268
+ name: tool.name,
20269
+ description: tool.description,
20270
+ parameters: tool.parameters
20271
+ }
20272
+ };
20273
+ }
20274
+ const NETWORK_RE$1 = /network|connection|connect|disconnect/i;
20275
+ const TIMEOUT_RE$1 = /timed?\s*out|timeout|deadline/i;
20276
+ function classifyBaseApiError(message) {
20277
+ if (TIMEOUT_RE$1.test(message)) return new APITimeoutError(message);
20278
+ if (NETWORK_RE$1.test(message)) return new APIConnectionError$3(message);
20279
+ return new ChatProviderError(`Error: ${message}`);
20280
+ }
20281
+ /**
20282
+ * Convert an OpenAI SDK error (or raw Error) to a kosong `ChatProviderError`.
20283
+ */
20284
+ function convertOpenAIError(error) {
20285
+ if (error instanceof APIConnectionTimeoutError$2) return new APITimeoutError(error.message);
20286
+ if (error instanceof APIConnectionError$2) return new APIConnectionError$3(error.message);
20287
+ if (error instanceof APIError$2 && typeof error.status === "number") {
20288
+ const reqId = error.requestID ?? null;
20289
+ return new APIStatusError(error.status, error.message, reqId);
20290
+ }
20291
+ if (error instanceof APIError$2 && error.constructor === APIError$2 && error.error === void 0) return classifyBaseApiError(error.message);
20292
+ if (error instanceof OpenAIError) return new ChatProviderError(`Error: ${error.message}`);
20293
+ if (error instanceof Error) return new ChatProviderError(`Error: ${error.message}`);
20294
+ return new ChatProviderError(`Error: ${String(error)}`);
20295
+ }
20296
+ /**
20297
+ * Type guard: narrow a tool call union to the function-type variant.
20298
+ * Works with OpenAI SDK's `ChatCompletionMessageToolCall` as well as
20299
+ * any object carrying `{ type: string }`.
20300
+ */
20301
+ function isFunctionToolCall(tc) {
20302
+ return tc.type === "function";
20303
+ }
20304
+ /**
20305
+ * Map kosong `ThinkingEffort` to OpenAI `reasoning_effort` string.
20306
+ */
20307
+ function thinkingEffortToReasoningEffort(effort) {
20308
+ switch (effort) {
20309
+ case "off": return;
20310
+ case "low": return "low";
20311
+ case "medium": return "medium";
20312
+ case "high": return "high";
20313
+ default: throw new Error(`Unknown thinking effort: ${String(effort)}`);
20314
+ }
20315
+ }
20316
+ /**
20317
+ * Map OpenAI `reasoning_effort` string back to kosong `ThinkingEffort`.
20318
+ */
20319
+ function reasoningEffortToThinkingEffort(reasoning) {
20320
+ if (reasoning === void 0 || reasoning === null) return null;
20321
+ switch (reasoning) {
20322
+ case "low":
20323
+ case "minimal": return "low";
20324
+ case "medium": return "medium";
20325
+ case "high":
20326
+ case "xhigh": return "high";
20327
+ case "none": return "off";
20328
+ default: return "off";
20329
+ }
20330
+ }
20331
+ /**
20332
+ * Extract `TokenUsage` from an OpenAI-compatible usage object.
20333
+ */
20334
+ function extractUsage(usage) {
20335
+ if (usage === null || usage === void 0 || typeof usage !== "object") return null;
20336
+ const u = usage;
20337
+ const promptTokens = typeof u["prompt_tokens"] === "number" ? u["prompt_tokens"] : 0;
20338
+ const completionTokens = typeof u["completion_tokens"] === "number" ? u["completion_tokens"] : 0;
20339
+ let cached = 0;
20340
+ if (typeof u["cached_tokens"] === "number") cached = u["cached_tokens"];
20341
+ else if (typeof u["prompt_tokens_details"] === "object" && u["prompt_tokens_details"] !== null) {
20342
+ const details = u["prompt_tokens_details"];
20343
+ if (typeof details["cached_tokens"] === "number") cached = details["cached_tokens"];
20344
+ }
20345
+ return {
20346
+ inputOther: promptTokens - cached,
20347
+ output: completionTokens,
20348
+ inputCacheRead: cached,
20349
+ inputCacheCreation: 0
20350
+ };
20351
+ }
20352
+ /**
20353
+ * Normalize an OpenAI Chat Completions–style `finish_reason` string to the
20354
+ * unified {@link FinishReason} enum.
20355
+ *
20356
+ * Used by both the Kimi and OpenAI Legacy adapters because they share the
20357
+ * Chat Completions wire format. Returns `{ finishReason: null,
20358
+ * rawFinishReason: null }` when the upstream value is missing or `null` so
20359
+ * callers can treat "no signal" uniformly.
20360
+ *
20361
+ * Mapping:
20362
+ * - `'stop'` → `'completed'`
20363
+ * - `'tool_calls'` → `'tool_calls'`
20364
+ * - `'function_call'` → `'tool_calls'` (legacy alias)
20365
+ * - `'length'` → `'truncated'`
20366
+ * - `'content_filter'` → `'filtered'`
20367
+ * - any other non-null string → `'other'`
20368
+ */
20369
+ function normalizeOpenAIFinishReason(raw) {
20370
+ if (raw === null || raw === void 0) return {
20371
+ finishReason: null,
20372
+ rawFinishReason: null
20373
+ };
20374
+ switch (raw) {
20375
+ case "stop": return {
20376
+ finishReason: "completed",
20377
+ rawFinishReason: raw
20378
+ };
20379
+ case "tool_calls":
20380
+ case "function_call": return {
20381
+ finishReason: "tool_calls",
20382
+ rawFinishReason: raw
20383
+ };
20384
+ case "length": return {
20385
+ finishReason: "truncated",
20386
+ rawFinishReason: raw
20387
+ };
20388
+ case "content_filter": return {
20389
+ finishReason: "filtered",
20390
+ rawFinishReason: raw
20391
+ };
20392
+ default: return {
20393
+ finishReason: "other",
20394
+ rawFinishReason: raw
20395
+ };
20396
+ }
20397
+ }
20398
+ /**
20399
+ * Convert tool-role message content according to the chosen strategy.
20400
+ */
20401
+ function convertToolMessageContent(message, conversion) {
20402
+ if (conversion === "extract_text") return extractText(message);
20403
+ return message.content.map((p) => convertContentPart(p)).filter((p) => p !== null);
20404
+ }
20405
+ //#endregion
20194
20406
  //#region ../../packages/kimi-core/src/hooks/execution-pipeline.ts
20195
20407
  const ALLOW = Object.freeze({
20196
20408
  blockAction: false,
@@ -20560,7 +20772,7 @@ var TurnManager = class {
20560
20772
  if (!this.deps.lifecycleStateMachine.isIdle() || this.getCurrentTurnId() !== void 0) return { error: "agent_busy" };
20561
20773
  const input = req.data.input;
20562
20774
  const capability = ((this.runtimeSlot?.current())?.runtime ?? this.runtime).kosong.getCapability?.(this.deps.contextState.model);
20563
- if (capability !== void 0 && capability !== UNKNOWN_CAPABILITY$1) {
20775
+ if (capability !== void 0 && capability !== UNKNOWN_CAPABILITY) {
20564
20776
  let inputContainsImage = false;
20565
20777
  let inputContainsVideo = false;
20566
20778
  for (const part of input.parts ?? []) if (part.type === "image_url") inputContainsImage = true;
@@ -20699,7 +20911,15 @@ var TurnManager = class {
20699
20911
  kind: "soul",
20700
20912
  agent_id: this.agentId
20701
20913
  };
20702
- let soulConfig;
20914
+ const soulConfig = {
20915
+ beforeStep: async () => {
20916
+ await this.deps.contextState.applySteerMessages();
20917
+ this.deps.contextState.beforeStep?.();
20918
+ },
20919
+ beforeTurnCompletes: async () => {
20920
+ return { continue: await this.deps.contextState.applySteerMessages() };
20921
+ }
20922
+ };
20703
20923
  const scope = this.deps.toolExecutionScopeFactory?.create({
20704
20924
  sessionId: this.sessionId,
20705
20925
  agentId: this.agentId,
@@ -20711,27 +20931,20 @@ var TurnManager = class {
20711
20931
  const runtimeForTurn = scope?.wrapRuntime(baseRuntime) ?? baseRuntime;
20712
20932
  if (scope !== void 0) {
20713
20933
  this.activeToolExecutionScope = scope;
20714
- const scoped = scope.buildSoulConfig({
20934
+ mergeSoulConfig(soulConfig, scope.buildSoulConfig({
20715
20935
  tools: this.deps.tools,
20716
20936
  turnId,
20717
20937
  permissionRules: () => mergeTurnRules(this.sessionRules, turnOverrides),
20718
20938
  permissionMode: () => this.permissionMode,
20719
20939
  approvalSource
20720
- });
20721
- soulConfig = {
20722
- tools: [...scoped.tools],
20723
- beforeToolCall: scoped.beforeToolCall,
20724
- afterToolCall: scoped.afterToolCall
20725
- };
20726
- } else soulConfig = { tools: [...this.deps.tools] };
20940
+ }));
20941
+ } else mergeSoulConfig(soulConfig, { tools: this.deps.tools });
20727
20942
  const compactionConfig = this.runtimeSlot !== void 0 ? runtimeBundle?.compactionConfig : this.compactionConfig;
20728
- if (compactionConfig !== void 0) soulConfig = {
20729
- ...soulConfig,
20943
+ if (compactionConfig !== void 0) mergeSoulConfig(soulConfig, {
20730
20944
  compactionConfig,
20731
20945
  contextWindow: compactionConfig.maxContextSize
20732
- };
20733
- if (this.deps.hookEngine !== void 0) soulConfig = {
20734
- ...soulConfig,
20946
+ });
20947
+ if (this.deps.hookEngine !== void 0) mergeSoulConfig(soulConfig, {
20735
20948
  beforeStep: async (ctx, signal) => {
20736
20949
  const result = await this.executionHooks.run({
20737
20950
  event: "PreStep",
@@ -20755,7 +20968,7 @@ var TurnManager = class {
20755
20968
  stopReason: ctx.stopReason
20756
20969
  }, signal);
20757
20970
  }
20758
- };
20971
+ });
20759
20972
  const runPromise = this.runTurn(turnId, soulConfig, runtimeForTurn, controller.signal);
20760
20973
  runPromise.catch(() => {});
20761
20974
  this.deps.lifecycle.registerTurn(turnId, controller, runPromise);
@@ -24250,7 +24463,7 @@ var EditTool = class {
24250
24463
  };
24251
24464
  //#endregion
24252
24465
  //#region ../../packages/kimi-core/src/tools/builtin/shell/bash.ts
24253
- const DEFAULT_TIMEOUT_MS$1 = 6e4;
24466
+ const DEFAULT_TIMEOUT_MS$2 = 6e4;
24254
24467
  const MAX_TIMEOUT_MS = 300 * 1e3;
24255
24468
  const MAX_BACKGROUND_TIMEOUT_MS = 1440 * 60 * 1e3;
24256
24469
  const SIGTERM_GRACE_MS$1 = 5e3;
@@ -24264,7 +24477,7 @@ If \`run_in_background=true\`, the command will be started as a background task
24264
24477
 
24265
24478
  **Guidelines for safety and security:**
24266
24479
  - Each shell tool call will be executed in a fresh shell environment. The shell variables, current working directory changes, and the shell history is not preserved between calls.
24267
- - The tool call will return after the command is finished. You shall not use this tool to execute an interactive command or a command that may run forever. For possibly long-running commands, set the \`timeout\` argument in milliseconds. The default is ${String(DEFAULT_TIMEOUT_MS$1)}ms; foreground commands allow up to ${String(MAX_TIMEOUT_MS)}ms, and background commands allow up to ${String(MAX_BACKGROUND_TIMEOUT_MS)}ms.
24480
+ - The tool call will return after the command is finished. You shall not use this tool to execute an interactive command or a command that may run forever. For possibly long-running commands, set the \`timeout\` argument in milliseconds. The default is ${String(DEFAULT_TIMEOUT_MS$2)}ms; foreground commands allow up to ${String(MAX_TIMEOUT_MS)}ms, and background commands allow up to ${String(MAX_BACKGROUND_TIMEOUT_MS)}ms.
24268
24481
  - Avoid using \`..\` to access files or directories outside of the working directory.
24269
24482
  - Avoid modifying files outside of the working directory unless explicitly instructed to do so.
24270
24483
  - Never run commands that require superuser privileges unless explicitly instructed to do so.
@@ -24287,7 +24500,7 @@ If \`run_in_background=true\`, the command will be started as a background task
24287
24500
 
24288
24501
  **Guidelines for safety and security:**
24289
24502
  - Every tool call starts a fresh PowerShell session. Environment variables, \`cd\` changes, and command history do not persist between calls.
24290
- - Do not launch interactive programs or anything that is expected to block indefinitely; ensure each command finishes promptly. Provide a \`timeout\` argument in milliseconds for potentially long runs. The default is ${String(DEFAULT_TIMEOUT_MS$1)}ms; foreground commands allow up to ${String(MAX_TIMEOUT_MS)}ms, and background commands allow up to ${String(MAX_BACKGROUND_TIMEOUT_MS)}ms.
24503
+ - Do not launch interactive programs or anything that is expected to block indefinitely; ensure each command finishes promptly. Provide a \`timeout\` argument in milliseconds for potentially long runs. The default is ${String(DEFAULT_TIMEOUT_MS$2)}ms; foreground commands allow up to ${String(MAX_TIMEOUT_MS)}ms, and background commands allow up to ${String(MAX_BACKGROUND_TIMEOUT_MS)}ms.
24291
24504
  - Avoid using \`..\` to leave the working directory, and never touch files outside that directory unless explicitly instructed.
24292
24505
  - Never attempt commands that require elevated (Administrator) privileges unless explicitly authorized.
24293
24506
 
@@ -24394,7 +24607,7 @@ var ShellTool = class {
24394
24607
  };
24395
24608
  return this.executeInBackground(args);
24396
24609
  }
24397
- const timeoutMs = Math.min(args.timeout !== void 0 ? args.timeout : DEFAULT_TIMEOUT_MS$1, MAX_TIMEOUT_MS);
24610
+ const timeoutMs = Math.min(args.timeout !== void 0 ? args.timeout : DEFAULT_TIMEOUT_MS$2, MAX_TIMEOUT_MS);
24398
24611
  let proc;
24399
24612
  try {
24400
24613
  const effectiveCwd = args.cwd ?? this.cwd;
@@ -24497,7 +24710,7 @@ var ShellTool = class {
24497
24710
  isError: true,
24498
24711
  content: "description is required when run_in_background is true."
24499
24712
  };
24500
- const timeoutMs = Math.min(args.timeout !== void 0 ? args.timeout : DEFAULT_TIMEOUT_MS$1, MAX_BACKGROUND_TIMEOUT_MS);
24713
+ const timeoutMs = Math.min(args.timeout !== void 0 ? args.timeout : DEFAULT_TIMEOUT_MS$2, MAX_BACKGROUND_TIMEOUT_MS);
24501
24714
  let proc;
24502
24715
  try {
24503
24716
  const effectiveCwd = args.cwd ?? this.cwd;
@@ -28911,7 +29124,7 @@ Error: ${cause instanceof Error ? cause.message : typeof cause === "string" ? ca
28911
29124
  }
28912
29125
  //#endregion
28913
29126
  //#region ../../packages/kimi-core/src/tools/builtin/file/grep.ts
28914
- const DEFAULT_TIMEOUT_MS = 2e4;
29127
+ const DEFAULT_TIMEOUT_MS$1 = 2e4;
28915
29128
  const SIGTERM_GRACE_MS = 5e3;
28916
29129
  const MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
28917
29130
  const CONTENT_LINE_RE = /^(.*?)([:-])(\d+)\2/;
@@ -28997,7 +29210,7 @@ var GrepTool = class {
28997
29210
  const timeoutHandle = setTimeout(() => {
28998
29211
  timedOut = true;
28999
29212
  killProc();
29000
- }, DEFAULT_TIMEOUT_MS);
29213
+ }, DEFAULT_TIMEOUT_MS$1);
29001
29214
  let exitCode = 0;
29002
29215
  let stdoutText = "";
29003
29216
  let stderrText = "";
@@ -29050,7 +29263,7 @@ var GrepTool = class {
29050
29263
  messages.push(`Results truncated to ${String(headLimit ?? 0)} lines (total: ${String(total)}). Use offset=${String(nextOffset)} to see more.`);
29051
29264
  }
29052
29265
  if (bufferTruncated) messages.push(`[stdout truncated at ${String(MAX_OUTPUT_BYTES)} bytes]`);
29053
- if (timedOut) messages.push(`Grep timed out after ${String(DEFAULT_TIMEOUT_MS / 1e3)}s; partial results`);
29266
+ if (timedOut) messages.push(`Grep timed out after ${String(DEFAULT_TIMEOUT_MS$1 / 1e3)}s; partial results`);
29054
29267
  const contentBody = limited.join("\n");
29055
29268
  const combined = messages.length > 0 ? contentBody === "" ? messages.join("\n") : `${contentBody}\n${messages.join("\n")}` : contentBody;
29056
29269
  if (mode === "files_with_matches") return {
@@ -30662,12 +30875,12 @@ var DefaultSessionApplicationService = class {
30662
30875
  workspaceDir: this.deps.workspaceDir,
30663
30876
  compactionProvider,
30664
30877
  compactionConfig,
30665
- ...this.deps.approvalRuntime !== void 0 ? { approvalRuntime: this.deps.approvalRuntime } : {},
30666
- ...this.deps.approvalRuntimeFactory !== void 0 ? { approvalRuntimeFactory: this.deps.approvalRuntimeFactory } : {},
30878
+ approvalRuntime: this.deps.approvalRuntime,
30879
+ approvalRuntimeFactory: this.deps.approvalRuntimeFactory,
30667
30880
  hookEngine,
30668
30881
  skillManager: this.deps.skillManager,
30669
- ...this.deps.agentTypeRegistry !== void 0 ? { agentTypeRegistry: this.deps.agentTypeRegistry } : {},
30670
- ...this.deps.questionRuntimeProvider !== void 0 ? { questionRuntime: this.deps.questionRuntimeProvider({ sessionId }) } : {},
30882
+ agentTypeRegistry: this.deps.agentTypeRegistry,
30883
+ questionRuntime: this.deps.questionRuntimeProvider?.({ sessionId }),
30671
30884
  logger: this.deps.logger
30672
30885
  });
30673
30886
  this.deps.sessionLifecycle?.onSessionCreated?.(managed);
@@ -30707,12 +30920,12 @@ var DefaultSessionApplicationService = class {
30707
30920
  eventBus,
30708
30921
  compactionProvider,
30709
30922
  compactionConfig,
30710
- ...this.deps.approvalRuntime !== void 0 ? { approvalRuntime: this.deps.approvalRuntime } : {},
30711
- ...this.deps.approvalRuntimeFactory !== void 0 ? { approvalRuntimeFactory: this.deps.approvalRuntimeFactory } : {},
30923
+ approvalRuntime: this.deps.approvalRuntime,
30924
+ approvalRuntimeFactory: this.deps.approvalRuntimeFactory,
30712
30925
  hookEngine,
30713
30926
  skillManager: this.deps.skillManager,
30714
- ...this.deps.agentTypeRegistry !== void 0 ? { agentTypeRegistry: this.deps.agentTypeRegistry } : {},
30715
- ...this.deps.questionRuntimeProvider !== void 0 ? { questionRuntime: this.deps.questionRuntimeProvider({ sessionId }) } : {},
30927
+ agentTypeRegistry: this.deps.agentTypeRegistry,
30928
+ questionRuntime: this.deps.questionRuntimeProvider?.({ sessionId }),
30716
30929
  logger: this.deps.logger
30717
30930
  });
30718
30931
  const runtimeBundleProvider = this.deps.runtimeBundleProvider;
@@ -30777,7 +30990,7 @@ var DefaultSessionApplicationService = class {
30777
30990
  turn_count: turnCount,
30778
30991
  last_turn_id: lastTurnId,
30779
30992
  status,
30780
- ...planPath !== null ? { plan_path: planPath } : {}
30993
+ plan_path: planPath
30781
30994
  };
30782
30995
  }
30783
30996
  getStatus(sessionId) {
@@ -32772,6 +32985,12 @@ function projectReplayState(records, sessionInitialized, runtimeBaseline) {
32772
32985
  content,
32773
32986
  toolCalls: openStep.toolCalls
32774
32987
  });
32988
+ for (const tr of openStep.toolResults) messages.push({
32989
+ role: "tool",
32990
+ content: tr.content,
32991
+ toolCalls: [],
32992
+ toolCallId: tr.toolCallId
32993
+ });
32775
32994
  openStep = null;
32776
32995
  }
32777
32996
  for (const r of records) {
@@ -32795,18 +33014,25 @@ function projectReplayState(records, sessionInitialized, runtimeBaseline) {
32795
33014
  break;
32796
33015
  }
32797
33016
  case "tool_result": {
32798
- flushOpenStep();
32799
33017
  const pendingTodo = pendingTodoWrites.get(r.tool_call_id);
32800
33018
  if (pendingTodo !== void 0) {
32801
33019
  if (r.is_error !== true) todos = pendingTodo.map((todo) => ({ ...todo }));
32802
33020
  pendingTodoWrites.delete(r.tool_call_id);
32803
33021
  }
33022
+ const toolContent = isContentPartArray(r.output) ? r.output.map(cloneContentPart) : [{
33023
+ type: "text",
33024
+ text: typeof r.output === "string" ? r.output : JSON.stringify(r.output)
33025
+ }];
33026
+ if (openStep !== null && !openStep.hasStepEnd) {
33027
+ openStep.toolResults.push({
33028
+ toolCallId: r.tool_call_id,
33029
+ content: toolContent
33030
+ });
33031
+ break;
33032
+ }
32804
33033
  messages.push({
32805
33034
  role: "tool",
32806
- content: isContentPartArray(r.output) ? r.output.map(cloneContentPart) : [{
32807
- type: "text",
32808
- text: typeof r.output === "string" ? r.output : JSON.stringify(r.output)
32809
- }],
33035
+ content: toolContent,
32810
33036
  toolCalls: [],
32811
33037
  toolCallId: r.tool_call_id
32812
33038
  });
@@ -32846,6 +33072,7 @@ function projectReplayState(records, sessionInitialized, runtimeBaseline) {
32846
33072
  textChunks: [],
32847
33073
  thinkChunks: [],
32848
33074
  toolCalls: [],
33075
+ toolResults: [],
32849
33076
  hasStepEnd: false
32850
33077
  };
32851
33078
  break;
@@ -32857,6 +33084,7 @@ function projectReplayState(records, sessionInitialized, runtimeBaseline) {
32857
33084
  textChunks: [],
32858
33085
  thinkChunks: [],
32859
33086
  toolCalls: [],
33087
+ toolResults: [],
32860
33088
  hasStepEnd: false
32861
33089
  };
32862
33090
  }
@@ -32874,6 +33102,7 @@ function projectReplayState(records, sessionInitialized, runtimeBaseline) {
32874
33102
  textChunks: [],
32875
33103
  thinkChunks: [],
32876
33104
  toolCalls: [],
33105
+ toolResults: [],
32877
33106
  hasStepEnd: false
32878
33107
  };
32879
33108
  }
@@ -32961,7 +33190,7 @@ function normalizeSessionTimestampSeconds(value) {
32961
33190
  async function readSessionInfo(paths, sessionId) {
32962
33191
  const statePath = paths.statePath(sessionId);
32963
33192
  const cache = new StateCache(statePath);
32964
- const [state, lastActivity] = await Promise.all([cache.read(), stat(statePath).then((st) => Math.floor(st.mtimeMs / 1e3), () => {})]);
33193
+ const [state, lastActivity] = await Promise.all([cache.read(), stat(statePath).then((st) => Math.floor(st.mtimeMs / 1e3), () => void 0)]);
32965
33194
  if (state !== null) return {
32966
33195
  session_id: state.session_id,
32967
33196
  created_at: state.created_at,
@@ -33287,13 +33516,13 @@ var SessionManager = class {
33287
33516
  runtime: runtimeBundle.runtime,
33288
33517
  eventBus,
33289
33518
  tools: sessionTools,
33290
- ...options.enabledToolNames !== void 0 ? { enabledToolNames: options.enabledToolNames } : {},
33519
+ enabledToolNames: options.enabledToolNames,
33291
33520
  lifecycleStateMachine,
33292
33521
  producer: this.producer,
33293
33522
  onShellDeliver: options.onShellDeliver,
33294
33523
  skillManager: options.skillManager,
33295
- ...options.questionRuntime !== void 0 ? { questionRuntime: options.questionRuntime } : {},
33296
- ...runtimeBundle.compactionConfig !== void 0 ? { compactionConfig: runtimeBundle.compactionConfig } : {},
33524
+ questionRuntime: options.questionRuntime,
33525
+ compactionConfig: runtimeBundle.compactionConfig,
33297
33526
  compactionProvider: runtimeBundle.compactionProvider,
33298
33527
  journalCapability: options.journalCapability ?? createWiredJournalCapability({
33299
33528
  sessionDir,
@@ -33302,13 +33531,13 @@ var SessionManager = class {
33302
33531
  }),
33303
33532
  approvalRuntime,
33304
33533
  hookEngine: options.hookEngine,
33305
- ...options.toolExecutionScopeFactory !== void 0 ? { toolExecutionScopeFactory: options.toolExecutionScopeFactory } : {},
33534
+ toolExecutionScopeFactory: options.toolExecutionScopeFactory,
33306
33535
  sessionRules: options.sessionRules,
33307
33536
  permissionMode: options.permissionMode,
33308
33537
  stateCache,
33309
33538
  initialMeta,
33310
33539
  pathConfig: this.paths,
33311
- ...options.approvalStateStore !== void 0 ? { approvalStateStore: options.approvalStateStore } : {},
33540
+ approvalStateStore: options.approvalStateStore,
33312
33541
  ...options.agentTypeRegistry !== void 0 ? {
33313
33542
  subagentStore: new SubagentStore(sessionDir),
33314
33543
  agentTypeRegistry: options.agentTypeRegistry,
@@ -33425,7 +33654,7 @@ var SessionManager = class {
33425
33654
  model: projected.model,
33426
33655
  runtime: options.runtime,
33427
33656
  compactionProvider: options.compactionProvider,
33428
- ...options.compactionConfig !== void 0 ? { compactionConfig: options.compactionConfig } : {}
33657
+ compactionConfig: options.compactionConfig
33429
33658
  });
33430
33659
  const runtimeBundle = runtimeSlot.current();
33431
33660
  await stateCache.write({
@@ -33457,10 +33686,7 @@ var SessionManager = class {
33457
33686
  subagentStore = new SubagentStore(sessionDir);
33458
33687
  lostSubagentIds = await cleanupStaleSubagents(subagentStore);
33459
33688
  }
33460
- const approvalRuntime = this.buildApprovalRuntime(sessionId, sessionJournal, stateCache, {
33461
- ...options.approvalRuntime !== void 0 ? { approvalRuntime: options.approvalRuntime } : {},
33462
- ...options.approvalRuntimeFactory !== void 0 ? { approvalRuntimeFactory: options.approvalRuntimeFactory } : {}
33463
- });
33689
+ const approvalRuntime = this.buildApprovalRuntime(sessionId, sessionJournal, stateCache, options);
33464
33690
  const soulPlus = new SoulPlus({
33465
33691
  sessionId,
33466
33692
  contextState,
@@ -33775,7 +34001,7 @@ var SessionManager = class {
33775
34001
  turn_count: meta?.turn_count ?? existing.turn_count ?? 0,
33776
34002
  plan_slug: meta?.plan_slug,
33777
34003
  thinking_level: currentThinkingLevel,
33778
- ...currentPlanMode ? { plan_mode: true } : { plan_mode: false },
34004
+ plan_mode: currentPlanMode,
33779
34005
  todos: cloneTodoItems(currentTodos),
33780
34006
  last_exit_code: "clean"
33781
34007
  } : {
@@ -41668,12 +41894,9 @@ const KimiConfigSchema = z.object({
41668
41894
  defaultThinking: z.boolean().optional(),
41669
41895
  defaultYolo: z.boolean().optional(),
41670
41896
  defaultPlanMode: z.boolean().optional(),
41671
- defaultEditor: z.string().optional(),
41672
- theme: z.string().optional(),
41673
41897
  hooks: z.array(z.unknown()).optional(),
41674
41898
  services: ServicesConfigSchema.optional(),
41675
41899
  mergeAllAvailableSkills: z.boolean().optional(),
41676
- showThinkingStream: z.boolean().optional(),
41677
41900
  loopControl: LoopControlSchema.optional(),
41678
41901
  raw: z.record(z.string(), z.unknown()).optional()
41679
41902
  });
@@ -41871,11 +42094,8 @@ function configToTomlData(config) {
41871
42094
  setDefined(out, "default_thinking", config.defaultThinking);
41872
42095
  setDefined(out, "default_yolo", config.defaultYolo);
41873
42096
  setDefined(out, "default_plan_mode", config.defaultPlanMode);
41874
- setDefined(out, "default_editor", config.defaultEditor);
41875
- setDefined(out, "theme", config.theme);
41876
42097
  setDefined(out, "hooks", config.hooks);
41877
42098
  setDefined(out, "merge_all_available_skills", config.mergeAllAvailableSkills);
41878
- setDefined(out, "show_thinking_stream", config.showThinkingStream);
41879
42099
  const rawProviders = cloneRecord(out["providers"]);
41880
42100
  out["providers"] = Object.fromEntries(Object.entries(config.providers).map(([name, provider]) => [name, providerToToml(provider, rawProviders[name])]));
41881
42101
  if (config.models !== void 0) {
@@ -41890,63 +42110,6 @@ function configToTomlData(config) {
41890
42110
  return out;
41891
42111
  }
41892
42112
  //#endregion
41893
- //#region ../../packages/kosong/dist/capability-6GoTY4Ii.mjs
41894
- /**
41895
- * Shared read-only default returned when a provider has not catalogued a
41896
- * given model. Frozen so accidental mutation at one call site cannot leak
41897
- * into another.
41898
- */
41899
- const UNKNOWN_CAPABILITY = Object.freeze({
41900
- image_in: false,
41901
- video_in: false,
41902
- audio_in: false,
41903
- thinking: false,
41904
- tool_use: false,
41905
- max_context_tokens: 0
41906
- });
41907
- //#endregion
41908
- //#region ../../packages/kosong/dist/errors-DYOwJhxZ.mjs
41909
- /**
41910
- * Base error for all chat provider errors.
41911
- */
41912
- var ChatProviderError = class extends Error {
41913
- constructor(message) {
41914
- super(message);
41915
- this.name = "ChatProviderError";
41916
- }
41917
- };
41918
- /**
41919
- * Network-level connection failure.
41920
- */
41921
- var APIConnectionError$2 = class extends ChatProviderError {
41922
- constructor(message) {
41923
- super(message);
41924
- this.name = "APIConnectionError";
41925
- }
41926
- };
41927
- /**
41928
- * Request timed out.
41929
- */
41930
- var APITimeoutError = class extends ChatProviderError {
41931
- constructor(message) {
41932
- super(message);
41933
- this.name = "APITimeoutError";
41934
- }
41935
- };
41936
- /**
41937
- * HTTP status error from the API.
41938
- */
41939
- var APIStatusError = class extends ChatProviderError {
41940
- statusCode;
41941
- requestId;
41942
- constructor(statusCode, message, requestId) {
41943
- super(message);
41944
- this.name = "APIStatusError";
41945
- this.statusCode = statusCode;
41946
- this.requestId = requestId ?? null;
41947
- }
41948
- };
41949
- //#endregion
41950
42113
  //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.88.0_zod@4.3.6/node_modules/@anthropic-ai/sdk/internal/tslib.mjs
41951
42114
  function __classPrivateFieldSet(receiver, state, value, kind, f) {
41952
42115
  if (kind === "m") throw new TypeError("Private method is not writable");
@@ -47091,7 +47254,7 @@ Anthropic.Messages = Messages;
47091
47254
  Anthropic.Models = Models$1;
47092
47255
  Anthropic.Beta = Beta;
47093
47256
  //#endregion
47094
- //#region ../../packages/kosong/dist/providers/anthropic.mjs
47257
+ //#region ../../packages/kosong/src/providers/anthropic.ts
47095
47258
  /**
47096
47259
  * Normalize an Anthropic `stop_reason` string to the unified
47097
47260
  * {@link FinishReason} enum.
@@ -47277,7 +47440,7 @@ function convertMessage$3(message) {
47277
47440
  }
47278
47441
  function convertAnthropicError(error) {
47279
47442
  if (error instanceof APIConnectionTimeoutError$1) return new APITimeoutError(error.message);
47280
- if (error instanceof APIConnectionError$1) return new APIConnectionError$2(error.message);
47443
+ if (error instanceof APIConnectionError$1) return new APIConnectionError$3(error.message);
47281
47444
  if (error instanceof APIError$1 && typeof error.status === "number") {
47282
47445
  const reqId = error.requestID ?? null;
47283
47446
  return new APIStatusError(error.status, error.message, reqId);
@@ -75826,7 +75989,7 @@ function getApiKeyFromEnv() {
75826
75989
  return envGoogleApiKey || envGeminiApiKey || void 0;
75827
75990
  }
75828
75991
  //#endregion
75829
- //#region ../../packages/kosong/dist/providers/google-genai.mjs
75992
+ //#region ../../packages/kosong/src/providers/google-genai.ts
75830
75993
  /**
75831
75994
  * Normalize a Google GenAI (Gemini) `finishReason` value to the unified
75832
75995
  * {@link FinishReason} enum.
@@ -76214,8 +76377,8 @@ var GoogleGenAIStreamedMessage = class {
76214
76377
  }
76215
76378
  }
76216
76379
  };
76217
- const NETWORK_RE$1 = /network|connection|connect|disconnect|fetch failed/i;
76218
- const TIMEOUT_RE$1 = /timed?\s*out|timeout|deadline/i;
76380
+ const NETWORK_RE = /network|connection|connect|disconnect|fetch failed/i;
76381
+ const TIMEOUT_RE = /timed?\s*out|timeout|deadline/i;
76219
76382
  /**
76220
76383
  * Convert a Google GenAI SDK error (or raw Error) to a kosong `ChatProviderError`.
76221
76384
  */
@@ -76223,8 +76386,8 @@ function convertGoogleGenAIError(error) {
76223
76386
  if (error instanceof ApiError) return new APIStatusError(error.status, error.message);
76224
76387
  if (error instanceof Error) {
76225
76388
  const msg = error.message;
76226
- if (TIMEOUT_RE$1.test(msg)) return new APITimeoutError(msg);
76227
- if (NETWORK_RE$1.test(msg) || error instanceof TypeError && msg.includes("fetch")) return new APIConnectionError$2(msg);
76389
+ if (TIMEOUT_RE.test(msg)) return new APITimeoutError(msg);
76390
+ if (NETWORK_RE.test(msg) || error instanceof TypeError && msg.includes("fetch")) return new APIConnectionError$3(msg);
76228
76391
  const statusCode = error.code;
76229
76392
  if (typeof statusCode === "number") return new APIStatusError(statusCode, msg);
76230
76393
  return new ChatProviderError(`GoogleGenAI error: ${msg}`);
@@ -76388,197 +76551,7 @@ var GoogleGenAIChatProvider = class {
76388
76551
  }
76389
76552
  };
76390
76553
  //#endregion
76391
- //#region ../../packages/kosong/dist/openai-common-CmMTBbL2.mjs
76392
- /**
76393
- * Extract the concatenated text from a message's content parts.
76394
- *
76395
- * @param message The message to extract text from.
76396
- * @param sep Separator between text parts. Defaults to empty string.
76397
- */
76398
- function extractText(message, sep = "") {
76399
- return message.content.filter((part) => part.type === "text").map((part) => part.text).join(sep);
76400
- }
76401
- /**
76402
- * Convert a kosong `ContentPart` to OpenAI-compatible content part.
76403
- * Returns `null` for think parts (handled separately as reasoning_content).
76404
- */
76405
- function convertContentPart(part) {
76406
- switch (part.type) {
76407
- case "text": return {
76408
- type: "text",
76409
- text: part.text
76410
- };
76411
- case "think": return null;
76412
- case "image_url": return {
76413
- type: "image_url",
76414
- image_url: part.imageUrl.id === void 0 ? { url: part.imageUrl.url } : {
76415
- url: part.imageUrl.url,
76416
- id: part.imageUrl.id
76417
- }
76418
- };
76419
- case "audio_url": return {
76420
- type: "audio_url",
76421
- audio_url: part.audioUrl.id === void 0 ? { url: part.audioUrl.url } : {
76422
- url: part.audioUrl.url,
76423
- id: part.audioUrl.id
76424
- }
76425
- };
76426
- case "video_url": return {
76427
- type: "video_url",
76428
- video_url: part.videoUrl.id === void 0 ? { url: part.videoUrl.url } : {
76429
- url: part.videoUrl.url,
76430
- id: part.videoUrl.id
76431
- }
76432
- };
76433
- default: throw new Error(`Unknown content part type: ${part.type}`);
76434
- }
76435
- }
76436
- /**
76437
- * Convert a kosong `Tool` to OpenAI tool format.
76438
- */
76439
- function toolToOpenAI(tool) {
76440
- return {
76441
- type: "function",
76442
- function: {
76443
- name: tool.name,
76444
- description: tool.description,
76445
- parameters: tool.parameters
76446
- }
76447
- };
76448
- }
76449
- const NETWORK_RE = /network|connection|connect|disconnect/i;
76450
- const TIMEOUT_RE = /timed?\s*out|timeout|deadline/i;
76451
- function classifyBaseApiError(message) {
76452
- if (TIMEOUT_RE.test(message)) return new APITimeoutError(message);
76453
- if (NETWORK_RE.test(message)) return new APIConnectionError$2(message);
76454
- return new ChatProviderError(`Error: ${message}`);
76455
- }
76456
- /**
76457
- * Convert an OpenAI SDK error (or raw Error) to a kosong `ChatProviderError`.
76458
- */
76459
- function convertOpenAIError(error) {
76460
- if (error instanceof APIConnectionTimeoutError$2) return new APITimeoutError(error.message);
76461
- if (error instanceof APIConnectionError$3) return new APIConnectionError$2(error.message);
76462
- if (error instanceof APIError$2 && typeof error.status === "number") {
76463
- const reqId = error.requestID ?? null;
76464
- return new APIStatusError(error.status, error.message, reqId);
76465
- }
76466
- if (error instanceof APIError$2 && error.constructor === APIError$2 && error.error === void 0) return classifyBaseApiError(error.message);
76467
- if (error instanceof OpenAIError) return new ChatProviderError(`Error: ${error.message}`);
76468
- if (error instanceof Error) return new ChatProviderError(`Error: ${error.message}`);
76469
- return new ChatProviderError(`Error: ${String(error)}`);
76470
- }
76471
- /**
76472
- * Type guard: narrow a tool call union to the function-type variant.
76473
- * Works with OpenAI SDK's `ChatCompletionMessageToolCall` as well as
76474
- * any object carrying `{ type: string }`.
76475
- */
76476
- function isFunctionToolCall(tc) {
76477
- return tc.type === "function";
76478
- }
76479
- /**
76480
- * Map kosong `ThinkingEffort` to OpenAI `reasoning_effort` string.
76481
- */
76482
- function thinkingEffortToReasoningEffort(effort) {
76483
- switch (effort) {
76484
- case "off": return;
76485
- case "low": return "low";
76486
- case "medium": return "medium";
76487
- case "high": return "high";
76488
- default: throw new Error(`Unknown thinking effort: ${String(effort)}`);
76489
- }
76490
- }
76491
- /**
76492
- * Map OpenAI `reasoning_effort` string back to kosong `ThinkingEffort`.
76493
- */
76494
- function reasoningEffortToThinkingEffort(reasoning) {
76495
- if (reasoning === void 0 || reasoning === null) return null;
76496
- switch (reasoning) {
76497
- case "low":
76498
- case "minimal": return "low";
76499
- case "medium": return "medium";
76500
- case "high":
76501
- case "xhigh": return "high";
76502
- case "none": return "off";
76503
- default: return "off";
76504
- }
76505
- }
76506
- /**
76507
- * Extract `TokenUsage` from an OpenAI-compatible usage object.
76508
- */
76509
- function extractUsage(usage) {
76510
- if (usage === null || usage === void 0 || typeof usage !== "object") return null;
76511
- const u = usage;
76512
- const promptTokens = typeof u["prompt_tokens"] === "number" ? u["prompt_tokens"] : 0;
76513
- const completionTokens = typeof u["completion_tokens"] === "number" ? u["completion_tokens"] : 0;
76514
- let cached = 0;
76515
- if (typeof u["cached_tokens"] === "number") cached = u["cached_tokens"];
76516
- else if (typeof u["prompt_tokens_details"] === "object" && u["prompt_tokens_details"] !== null) {
76517
- const details = u["prompt_tokens_details"];
76518
- if (typeof details["cached_tokens"] === "number") cached = details["cached_tokens"];
76519
- }
76520
- return {
76521
- inputOther: promptTokens - cached,
76522
- output: completionTokens,
76523
- inputCacheRead: cached,
76524
- inputCacheCreation: 0
76525
- };
76526
- }
76527
- /**
76528
- * Normalize an OpenAI Chat Completions–style `finish_reason` string to the
76529
- * unified {@link FinishReason} enum.
76530
- *
76531
- * Used by both the Kimi and OpenAI Legacy adapters because they share the
76532
- * Chat Completions wire format. Returns `{ finishReason: null,
76533
- * rawFinishReason: null }` when the upstream value is missing or `null` so
76534
- * callers can treat "no signal" uniformly.
76535
- *
76536
- * Mapping:
76537
- * - `'stop'` → `'completed'`
76538
- * - `'tool_calls'` → `'tool_calls'`
76539
- * - `'function_call'` → `'tool_calls'` (legacy alias)
76540
- * - `'length'` → `'truncated'`
76541
- * - `'content_filter'` → `'filtered'`
76542
- * - any other non-null string → `'other'`
76543
- */
76544
- function normalizeOpenAIFinishReason(raw) {
76545
- if (raw === null || raw === void 0) return {
76546
- finishReason: null,
76547
- rawFinishReason: null
76548
- };
76549
- switch (raw) {
76550
- case "stop": return {
76551
- finishReason: "completed",
76552
- rawFinishReason: raw
76553
- };
76554
- case "tool_calls":
76555
- case "function_call": return {
76556
- finishReason: "tool_calls",
76557
- rawFinishReason: raw
76558
- };
76559
- case "length": return {
76560
- finishReason: "truncated",
76561
- rawFinishReason: raw
76562
- };
76563
- case "content_filter": return {
76564
- finishReason: "filtered",
76565
- rawFinishReason: raw
76566
- };
76567
- default: return {
76568
- finishReason: "other",
76569
- rawFinishReason: raw
76570
- };
76571
- }
76572
- }
76573
- /**
76574
- * Convert tool-role message content according to the chosen strategy.
76575
- */
76576
- function convertToolMessageContent(message, conversion) {
76577
- if (conversion === "extract_text") return extractText(message);
76578
- return message.content.map((p) => convertContentPart(p)).filter((p) => p !== null);
76579
- }
76580
- //#endregion
76581
- //#region ../../packages/kosong/dist/providers/kimi.mjs
76554
+ //#region ../../packages/kosong/src/providers/kimi-files.ts
76582
76555
  /**
76583
76556
  * Kimi-specific file upload client.
76584
76557
  *
@@ -76668,6 +76641,8 @@ function guessMimeTypeFromExt(filename) {
76668
76641
  if (dot < 0) return void 0;
76669
76642
  return EXT_TO_MIME[filename.slice(dot + 1).toLowerCase()];
76670
76643
  }
76644
+ //#endregion
76645
+ //#region ../../packages/kosong/src/providers/kimi.ts
76671
76646
  function convertStreamToolCall$1(toolCall, bufferedByIndex) {
76672
76647
  if (!toolCall.function) return [];
76673
76648
  const streamIndex = toolCall.index;
@@ -77007,7 +76982,7 @@ var KimiChatProvider = class {
77007
76982
  }
77008
76983
  };
77009
76984
  //#endregion
77010
- //#region ../../packages/kosong/dist/providers/openai-legacy.mjs
76985
+ //#region ../../packages/kosong/src/providers/openai-legacy.ts
77011
76986
  function convertStreamToolCall(toolCall, bufferedByIndex) {
77012
76987
  if (!toolCall.function) return [];
77013
76988
  const streamIndex = toolCall.index;
@@ -77311,7 +77286,7 @@ var OpenAILegacyChatProvider = class {
77311
77286
  }
77312
77287
  };
77313
77288
  //#endregion
77314
- //#region ../../packages/kosong/dist/providers/openai-responses.mjs
77289
+ //#region ../../packages/kosong/src/providers/openai-responses.ts
77315
77290
  /**
77316
77291
  * Normalize the Responses API status / incomplete_details into the unified
77317
77292
  * {@link FinishReason} enum.
@@ -78125,7 +78100,7 @@ var DeferredOAuthChatProvider = class DeferredOAuthChatProvider {
78125
78100
  return createProvider(this.options.providerName, {
78126
78101
  ...this.options.providerConfig,
78127
78102
  apiKey: "__oauth_token_placeholder__"
78128
- }, this.options.modelOverride, this.options.defaultHeaders).getCapability?.(model) ?? UNKNOWN_CAPABILITY$1;
78103
+ }, this.options.modelOverride, this.options.defaultHeaders).getCapability?.(model) ?? UNKNOWN_CAPABILITY;
78129
78104
  }
78130
78105
  async materializeProvider() {
78131
78106
  const accessToken = await this.options.oauthResolver(this.options.providerName);
@@ -94703,8 +94678,7 @@ async function startCoreWireServer(options) {
94703
94678
  },
94704
94679
  modelsProvider: options.modelsProvider,
94705
94680
  configProvider: options.configProvider,
94706
- logger: options.logger,
94707
- skillManager: options.skillManager
94681
+ logger: options.logger
94708
94682
  }).getEventFilter;
94709
94683
  options.transport.onMessage = (frame) => {
94710
94684
  (async () => {
@@ -94900,12 +94874,6 @@ async function createDefaultSoulPlusWireClient(options) {
94900
94874
  get defaultPlanMode() {
94901
94875
  return effectiveConfig.planMode ?? effectiveConfig.defaultPlanMode ?? false;
94902
94876
  },
94903
- get theme() {
94904
- return effectiveConfig.theme === "light" ? "light" : "dark";
94905
- },
94906
- get defaultEditor() {
94907
- return effectiveConfig.defaultEditor ?? "";
94908
- },
94909
94877
  get availableModels() {
94910
94878
  return effectiveConfig.models ?? {};
94911
94879
  },
@@ -95106,6 +95074,90 @@ async function createKimiAgent() {
95106
95074
  };
95107
95075
  }
95108
95076
  //#endregion
95077
+ //#region src/tui/config.ts
95078
+ /**
95079
+ * TUI-owned configuration.
95080
+ *
95081
+ * Agent/runtime settings live in core's `config.toml`; this file owns only
95082
+ * terminal UI preferences for the kimi-code client.
95083
+ */
95084
+ const INVALID_TUI_CONFIG_MESSAGE = "Invalid TUI config in ~/.kimi-code/tui.toml; using defaults.";
95085
+ const TuiThemeSchema = z.enum([
95086
+ "dark",
95087
+ "light",
95088
+ "auto"
95089
+ ]);
95090
+ const TuiConfigFileSchema = z.object({
95091
+ theme: TuiThemeSchema.optional(),
95092
+ editor: z.object({ command: z.string().optional() }).optional()
95093
+ });
95094
+ const TuiConfigSchema = z.object({
95095
+ theme: TuiThemeSchema,
95096
+ editorCommand: z.string().nullable()
95097
+ });
95098
+ const DEFAULT_TUI_CONFIG = TuiConfigSchema.parse({
95099
+ theme: "auto",
95100
+ editorCommand: null
95101
+ });
95102
+ /**
95103
+ * Thrown by `loadTuiConfig` when the on-disk TOML cannot be parsed.
95104
+ * Carries `fallback` so the caller can recover without re-running the
95105
+ * I/O, and use `message` (== `INVALID_TUI_CONFIG_MESSAGE`) as a
95106
+ * user-facing notice.
95107
+ */
95108
+ var TuiConfigParseError = class extends Error {
95109
+ name = "TuiConfigParseError";
95110
+ fallback;
95111
+ constructor(fallback) {
95112
+ super(INVALID_TUI_CONFIG_MESSAGE);
95113
+ this.fallback = fallback;
95114
+ }
95115
+ };
95116
+ function getTuiConfigPath() {
95117
+ return join(getDataDir(), "tui.toml");
95118
+ }
95119
+ async function loadTuiConfig(filePath = getTuiConfigPath()) {
95120
+ if (!existsSync(filePath)) {
95121
+ await saveTuiConfig(DEFAULT_TUI_CONFIG, filePath);
95122
+ return DEFAULT_TUI_CONFIG;
95123
+ }
95124
+ try {
95125
+ return parseTuiConfig(await readFile(filePath, "utf-8"));
95126
+ } catch {
95127
+ throw new TuiConfigParseError(DEFAULT_TUI_CONFIG);
95128
+ }
95129
+ }
95130
+ function parseTuiConfig(tomlText) {
95131
+ if (tomlText.trim().length === 0) return DEFAULT_TUI_CONFIG;
95132
+ const raw = parse$1(tomlText);
95133
+ return normalizeTuiConfig(TuiConfigFileSchema.parse(raw));
95134
+ }
95135
+ async function saveTuiConfig(config, filePath = getTuiConfigPath()) {
95136
+ await mkdir(dirname(filePath), { recursive: true });
95137
+ await writeFile(filePath, renderTuiConfig(config), "utf-8");
95138
+ }
95139
+ function normalizeTuiConfig(config) {
95140
+ const command = config.editor?.command?.trim();
95141
+ return TuiConfigSchema.parse({
95142
+ theme: config.theme ?? DEFAULT_TUI_CONFIG.theme,
95143
+ editorCommand: command === void 0 || command.length === 0 ? null : command
95144
+ });
95145
+ }
95146
+ function renderTuiConfig(config) {
95147
+ return `# ~/.kimi-code/tui.toml
95148
+ # Terminal UI preferences for kimi-code.
95149
+ # Agent/runtime settings stay in ~/.kimi-code/config.toml.
95150
+
95151
+ theme = "${config.theme}" # "auto" | "dark" | "light"
95152
+
95153
+ [editor]
95154
+ command = "${escapeTomlBasicString(config.editorCommand ?? "")}" # Empty uses $VISUAL / $EDITOR
95155
+ `;
95156
+ }
95157
+ function escapeTomlBasicString(value) {
95158
+ return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\b", "\\b").replaceAll(" ", "\\t").replaceAll("\n", "\\n").replaceAll("\f", "\\f").replaceAll("\r", "\\r");
95159
+ }
95160
+ //#endregion
95109
95161
  //#region src/utils/persistence.ts
95110
95162
  /**
95111
95163
  * Small persistence helpers for CLI-owned data files.
@@ -95269,7 +95321,7 @@ var ImageThumbnail = class extends Container {
95269
95321
  super();
95270
95322
  const caps = getCapabilities();
95271
95323
  if (!(caps.images === "kitty" || caps.images === "iterm2")) {
95272
- this.addChild(new Text(chalk.dim.cyan(` ${attachment.placeholder}`), 0, 0));
95324
+ this.addChild(new Text(chalk.hex(colors.accent)(` ${attachment.placeholder}`), 0, 0));
95273
95325
  return;
95274
95326
  }
95275
95327
  const image = new Image(Buffer.from(attachment.bytes).toString("base64"), attachment.mime, { fallbackColor: (s) => chalk.hex(colors.textDim)(s) }, {
@@ -95289,10 +95341,12 @@ const INDENT$1 = " ";
95289
95341
  var AssistantMessageComponent = class {
95290
95342
  contentContainer;
95291
95343
  markdownTheme;
95344
+ bulletColor;
95292
95345
  lastText = "";
95293
95346
  showBullet;
95294
- constructor(markdownTheme, showBullet = true) {
95347
+ constructor(markdownTheme, colors, showBullet = true) {
95295
95348
  this.markdownTheme = markdownTheme;
95349
+ this.bulletColor = colors.roleAssistant;
95296
95350
  this.showBullet = showBullet;
95297
95351
  this.contentContainer = new Container();
95298
95352
  }
@@ -95315,7 +95369,7 @@ var AssistantMessageComponent = class {
95315
95369
  const contentLines = this.contentContainer.render(contentWidth);
95316
95370
  const lines = [""];
95317
95371
  for (let i = 0; i < contentLines.length; i++) {
95318
- const p = i === 0 && this.showBullet ? chalk.white(BULLET$1) : INDENT$1;
95372
+ const p = i === 0 && this.showBullet ? chalk.hex(this.bulletColor)(BULLET$1) : INDENT$1;
95319
95373
  lines.push(p + contentLines[i]);
95320
95374
  }
95321
95375
  return lines;
@@ -95343,7 +95397,7 @@ var SkillActivationComponent = class extends Container {
95343
95397
  constructor(name, args, colors) {
95344
95398
  super();
95345
95399
  this.addChild(new Spacer(1));
95346
- const head = chalk.hex(colors.primary).bold("▶ Activated skill: ") + chalk.hex(colors.user).bold(name);
95400
+ const head = chalk.hex(colors.primary).bold("▶ Activated skill: ") + chalk.hex(colors.roleUser).bold(name);
95347
95401
  this.addChild(new Text(head, 0, 0));
95348
95402
  const trimmed = args?.trim() ?? "";
95349
95403
  if (trimmed.length > 0) {
@@ -95381,7 +95435,7 @@ var ThinkingComponent = class {
95381
95435
  spinnerInterval;
95382
95436
  constructor(text, colors, showMarker = true, mode = "finalized", ui) {
95383
95437
  this.text = text;
95384
- this.color = colors.thinking;
95438
+ this.color = colors.roleThinking;
95385
95439
  this.showMarker = showMarker;
95386
95440
  this.mode = mode;
95387
95441
  this.ui = ui;
@@ -95494,6 +95548,16 @@ function highlightLines(code, lang) {
95494
95548
  * Reuses the diff algorithm from approval/DiffPreview.tsx, but outputs
95495
95549
  * formatted text lines instead of React elements.
95496
95550
  */
95551
+ function makeDiffStyles(colors) {
95552
+ return {
95553
+ add: (s) => chalk.hex(colors.diffAdded)(s),
95554
+ del: (s) => chalk.hex(colors.diffRemoved)(s),
95555
+ addBold: (s) => chalk.bold.hex(colors.diffAddedStrong)(s),
95556
+ delBold: (s) => chalk.bold.hex(colors.diffRemovedStrong)(s),
95557
+ gutter: (s) => chalk.hex(colors.diffGutter)(s),
95558
+ meta: (s) => chalk.hex(colors.diffMeta)(s)
95559
+ };
95560
+ }
95497
95561
  function computeDiffLines(oldText, newText, oldStart = 1, newStart = 1, isIncomplete = false) {
95498
95562
  const oldLines = oldText ? oldText.split("\n") : [];
95499
95563
  const newLines = newText ? newText.split("\n") : [];
@@ -95582,10 +95646,10 @@ function buildClusters(diffLines, contextLines) {
95582
95646
  removedCount: removed
95583
95647
  };
95584
95648
  }
95585
- function formatDiffRow(line) {
95586
- const gutter = chalk.gray(String(line.lineNum).padStart(4) + " ");
95587
- if (line.kind === "add") return gutter + chalk.green("+ " + line.code);
95588
- if (line.kind === "delete") return gutter + chalk.red("- " + line.code);
95649
+ function formatDiffRow(line, s) {
95650
+ const gutter = s.gutter(String(line.lineNum).padStart(4) + " ");
95651
+ if (line.kind === "add") return gutter + s.add("+ " + line.code);
95652
+ if (line.kind === "delete") return gutter + s.del("- " + line.code);
95589
95653
  return gutter + " " + line.code;
95590
95654
  }
95591
95655
  /**
@@ -95597,15 +95661,16 @@ function formatDiffRow(line) {
95597
95661
  * Used by Edit's call preview where we want to show *what changed*
95598
95662
  * with enough context to read the change, but not the whole file.
95599
95663
  */
95600
- function renderDiffLinesClustered(oldText, newText, path, opts = {}) {
95664
+ function renderDiffLinesClustered(oldText, newText, path, colors, opts = {}) {
95665
+ const s = makeDiffStyles(colors);
95601
95666
  const contextLines = opts.contextLines ?? 3;
95602
95667
  const maxLines = opts.maxLines;
95603
95668
  const diffLines = computeDiffLines(oldText, newText, 1, 1, opts.isIncomplete ?? false);
95604
95669
  const { clusters, changedCount, addedCount, removedCount } = buildClusters(diffLines, contextLines);
95605
95670
  const output = [];
95606
95671
  let header = "";
95607
- if (addedCount > 0) header += chalk.green.bold(`+${String(addedCount)} `);
95608
- if (removedCount > 0) header += chalk.red.bold(`-${String(removedCount)} `);
95672
+ if (addedCount > 0) header += s.addBold(`+${String(addedCount)} `);
95673
+ if (removedCount > 0) header += s.delBold(`-${String(removedCount)} `);
95609
95674
  header += path;
95610
95675
  output.push(header);
95611
95676
  if (clusters.length === 0) return output;
@@ -95626,7 +95691,7 @@ function renderDiffLinesClustered(oldText, newText, path, opts = {}) {
95626
95691
  truncated = true;
95627
95692
  break;
95628
95693
  }
95629
- output.push(chalk.dim(` … ${String(gap)} unchanged line${gap > 1 ? "s" : ""} …`));
95694
+ output.push(s.meta(` … ${String(gap)} unchanged line${gap > 1 ? "s" : ""} …`));
95630
95695
  body++;
95631
95696
  }
95632
95697
  }
@@ -95636,7 +95701,7 @@ function renderDiffLinesClustered(oldText, newText, path, opts = {}) {
95636
95701
  break outer;
95637
95702
  }
95638
95703
  const line = diffLines[i];
95639
- output.push(formatDiffRow(line));
95704
+ output.push(formatDiffRow(line, s));
95640
95705
  body++;
95641
95706
  if (line.kind !== "context") shownChanges++;
95642
95707
  prevEnd = i;
@@ -95644,7 +95709,7 @@ function renderDiffLinesClustered(oldText, newText, path, opts = {}) {
95644
95709
  }
95645
95710
  if (truncated) {
95646
95711
  const hidden = changedCount - shownChanges;
95647
- if (hidden > 0) output.push(chalk.dim(` … ${String(hidden)} more change${hidden > 1 ? "s" : ""} hidden (ctrl+o to expand)`));
95712
+ if (hidden > 0) output.push(s.meta(` … ${String(hidden)} more change${hidden > 1 ? "s" : ""} hidden (ctrl+o to expand)`));
95648
95713
  }
95649
95714
  return output;
95650
95715
  }
@@ -95652,12 +95717,16 @@ function renderDiffLinesClustered(oldText, newText, path, opts = {}) {
95652
95717
  //#region src/tui/components/panels/plan-box.ts
95653
95718
  const LEFT_MARGIN$1 = 2;
95654
95719
  const SIDE_PADDING$1 = 1;
95720
+ const TITLE_PREFIX = " plan: ";
95721
+ const TITLE_SUFFIX = " ";
95722
+ const ELLIPSIS_PREFIX = "…/";
95655
95723
  var PlanBoxComponent = class {
95656
95724
  markdown;
95657
95725
  cachedWidth;
95658
95726
  cachedLines;
95659
- constructor(plan, markdownTheme, borderHex) {
95727
+ constructor(plan, markdownTheme, borderHex, planPath) {
95660
95728
  this.borderHex = borderHex;
95729
+ this.planPath = planPath;
95661
95730
  this.markdown = new Markdown(plan.trim(), 0, 0, markdownTheme);
95662
95731
  }
95663
95732
  invalidate() {
@@ -95671,8 +95740,8 @@ var PlanBoxComponent = class {
95671
95740
  const contentWidth = Math.max(1, horzLen - 2 * SIDE_PADDING$1);
95672
95741
  const paint = (s) => chalk.hex(this.borderHex)(s);
95673
95742
  const indent = " ".repeat(LEFT_MARGIN$1);
95674
- const title = " plan ";
95675
- const trailingDashLen = Math.max(0, horzLen - 6);
95743
+ const title = this.buildTitle(horzLen);
95744
+ const trailingDashLen = Math.max(0, horzLen - title.length);
95676
95745
  const top = indent + paint("┌") + paint(title) + paint("─".repeat(trailingDashLen)) + paint("┐");
95677
95746
  const bottom = indent + paint("└" + "─".repeat(horzLen) + "┘");
95678
95747
  const rawLines = this.markdown.render(contentWidth);
@@ -95686,7 +95755,28 @@ var PlanBoxComponent = class {
95686
95755
  this.cachedLines = lines;
95687
95756
  return lines;
95688
95757
  }
95758
+ buildTitle(horzLen) {
95759
+ const fallback = " plan ";
95760
+ const path = this.planPath;
95761
+ if (path === void 0 || path.length === 0) return fallback;
95762
+ const pathBudget = horzLen - 1 - 8;
95763
+ if (pathBudget < (path.split("/").pop() ?? path).length) return fallback;
95764
+ return TITLE_PREFIX + (path.length <= pathBudget ? path : truncatePathHead(path, pathBudget)) + TITLE_SUFFIX;
95765
+ }
95689
95766
  };
95767
+ function truncatePathHead(path, budget) {
95768
+ if (budget <= 2) return path.slice(-budget);
95769
+ const segments = path.split("/");
95770
+ let acc = segments.at(-1) ?? "";
95771
+ for (let i = segments.length - 2; i >= 0; i -= 1) {
95772
+ const candidate = (segments[i] ?? "") + "/" + acc;
95773
+ if (2 + candidate.length > budget) break;
95774
+ acc = candidate;
95775
+ }
95776
+ if (acc.length + 2 <= budget) return ELLIPSIS_PREFIX + acc;
95777
+ const tailBudget = budget - 2;
95778
+ return ELLIPSIS_PREFIX + acc.slice(-tailBudget);
95779
+ }
95690
95780
  //#endregion
95691
95781
  //#region src/tui/components/messages/tool-renderers/types.ts
95692
95782
  function strArg(args, ...keys) {
@@ -95991,6 +96081,42 @@ function extractApprovedPlan(output) {
95991
96081
  if (markerIndex < 0) return "";
95992
96082
  return output.slice(markerIndex + 17).trim();
95993
96083
  }
96084
+ const REJECT_PREFIX = "User rejected the plan.";
96085
+ const REJECT_FEEDBACK_PREFIX = "User rejected the plan. Feedback:";
96086
+ const APPROVED_OPTION_RE = /^User approved option "([^"]+)"\./;
96087
+ const PLAN_SAVED_TO_RE = /\nPlan saved to: ([^\n]+)\n/;
96088
+ /**
96089
+ * 解析 ExitPlanMode 工具结果的 content 字符串,识别审批结局 + plan 路径。
96090
+ * core 侧字符串模板见 `packages/kimi-core/src/tools/builtin/planning/exit-plan-mode.ts`:
96091
+ * - 通过:以 'Exited plan mode.' 或 'User approved option "<label>".' 开头,
96092
+ * plan-file 模式还会附带一行 'Plan saved to: <path>'。
96093
+ * - 拒绝:以 'User rejected the plan.' 开头;带 feedback 的形式是
96094
+ * 'User rejected the plan. Feedback:\n\n<text>'。
96095
+ * 这是字符串协议而非结构化字段,未来若 core 把结局结构化进 wire 层应优先切过去。
96096
+ */
96097
+ function interpretExitPlanModeOutcome(output) {
96098
+ if (output.startsWith(REJECT_PREFIX)) {
96099
+ if (output.startsWith(REJECT_FEEDBACK_PREFIX)) return {
96100
+ kind: "rejected",
96101
+ feedback: output.slice(33).trimStart()
96102
+ };
96103
+ return { kind: "rejected" };
96104
+ }
96105
+ const path = PLAN_SAVED_TO_RE.exec(output)?.[1]?.trim();
96106
+ const optionMatch = APPROVED_OPTION_RE.exec(output);
96107
+ if (optionMatch !== null) return path !== void 0 && path.length > 0 ? {
96108
+ kind: "approved",
96109
+ chosen: optionMatch[1],
96110
+ path
96111
+ } : {
96112
+ kind: "approved",
96113
+ chosen: optionMatch[1]
96114
+ };
96115
+ return path !== void 0 && path.length > 0 ? {
96116
+ kind: "approved",
96117
+ path
96118
+ } : { kind: "approved" };
96119
+ }
95994
96120
  const STREAMING_FIELD_RE$1 = /"(path|file_path|command|pattern|query|url|description|title|name)"\s*:\s*"((?:\\.|[^"\\])*)"/g;
95995
96121
  function unescapeJsonString$1(s) {
95996
96122
  return s.replaceAll(/\\(["\\/bfnrt])/g, (_, ch) => {
@@ -96116,6 +96242,14 @@ var ToolCallComponent = class extends Container {
96116
96242
  colors;
96117
96243
  ui;
96118
96244
  markdownTheme;
96245
+ planPath;
96246
+ /**
96247
+ * 退化用的 plan body:当 LLM 走 plan-file 模式、`args.plan` 为空时,
96248
+ * stream-ops 会通过 `setPlanInfo` 把从 `client.getPlan()` 拉到的内容
96249
+ * 喂进来,让 plan 框在审批等待阶段就能渲染(也避免拒绝/修订时
96250
+ * `## Approved Plan:` 标记缺失导致 plan body 永远不出现)。
96251
+ */
96252
+ currentPlan;
96119
96253
  headerText;
96120
96254
  callPreviewEndIndex = 0;
96121
96255
  subagentAgentId;
@@ -96160,6 +96294,26 @@ var ToolCallComponent = class extends Container {
96160
96294
  this.rebuildBody();
96161
96295
  this.ui?.requestRender();
96162
96296
  }
96297
+ /**
96298
+ * 异步注入 plan body / path。仅 ExitPlanMode 工具卡使用:当 LLM 走
96299
+ * plan-file 模式时 `args.plan` 为空,stream-ops 会从 `client.getPlan()`
96300
+ * 拉到 plan 文本后调用本方法,触发 plan 框渲染。
96301
+ */
96302
+ setPlanInfo(info) {
96303
+ if (this.toolCall.name !== "ExitPlanMode") return;
96304
+ let changed = false;
96305
+ if (info.plan !== void 0 && info.plan.length > 0 && this.currentPlan !== info.plan) {
96306
+ this.currentPlan = info.plan;
96307
+ changed = true;
96308
+ }
96309
+ if (info.path !== void 0 && info.path.length > 0 && this.planPath !== info.path) {
96310
+ this.planPath = info.path;
96311
+ changed = true;
96312
+ }
96313
+ if (!changed) return;
96314
+ this.rebuildBody();
96315
+ this.ui?.requestRender();
96316
+ }
96163
96317
  applySubagentReplay(subagent) {
96164
96318
  if (subagent === void 0) return;
96165
96319
  this.subagentAgentId = subagent.id;
@@ -96242,8 +96396,17 @@ var ToolCallComponent = class extends Container {
96242
96396
  const isError = result?.is_error ?? false;
96243
96397
  let bullet;
96244
96398
  if (isFinished) bullet = isError ? chalk.hex(colors.error)("✗ ") : chalk.hex(colors.success)("⏺ ");
96245
- else bullet = chalk.white("⏺ ");
96246
- if (toolCall.name === "ExitPlanMode") return chalk.hex(colors.primary).bold("Current plan");
96399
+ else bullet = chalk.hex(colors.roleAssistant)("⏺ ");
96400
+ if (toolCall.name === "ExitPlanMode") {
96401
+ const label = chalk.hex(colors.primary).bold("Current plan");
96402
+ if (!isFinished || result === void 0 || result.is_error === true) return label;
96403
+ const outcome = interpretExitPlanModeOutcome(result.output);
96404
+ if (outcome.kind === "approved") {
96405
+ const chipText = outcome.chosen !== void 0 && outcome.chosen.length > 0 ? `Approved: ${outcome.chosen}` : "Approved";
96406
+ return `${label}${chalk.hex(colors.success)(` · ${chipText}`)}`;
96407
+ }
96408
+ return `${label}${chalk.hex(colors.error)(" · Rejected")}`;
96409
+ }
96247
96410
  if (toolCall.name === "AskUserQuestion") {
96248
96411
  const label = isFinished ? isError ? "Could not collect your input" : "Collected your answers" : "Waiting for your input";
96249
96412
  const tone = isError ? chalk.hex(colors.error) : chalk.hex(colors.primary);
@@ -96333,7 +96496,7 @@ var ToolCallComponent = class extends Container {
96333
96496
  const oldStr = str(this.toolCall.args["old_string"]);
96334
96497
  const newStr = str(this.toolCall.args["new_string"]);
96335
96498
  if (oldStr.length === 0 && newStr.length === 0) return;
96336
- const lines = renderDiffLinesClustered(oldStr, newStr, str(this.toolCall.args["file_path"] ?? this.toolCall.args["path"]), {
96499
+ const lines = renderDiffLinesClustered(oldStr, newStr, str(this.toolCall.args["file_path"] ?? this.toolCall.args["path"]), this.colors, {
96337
96500
  contextLines: 3,
96338
96501
  ...shouldCap ? { maxLines: CALL_PREVIEW_LINES } : {}
96339
96502
  });
@@ -96366,7 +96529,7 @@ var ToolCallComponent = class extends Container {
96366
96529
  const oldStr = extractPartialStringField(streamText, "old_string") ?? "";
96367
96530
  const newStr = extractPartialStringField(streamText, "new_string") ?? "";
96368
96531
  if (oldStr.length === 0 && newStr.length === 0) return;
96369
- const lines = renderDiffLinesClustered(oldStr, newStr, extractPartialStringField(streamText, "file_path") ?? extractPartialStringField(streamText, "path") ?? "", {
96532
+ const lines = renderDiffLinesClustered(oldStr, newStr, extractPartialStringField(streamText, "file_path") ?? extractPartialStringField(streamText, "path") ?? "", this.colors, {
96370
96533
  contextLines: 3,
96371
96534
  isIncomplete: true
96372
96535
  });
@@ -96384,22 +96547,41 @@ var ToolCallComponent = class extends Container {
96384
96547
  }
96385
96548
  }
96386
96549
  buildPlanPreview() {
96387
- const plan = str(this.toolCall.args["plan"]);
96388
- if (plan.length === 0 || this.markdownTheme === void 0) return;
96389
- this.addChild(new PlanBoxComponent(plan, this.markdownTheme, this.colors.success));
96390
- }
96391
- renderPlanFromResult(result) {
96392
- const plan = extractApprovedPlan(result.output);
96393
- if (plan.length === 0) return false;
96394
- if (this.markdownTheme !== void 0) this.addChild(new PlanBoxComponent(plan, this.markdownTheme, this.colors.success));
96550
+ const plan = this.resolvePlanForPreview();
96551
+ if (plan.length === 0) return;
96552
+ const path = this.resolvePlanPath();
96553
+ if (this.markdownTheme !== void 0) this.addChild(new PlanBoxComponent(plan, this.markdownTheme, this.colors.success, path));
96395
96554
  else this.addChild(new Text(chalk.dim(plan), 2, 0));
96396
- return true;
96555
+ }
96556
+ resolvePlanForPreview() {
96557
+ const inlinePlan = str(this.toolCall.args["plan"]);
96558
+ if (inlinePlan.length > 0) return inlinePlan;
96559
+ if (this.result !== void 0 && !this.result.is_error) {
96560
+ const approved = extractApprovedPlan(this.result.output);
96561
+ if (approved.length > 0) return approved;
96562
+ }
96563
+ return this.currentPlan ?? "";
96564
+ }
96565
+ resolvePlanPath() {
96566
+ if (this.result !== void 0 && !this.result.is_error) {
96567
+ const fromResult = interpretExitPlanModeOutcome(this.result.output).path;
96568
+ if (fromResult !== void 0 && fromResult.length > 0) return fromResult;
96569
+ }
96570
+ return this.planPath;
96397
96571
  }
96398
96572
  buildContent() {
96399
96573
  const { result } = this;
96400
96574
  if (result === void 0 || !result.output) return;
96401
96575
  if (this.toolCall.name === "ExitPlanMode" && !result.is_error) {
96402
- if (str(this.toolCall.args["plan"]).length === 0 && this.renderPlanFromResult(result)) return;
96576
+ const outcome = interpretExitPlanModeOutcome(result.output);
96577
+ if (outcome.kind === "rejected" && outcome.feedback !== void 0) {
96578
+ const trimmed = outcome.feedback.trim();
96579
+ if (trimmed.length > 0) {
96580
+ const labelTone = chalk.hex(this.colors.warning).bold;
96581
+ this.addChild(new Text(labelTone("↪ Suggestion"), 2, 0));
96582
+ for (const line of trimmed.split("\n")) this.addChild(new Text(line, 4, 0));
96583
+ }
96584
+ }
96403
96585
  return;
96404
96586
  }
96405
96587
  if (this.toolCall.name === "SetTodoList" && !result.is_error) return;
@@ -96450,7 +96632,7 @@ var UserMessageComponent = class extends Container {
96450
96632
  constructor(text, colors) {
96451
96633
  super();
96452
96634
  this.addChild(new Spacer(1));
96453
- this.addChild(new Text(chalk.hex(colors.user).bold("✨ " + text), 0, 0));
96635
+ this.addChild(new Text(chalk.hex(colors.roleUser).bold("✨ " + text), 0, 0));
96454
96636
  }
96455
96637
  };
96456
96638
  //#endregion
@@ -96473,14 +96655,16 @@ var WelcomeComponent = class {
96473
96655
  const textWidth = Math.max(4, innerWidth - logoWidth - 2);
96474
96656
  const rightRow0 = truncateToWidth(chalk.bold.hex(this.colors.primary)("Welcome to Kimi Code!"), textWidth, "…");
96475
96657
  const isLoggedOut = !this.state.model;
96476
- const rightRow1 = truncateToWidth(chalk.dim(isLoggedOut ? "Run /login to sign in." : "Send /help for help information."), textWidth, "…");
96658
+ const dim = chalk.hex(this.colors.textDim);
96659
+ const labelStyle = chalk.bold.hex(this.colors.textDim);
96660
+ const rightRow1 = truncateToWidth(dim(isLoggedOut ? "Run /login to sign in." : "Send /help for help information."), textWidth, "…");
96477
96661
  const headerLines = [primary(logo[0].padEnd(logoWidth)) + gap + rightRow0, primary(logo[1].padEnd(logoWidth)) + gap + rightRow1];
96478
- const modelValue = isLoggedOut ? chalk.yellow("(not signed in — run /login)") : this.state.model;
96662
+ const modelValue = isLoggedOut ? chalk.hex(this.colors.warning)("(not signed in — run /login)") : this.state.model;
96479
96663
  const infoLines = [
96480
- chalk.dim.bold("Directory: ") + this.state.workDir,
96481
- chalk.dim.bold("Session: ") + this.state.sessionId,
96482
- chalk.dim.bold("Model: ") + modelValue,
96483
- chalk.dim.bold("Version: ") + this.state.version
96664
+ labelStyle("Directory: ") + this.state.workDir,
96665
+ labelStyle("Session: ") + this.state.sessionId,
96666
+ labelStyle("Model: ") + modelValue,
96667
+ labelStyle("Version: ") + this.state.version
96484
96668
  ];
96485
96669
  const contentLines = [
96486
96670
  ...headerLines,
@@ -96646,32 +96830,32 @@ function createTranscriptComponent(state, entry) {
96646
96830
  if (state.toolOutputExpanded) tc.setExpanded(true);
96647
96831
  return tc;
96648
96832
  }
96649
- return entry.renderMode === "notice" ? createNoticeEntry(entry.content, entry.detail, state.colors) : createStatusEntry(entry.content, entry.color);
96650
- case "status": return entry.renderMode === "notice" ? createNoticeEntry(entry.content, entry.detail, state.colors) : createStatusEntry(entry.content, entry.color);
96833
+ return entry.renderMode === "notice" ? createNoticeEntry(entry.content, entry.detail, state.colors) : createStatusEntry(entry.content, state.colors, entry.color);
96834
+ case "status": return entry.renderMode === "notice" ? createNoticeEntry(entry.content, entry.detail, state.colors) : createStatusEntry(entry.content, state.colors, entry.color);
96651
96835
  default: return null;
96652
96836
  }
96653
96837
  }
96654
96838
  function createAssistantEntry(state, content) {
96655
- const component = new AssistantMessageComponent(state.markdownTheme);
96839
+ const component = new AssistantMessageComponent(state.markdownTheme, state.colors);
96656
96840
  component.updateContent(content);
96657
96841
  return component;
96658
96842
  }
96659
- function paintStatus(content, color) {
96660
- if (color === void 0) return chalk.dim(content);
96843
+ function paintStatus(content, colors, color) {
96844
+ if (color === void 0) return chalk.hex(colors.textDim)(content);
96661
96845
  if (color.startsWith("#")) return chalk.hex(color)(content);
96662
96846
  const fn = chalk[color];
96663
96847
  return typeof fn === "function" ? fn(content) : content;
96664
96848
  }
96665
- function createStatusEntry(content, color) {
96849
+ function createStatusEntry(content, colors, color) {
96666
96850
  const container = new Container();
96667
- const styled = paintStatus(content, color);
96851
+ const styled = paintStatus(content, colors, color);
96668
96852
  container.addChild(new Text(` ${styled}`, 0, 0));
96669
96853
  return container;
96670
96854
  }
96671
96855
  function createNoticeEntry(title, detail, colors) {
96672
96856
  const container = new Container();
96673
96857
  container.addChild(new Spacer(1));
96674
- container.addChild(new Text(` ${chalk.white(title)}`, 0, 0));
96858
+ container.addChild(new Text(` ${chalk.hex(colors.textStrong)(title)}`, 0, 0));
96675
96859
  if (detail !== void 0 && detail.length > 0) container.addChild(new Text(` ${chalk.hex(colors.textDim)(detail)}`, 0, 0));
96676
96860
  return container;
96677
96861
  }
@@ -96779,21 +96963,243 @@ function buildImagePart(att) {
96779
96963
  };
96780
96964
  }
96781
96965
  //#endregion
96966
+ //#region src/tui/theme/colors.ts
96967
+ /**
96968
+ * Color palette definitions for dark and light themes.
96969
+ *
96970
+ * Two layers:
96971
+ * - private `dark` / `light` raw palettes — unsemantic constants reused
96972
+ * across multiple semantic tokens to avoid hex literal duplication.
96973
+ * - exported `darkColors` / `lightColors` — the semantic `ColorPalette`
96974
+ * consumed by every UI component via chalk.hex(...).
96975
+ *
96976
+ * Light palette values are tuned for ≥ 4.5:1 contrast against #FFFFFF
96977
+ * for text tokens and ≥ 3:1 for chrome (border / large text), matching
96978
+ * WCAG AA. See plan in `~/.claude/plans/kimi-code-tui-zippy-spindle.md`.
96979
+ */
96980
+ const dark = {
96981
+ blue400: "#4FA8FF",
96982
+ cyan400: "#5BC0BE",
96983
+ gray50: "#F5F5F5",
96984
+ gray100: "#E0E0E0",
96985
+ gray500: "#888888",
96986
+ gray600: "#6B6B6B",
96987
+ gray800: "#3A3A3A",
96988
+ green400: "#4EC87E",
96989
+ green300: "#7AD99B",
96990
+ red400: "#E85454",
96991
+ red300: "#F08585",
96992
+ amber400: "#E8A838",
96993
+ orange300: "#FFCB6B"
96994
+ };
96995
+ const light = {
96996
+ blue600: "#1565C0",
96997
+ cyan700: "#00838F",
96998
+ gray900: "#1A1A1A",
96999
+ gray700: "#454545",
97000
+ gray600: "#5F5F5F",
97001
+ gray500: "#737373",
97002
+ gray400: "#9CA3AF",
97003
+ green700: "#0E7A38",
97004
+ red700: "#B91C1C",
97005
+ amber800: "#92660A",
97006
+ orange700: "#9A4A00"
97007
+ };
97008
+ const darkColors = {
97009
+ primary: dark.blue400,
97010
+ accent: dark.cyan400,
97011
+ text: dark.gray100,
97012
+ textStrong: dark.gray50,
97013
+ textDim: dark.gray500,
97014
+ textMuted: dark.gray600,
97015
+ border: dark.gray800,
97016
+ borderFocus: dark.amber400,
97017
+ success: dark.green400,
97018
+ warning: dark.amber400,
97019
+ error: dark.red400,
97020
+ diffAdded: dark.green400,
97021
+ diffRemoved: dark.red400,
97022
+ diffAddedStrong: dark.green300,
97023
+ diffRemovedStrong: dark.red300,
97024
+ diffGutter: dark.gray600,
97025
+ diffMeta: dark.gray500,
97026
+ roleUser: dark.orange300,
97027
+ roleAssistant: dark.gray100,
97028
+ roleThinking: dark.gray500,
97029
+ roleTool: dark.amber400,
97030
+ status: dark.gray500
97031
+ };
97032
+ const lightColors = {
97033
+ primary: light.blue600,
97034
+ accent: light.cyan700,
97035
+ text: light.gray900,
97036
+ textStrong: light.gray900,
97037
+ textDim: light.gray700,
97038
+ textMuted: light.gray600,
97039
+ border: light.gray400,
97040
+ borderFocus: light.amber800,
97041
+ success: light.green700,
97042
+ warning: light.amber800,
97043
+ error: light.red700,
97044
+ diffAdded: light.green700,
97045
+ diffRemoved: light.red700,
97046
+ diffAddedStrong: light.green700,
97047
+ diffRemovedStrong: light.red700,
97048
+ diffGutter: light.gray500,
97049
+ diffMeta: light.gray600,
97050
+ roleUser: light.orange700,
97051
+ roleAssistant: light.gray900,
97052
+ roleThinking: light.gray700,
97053
+ roleTool: light.amber800,
97054
+ status: light.gray700
97055
+ };
97056
+ function getColorPalette(theme) {
97057
+ return theme === "dark" ? darkColors : lightColors;
97058
+ }
97059
+ //#endregion
97060
+ //#region src/tui/theme/detect.ts
97061
+ const DEFAULT_TIMEOUT_MS = 250;
97062
+ const OSC11_QUERY = "\x1B]11;?\x07";
97063
+ const OSC11_RESPONSE = /\]11;rgb:([0-9a-f]{1,4})\/([0-9a-f]{1,4})\/([0-9a-f]{1,4})/i;
97064
+ async function detectTerminalTheme(opts = {}) {
97065
+ if (!isInteractiveTerminal()) return "dark";
97066
+ if (isColorOptOut()) return "dark";
97067
+ const fromOsc = await queryOsc11({ timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS });
97068
+ if (fromOsc !== null) return fromOsc;
97069
+ const fromColorFgBg = parseColorFgBg(process.env["COLORFGBG"]);
97070
+ if (fromColorFgBg !== null) return fromColorFgBg;
97071
+ return "dark";
97072
+ }
97073
+ function isInteractiveTerminal() {
97074
+ return (process.stdin.isTTY ?? false) && (process.stdout.isTTY ?? false);
97075
+ }
97076
+ function isColorOptOut() {
97077
+ const env = process.env;
97078
+ if (env["NO_COLOR"] !== void 0 && env["NO_COLOR"] !== "") return true;
97079
+ if (env["FORCE_COLOR"] === "0") return true;
97080
+ if (env["CI"] !== void 0 && env["CI"] !== "" && env["CI"] !== "0") return true;
97081
+ return false;
97082
+ }
97083
+ async function queryOsc11(opts) {
97084
+ const stdin = process.stdin;
97085
+ if (typeof stdin.setRawMode !== "function") return null;
97086
+ if (process.stdin.listenerCount("data") > 0) return null;
97087
+ const wasRaw = stdin.isRaw === true;
97088
+ let buffer = "";
97089
+ let listener = null;
97090
+ let timer = null;
97091
+ try {
97092
+ if (!wasRaw) stdin.setRawMode(true);
97093
+ return await new Promise((resolve) => {
97094
+ listener = (chunk) => {
97095
+ buffer += chunk.toString("utf8");
97096
+ const match = OSC11_RESPONSE.exec(buffer);
97097
+ if (match === null) return;
97098
+ const [, r, g, b] = match;
97099
+ if (r === void 0 || g === void 0 || b === void 0) return;
97100
+ resolve(themeFromHexChannels(r, g, b));
97101
+ };
97102
+ stdin.on("data", listener);
97103
+ timer = setTimeout(() => {
97104
+ resolve(null);
97105
+ }, opts.timeoutMs);
97106
+ try {
97107
+ process.stdout.write(OSC11_QUERY);
97108
+ } catch {
97109
+ resolve(null);
97110
+ }
97111
+ });
97112
+ } catch {
97113
+ return null;
97114
+ } finally {
97115
+ if (timer !== null) clearTimeout(timer);
97116
+ if (listener !== null) stdin.off("data", listener);
97117
+ if (!wasRaw) try {
97118
+ stdin.setRawMode(false);
97119
+ } catch {}
97120
+ }
97121
+ }
97122
+ function themeFromHexChannels(rHex, gHex, bHex) {
97123
+ const r = normalizeChannel(rHex);
97124
+ const g = normalizeChannel(gHex);
97125
+ const b = normalizeChannel(bHex);
97126
+ return .2126 * r + .7152 * g + .0722 * b > .5 ? "light" : "dark";
97127
+ }
97128
+ function normalizeChannel(hex) {
97129
+ const max = (1 << hex.length * 4) - 1;
97130
+ const value = parseInt(hex, 16);
97131
+ return Number.isFinite(value) ? value / max : 0;
97132
+ }
97133
+ /**
97134
+ * COLORFGBG is `"fg;bg"` (sometimes `"fg;default;bg"`). The last token is
97135
+ * the background ANSI 16-color index; 0–6 and 8 are dark, the rest light.
97136
+ */
97137
+ function parseColorFgBg(value) {
97138
+ if (value === void 0 || value === "") return null;
97139
+ const bgRaw = value.split(";").at(-1);
97140
+ if (bgRaw === void 0) return null;
97141
+ const bg = parseInt(bgRaw, 10);
97142
+ if (!Number.isInteger(bg)) return null;
97143
+ return new Set([
97144
+ 0,
97145
+ 1,
97146
+ 2,
97147
+ 3,
97148
+ 4,
97149
+ 5,
97150
+ 6,
97151
+ 8
97152
+ ]).has(bg) ? "dark" : "light";
97153
+ }
97154
+ //#endregion
97155
+ //#region src/tui/theme/styles.ts
97156
+ /**
97157
+ * Theme-aware style helpers built on chalk. Components hold a reference
97158
+ * to a `ThemeStyles` instance via `state.styles` and never reach into
97159
+ * raw chalk color names — that keeps theme switches consistent and lets
97160
+ * every visual token route through `ColorPalette`.
97161
+ */
97162
+ function createThemeStyles(colors) {
97163
+ return {
97164
+ colors,
97165
+ primary: (s) => chalk.hex(colors.primary)(s),
97166
+ accent: (s) => chalk.hex(colors.accent)(s),
97167
+ dim: (s) => chalk.hex(colors.textDim)(s),
97168
+ muted: (s) => chalk.hex(colors.textMuted)(s),
97169
+ text: (s) => chalk.hex(colors.text)(s),
97170
+ strong: (s) => chalk.hex(colors.textStrong)(s),
97171
+ error: (s) => chalk.hex(colors.error)(s),
97172
+ warning: (s) => chalk.hex(colors.warning)(s),
97173
+ success: (s) => chalk.hex(colors.success)(s),
97174
+ label: (s) => chalk.bold.hex(colors.textDim)(s),
97175
+ value: (s) => chalk.hex(colors.text)(s),
97176
+ diffAdd: (s) => chalk.hex(colors.diffAdded)(s),
97177
+ diffDel: (s) => chalk.hex(colors.diffRemoved)(s),
97178
+ diffAddBold: (s) => chalk.bold.hex(colors.diffAddedStrong)(s),
97179
+ diffDelBold: (s) => chalk.bold.hex(colors.diffRemovedStrong)(s),
97180
+ diffGutter: (s) => chalk.hex(colors.diffGutter)(s),
97181
+ diffMeta: (s) => chalk.hex(colors.diffMeta)(s)
97182
+ };
97183
+ }
97184
+ //#endregion
96782
97185
  //#region src/tui/theme/pi-tui-theme.ts
96783
- const HEADING_HASH_PREFIX = /^((?:\x1B\[[0-9;]*m)*)#{1,6}[ \t]+/;
97186
+ const HEADING_HASH_PREFIX = /^((?:\u001B\[[0-9;]*m)*)#{1,6}[ \t]+/;
96784
97187
  function createMarkdownTheme(colors) {
96785
97188
  const stripHash = (text) => text.replace(HEADING_HASH_PREFIX, "$1");
97189
+ const muted = chalk.hex(colors.textMuted);
97190
+ const dim = chalk.hex(colors.textDim);
97191
+ const border = chalk.hex(colors.border);
96786
97192
  return {
96787
97193
  heading: (text) => chalk.bold.hex(colors.text)(stripHash(text)),
96788
97194
  link: (text) => chalk.hex(colors.primary)(text),
96789
- linkUrl: (text) => chalk.dim(text),
97195
+ linkUrl: (text) => muted(text),
96790
97196
  code: (text) => chalk.hex(colors.primary)(text),
96791
97197
  codeBlock: (text) => text,
96792
- codeBlockBorder: (text) => chalk.dim(text),
96793
- quote: (text) => chalk.gray(text),
96794
- quoteBorder: (text) => chalk.gray(text),
96795
- hr: (text) => chalk.dim(text),
96796
- listBullet: (text) => chalk.white(text.replace(/^-/, "•")),
97198
+ codeBlockBorder: (text) => border(text),
97199
+ quote: (text) => dim(text),
97200
+ quoteBorder: (text) => dim(text),
97201
+ hr: (text) => border(text),
97202
+ listBullet: (text) => chalk.hex(colors.roleAssistant)(text.replace(/^-/, "•")),
96797
97203
  bold: (text) => chalk.bold(text),
96798
97204
  italic: (text) => chalk.italic(text),
96799
97205
  strikethrough: (text) => chalk.strikethrough(text),
@@ -96811,18 +97217,39 @@ function createMarkdownTheme(colors) {
96811
97217
  };
96812
97218
  }
96813
97219
  function createEditorTheme(colors) {
97220
+ const muted = chalk.hex(colors.textMuted);
96814
97221
  return {
96815
97222
  borderColor: (s) => chalk.hex(colors.border)(s),
96816
97223
  selectList: {
96817
97224
  selectedPrefix: (s) => chalk.hex(colors.primary)(s),
96818
97225
  selectedText: (s) => chalk.hex(colors.primary)(s),
96819
- description: (s) => chalk.dim(s),
96820
- scrollInfo: (s) => chalk.dim(s),
96821
- noMatch: (s) => chalk.dim(s)
97226
+ description: (s) => muted(s),
97227
+ scrollInfo: (s) => muted(s),
97228
+ noMatch: (s) => muted(s)
96822
97229
  }
96823
97230
  };
96824
97231
  }
96825
97232
  //#endregion
97233
+ //#region src/tui/theme/index.ts
97234
+ /**
97235
+ * Resolve a user preference to a concrete palette key. `'auto'` triggers
97236
+ * terminal background detection (OSC 11 with COLORFGBG / dark fallback);
97237
+ * explicit choices pass through.
97238
+ */
97239
+ async function resolveTheme(theme) {
97240
+ if (theme === "auto") return detectTerminalTheme();
97241
+ return theme;
97242
+ }
97243
+ /**
97244
+ * Synchronous fallback used by paths that cannot wait on terminal probes
97245
+ * (initial state construction, in-TUI theme switches). `'auto'` collapses
97246
+ * to `'dark'`; explicit choices pass through.
97247
+ */
97248
+ function resolveThemeSync(theme) {
97249
+ if (theme === "auto") return "dark";
97250
+ return theme;
97251
+ }
97252
+ //#endregion
96826
97253
  //#region src/tui/core/ui-ops.ts
96827
97254
  function isExpandable(obj) {
96828
97255
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
@@ -96898,15 +97325,34 @@ function clearTranscriptAndRedraw(state) {
96898
97325
  }
96899
97326
  /** Use primary color for slash commands or while plan mode is active. */
96900
97327
  function updateEditorBorderHighlight(state, text) {
96901
- const editorTheme = createEditorTheme(state.colors);
96902
97328
  const trimmed = (text ?? state.editor.getText()).trimStart();
96903
97329
  if (state.appState.planMode || trimmed.startsWith("/")) {
96904
97330
  const primary = state.colors.primary;
96905
97331
  state.editor.borderColor = (s) => chalk.hex(primary)(s);
96906
- } else state.editor.borderColor = editorTheme.borderColor;
97332
+ } else state.editor.borderColor = state.editorTheme.borderColor;
96907
97333
  state.editor.slashHighlightHex = state.colors.primary;
96908
97334
  state.ui.requestRender();
96909
97335
  }
97336
+ /**
97337
+ * Apply a theme preference. `resolved` lets callers pre-compute the
97338
+ * concrete palette (e.g. async OSC 11 detection done before re-entering
97339
+ * the TUI); when omitted we fall back to the synchronous rule
97340
+ * (`'auto'` → `'dark'`).
97341
+ */
97342
+ function applyTheme(state, theme, hooks, resolved) {
97343
+ const resolvedTheme = resolved ?? resolveThemeSync(theme);
97344
+ Object.assign(state.colors, getColorPalette(resolvedTheme));
97345
+ state.appState.theme = theme;
97346
+ state.resolvedTheme = resolvedTheme;
97347
+ state.styles = createThemeStyles(state.colors);
97348
+ state.markdownTheme = createMarkdownTheme(state.colors);
97349
+ state.editorTheme = createEditorTheme(state.colors);
97350
+ updateEditorBorderHighlight(state);
97351
+ hooks.syncFooter();
97352
+ hooks.refreshActivityPane();
97353
+ hooks.refreshQueuePane();
97354
+ state.ui.requestRender(true);
97355
+ }
96910
97356
  //#endregion
96911
97357
  //#region src/tui/handlers/subagent.ts
96912
97358
  function handleSubagentSourceEvent(ectx, payload) {
@@ -97379,6 +97825,31 @@ const shellCommands = [
97379
97825
  async execute() {
97380
97826
  return ok$1("__show_usage__");
97381
97827
  }
97828
+ },
97829
+ {
97830
+ name: "editor",
97831
+ aliases: [],
97832
+ description: "Set the external editor for Ctrl-G",
97833
+ mode: "both",
97834
+ priority: 60,
97835
+ async execute(args, _ctx) {
97836
+ const trimmed = args.trim();
97837
+ if (trimmed.length === 0) return ok$1("__show_editor_picker__");
97838
+ return ok$1(`__set_editor__:${trimmed}`);
97839
+ }
97840
+ },
97841
+ {
97842
+ name: "theme",
97843
+ aliases: [],
97844
+ description: "Set the terminal UI theme",
97845
+ mode: "both",
97846
+ priority: 60,
97847
+ async execute(args) {
97848
+ const trimmed = args.trim();
97849
+ if (trimmed.length === 0) return ok$1("__show_theme_picker__");
97850
+ if (trimmed === "dark" || trimmed === "light" || trimmed === "auto") return ok$1(`__set_theme__:${trimmed}`);
97851
+ return ok$1(`Unknown theme: ${trimmed}`);
97852
+ }
97382
97853
  }
97383
97854
  ];
97384
97855
  //#endregion
@@ -98169,49 +98640,6 @@ var QuestionController = class extends ReverseRpcController {
98169
98640
  }
98170
98641
  };
98171
98642
  //#endregion
98172
- //#region src/tui/theme/colors.ts
98173
- const darkColors = {
98174
- primary: "#4FA8FF",
98175
- primaryDim: "#2563EB",
98176
- text: "#E0E0E0",
98177
- textDim: "#888888",
98178
- textMuted: "#555555",
98179
- success: "#4EC87E",
98180
- warning: "#E8A838",
98181
- error: "#E85454",
98182
- info: "#4FA8FF",
98183
- border: "#444444",
98184
- prompt: "#4FA8FF",
98185
- spinner: "#4FA8FF",
98186
- user: "#FFCB6B",
98187
- assistant: "#E0E0E0",
98188
- thinking: "#888888",
98189
- toolCall: "#E8A838",
98190
- status: "#888888"
98191
- };
98192
- const lightColors = {
98193
- primary: "#1783ff",
98194
- primaryDim: "#6ba7e8",
98195
- text: "#1A1A1A",
98196
- textDim: "#666666",
98197
- textMuted: "#999999",
98198
- success: "#16A34A",
98199
- warning: "#CA8A04",
98200
- error: "#DC2626",
98201
- info: "#1783ff",
98202
- border: "#CCCCCC",
98203
- prompt: "#1783ff",
98204
- spinner: "#1783ff",
98205
- user: "#B45309",
98206
- assistant: "#1A1A1A",
98207
- thinking: "#666666",
98208
- toolCall: "#CA8A04",
98209
- status: "#666666"
98210
- };
98211
- function getColorPalette(theme) {
98212
- return theme === "dark" ? darkColors : lightColors;
98213
- }
98214
- //#endregion
98215
98643
  //#region src/tui/state.ts
98216
98644
  const INITIAL_LIVE_PANE = {
98217
98645
  mode: "idle",
@@ -98228,7 +98656,9 @@ const INITIAL_LIVE_PANE = {
98228
98656
  */
98229
98657
  function createTUIState(options) {
98230
98658
  const initialAppState = options.initialAppState;
98231
- const colors = getColorPalette(initialAppState.theme);
98659
+ const resolvedTheme = options.resolvedTheme ?? resolveThemeSync(initialAppState.theme);
98660
+ const colors = { ...getColorPalette(resolvedTheme) };
98661
+ const styles = createThemeStyles(colors);
98232
98662
  const markdownTheme = createMarkdownTheme(colors);
98233
98663
  const editorTheme = createEditorTheme(colors);
98234
98664
  const terminal = new ProcessTerminal();
@@ -98261,10 +98691,13 @@ function createTUIState(options) {
98261
98691
  footer,
98262
98692
  editor,
98263
98693
  colors,
98694
+ styles,
98264
98695
  markdownTheme,
98696
+ editorTheme,
98697
+ resolvedTheme,
98265
98698
  appState: { ...initialAppState },
98266
98699
  startupState: "pending",
98267
- startupNotice: void 0,
98700
+ startupNotice: options.startup.startupNotice,
98268
98701
  livePane: { ...INITIAL_LIVE_PANE },
98269
98702
  transcriptEntries: [],
98270
98703
  toasts: [],
@@ -98439,6 +98872,9 @@ function dequeueFirst(state) {
98439
98872
  state.queuedMessages = state.queuedMessages.slice(1);
98440
98873
  return first.text;
98441
98874
  }
98875
+ function clearQueue(state) {
98876
+ state.queuedMessages.length = 0;
98877
+ }
98442
98878
  //#endregion
98443
98879
  //#region src/tui/actions/wire-ops.ts
98444
98880
  /** Immediately send a message through the wire client (no queue). */
@@ -98569,21 +99005,21 @@ function sendMessage(state, addEntry, input, options) {
98569
99005
  */
98570
99006
  function steerMessage(state, addEntry, input) {
98571
99007
  if (state.appState.isCompacting) {
98572
- enqueueMessage(state, input);
99008
+ for (const part of input) enqueueMessage(state, part);
98573
99009
  return;
98574
99010
  }
98575
99011
  if (!state.appState.isStreaming) {
98576
- sendMessageInternal(state, addEntry, input);
99012
+ for (const part of input) sendMessageInternal(state, addEntry, part);
98577
99013
  return;
98578
99014
  }
98579
- addEntry({
99015
+ for (const part of input) addEntry({
98580
99016
  id: nextTranscriptId(),
98581
99017
  kind: "user",
98582
99018
  turnId: state.currentTurnId,
98583
99019
  renderMode: "plain",
98584
- content: input
99020
+ content: part
98585
99021
  });
98586
- state.client.steer(state.appState.sessionId, input).catch((error) => {
99022
+ state.client.steer(state.appState.sessionId, input.join("\n\n")).catch((error) => {
98587
99023
  const message = error instanceof Error ? error.message : String(error);
98588
99024
  addEntry({
98589
99025
  id: nextTranscriptId(),
@@ -99150,7 +99586,7 @@ var CompactionComponent = class extends Container {
99150
99586
  }
99151
99587
  buildHeader() {
99152
99588
  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)`) : ""}`;
99153
- return `${this.blinkOn ? chalk.white("⏺ ") : " "}${chalk.hex(this.colors.primary).bold("Compacting context...")}`;
99589
+ return `${this.blinkOn ? chalk.hex(this.colors.roleAssistant)("⏺ ") : " "}${chalk.hex(this.colors.primary).bold("Compacting context...")}`;
99154
99590
  }
99155
99591
  startBlink() {
99156
99592
  this.blinkTimer = setInterval(() => {
@@ -99167,6 +99603,15 @@ var CompactionComponent = class extends Container {
99167
99603
  }
99168
99604
  };
99169
99605
  //#endregion
99606
+ //#region src/tui/actions/plan-ops.ts
99607
+ async function refreshPlanFromCore(state) {
99608
+ try {
99609
+ return await state.client.getPlan(state.appState.sessionId);
99610
+ } catch {
99611
+ return {};
99612
+ }
99613
+ }
99614
+ //#endregion
99170
99615
  //#region src/tui/actions/stream-ops.ts
99171
99616
  /**
99172
99617
  * Streaming / tool-call / compaction render hooks.
@@ -99176,7 +99621,7 @@ var CompactionComponent = class extends Container {
99176
99621
  * on `state` is touched — state-ops.ts owns AppState / LivePane.
99177
99622
  */
99178
99623
  function onStreamingTextStart(state) {
99179
- state.streamingComponent = new AssistantMessageComponent(state.markdownTheme);
99624
+ state.streamingComponent = new AssistantMessageComponent(state.markdownTheme, state.colors);
99180
99625
  state.transcriptContainer.addChild(state.streamingComponent);
99181
99626
  state.ui.requestRender();
99182
99627
  }
@@ -99209,6 +99654,10 @@ function onToolCallStart(state, toolCall) {
99209
99654
  state.pendingToolComponents.set(toolCall.id, tc);
99210
99655
  state.transcriptContainer.addChild(tc);
99211
99656
  state.ui.requestRender();
99657
+ if (toolCall.name === "ExitPlanMode" && typeof toolCall.args["plan"] !== "string") (async () => {
99658
+ const snapshot = await refreshPlanFromCore(state);
99659
+ tc.setPlanInfo(snapshot);
99660
+ })();
99212
99661
  }
99213
99662
  function onToolCallEnd(state, toolCallId, result) {
99214
99663
  const matchedCall = state.activeToolCalls.get(toolCallId);
@@ -99251,6 +99700,126 @@ function endCompaction(state, tokensBefore, tokensAfter) {
99251
99700
  state.ui.requestRender();
99252
99701
  }
99253
99702
  //#endregion
99703
+ //#region src/tui/components/dialogs/choice-picker.ts
99704
+ /**
99705
+ * ChoicePicker — modal single-select list for slash commands that ask
99706
+ * the user to pick from a small set of preset values.
99707
+ *
99708
+ * Mirrors SessionPickerComponent's container-replacement pattern: host
99709
+ * calls `showChoicePicker(...)` which clears the editor container,
99710
+ * addChild(picker), setFocus(picker); the picker invokes `onSelect` or
99711
+ * `onCancel`, and the host tears it down.
99712
+ */
99713
+ const CURRENT_MARK = "← current";
99714
+ var ChoicePickerComponent = class extends Container {
99715
+ focused = false;
99716
+ opts;
99717
+ selectedIndex;
99718
+ constructor(opts) {
99719
+ super();
99720
+ this.opts = opts;
99721
+ const currentIdx = opts.options.findIndex((o) => o.value === opts.currentValue);
99722
+ this.selectedIndex = Math.max(currentIdx, 0);
99723
+ }
99724
+ handleInput(data) {
99725
+ if (matchesKey(data, Key.escape)) {
99726
+ this.opts.onCancel();
99727
+ return;
99728
+ }
99729
+ if (matchesKey(data, Key.up)) {
99730
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
99731
+ return;
99732
+ }
99733
+ if (matchesKey(data, Key.down)) {
99734
+ this.selectedIndex = Math.min(this.opts.options.length - 1, this.selectedIndex + 1);
99735
+ return;
99736
+ }
99737
+ if (matchesKey(data, Key.enter)) {
99738
+ const chosen = this.opts.options[this.selectedIndex];
99739
+ if (chosen !== void 0) this.opts.onSelect(chosen.value);
99740
+ return;
99741
+ }
99742
+ }
99743
+ render(width) {
99744
+ const { colors } = this.opts;
99745
+ const hint = this.opts.hint ?? "↑↓ navigate · Enter select · Esc cancel";
99746
+ const lines = [
99747
+ chalk.hex(colors.primary)("─".repeat(width)),
99748
+ chalk.hex(colors.primary).bold(` ${this.opts.title}`),
99749
+ chalk.hex(colors.textMuted)(` ${hint}`),
99750
+ ""
99751
+ ];
99752
+ for (let i = 0; i < this.opts.options.length; i++) {
99753
+ const opt = this.opts.options[i];
99754
+ const isSelected = i === this.selectedIndex;
99755
+ const isCurrent = opt.value === this.opts.currentValue;
99756
+ const pointer = isSelected ? "❯" : " ";
99757
+ const labelStyle = isSelected ? chalk.hex(colors.primary).bold : chalk.hex(colors.text);
99758
+ let line = chalk.hex(isSelected ? colors.primary : colors.textDim)(` ${pointer} `);
99759
+ line += labelStyle(opt.label);
99760
+ if (isCurrent) line += " " + chalk.hex(colors.success)(CURRENT_MARK);
99761
+ lines.push(line);
99762
+ }
99763
+ lines.push("");
99764
+ lines.push(chalk.hex(colors.primary)("─".repeat(width)));
99765
+ return lines;
99766
+ }
99767
+ };
99768
+ //#endregion
99769
+ //#region src/tui/panels/theme-picker.ts
99770
+ const THEME_OPTIONS = [
99771
+ {
99772
+ value: "auto",
99773
+ label: "Auto (match terminal)"
99774
+ },
99775
+ {
99776
+ value: "dark",
99777
+ label: "Dark"
99778
+ },
99779
+ {
99780
+ value: "light",
99781
+ label: "Light"
99782
+ }
99783
+ ];
99784
+ function isTheme(value) {
99785
+ return value === "dark" || value === "light" || value === "auto";
99786
+ }
99787
+ function showThemePicker(state, hooks) {
99788
+ mountPanel(state, new ChoicePickerComponent({
99789
+ title: "Select theme",
99790
+ hint: "↑↓ navigate · Enter select · Esc cancel",
99791
+ options: [...THEME_OPTIONS],
99792
+ currentValue: state.appState.theme,
99793
+ colors: state.colors,
99794
+ onSelect: (value) => {
99795
+ closeThemePicker(state);
99796
+ if (isTheme(value)) applyThemeChoice(state, value, hooks);
99797
+ },
99798
+ onCancel: () => closeThemePicker(state)
99799
+ }));
99800
+ }
99801
+ function closeThemePicker(state) {
99802
+ unmountPanel(state);
99803
+ }
99804
+ async function applyThemeChoice(state, theme, hooks) {
99805
+ if (theme === state.appState.theme) {
99806
+ emitStatus(state, `Theme unchanged: "${theme}".`);
99807
+ return;
99808
+ }
99809
+ try {
99810
+ await saveTuiConfig({
99811
+ theme,
99812
+ editorCommand: state.appState.editorCommand
99813
+ });
99814
+ } catch (error) {
99815
+ emitStatus(state, `Failed to save theme: ${error instanceof Error ? error.message : String(error)}`, state.colors.error);
99816
+ return;
99817
+ }
99818
+ const resolved = await resolveTheme(theme);
99819
+ applyTheme(state, theme, hooks, resolved);
99820
+ emitStatus(state, `Theme set to "${theme}"${theme === "auto" ? ` (detected: ${resolved})` : ""}.`);
99821
+ }
99822
+ //#endregion
99254
99823
  //#region src/tui/commands/skill-commands.ts
99255
99824
  const SKILL_ACTIVATION_SENTINEL_PREFIX = "__activate_skill__:";
99256
99825
  async function fetchSkills(client, sessionId) {
@@ -99319,6 +99888,27 @@ async function tryDispatchSkill(client, sessionId, name, args) {
99319
99888
  }
99320
99889
  //#endregion
99321
99890
  //#region src/tui/commands/dispatch.ts
99891
+ /**
99892
+ * Slash-command dispatcher.
99893
+ *
99894
+ * 入口 `dispatchSlashCommand(input, state, buildCtx)` 替代旧
99895
+ * `KimiTUI.executeSlashCommand`。职责:
99896
+ *
99897
+ * 1. 通过 `parseSlashInput` 拆 `/name args`。
99898
+ * 2. 走 `state.registry.find(name)` 定位命令定义。
99899
+ * - 未命中:交给 `tryDispatchSkill` 做 skill 兜底。
99900
+ * - 命中:调用命令 `execute(args, ctx)` 拿到 `SlashCommandResult`。
99901
+ * 3. 解释 result:
99902
+ * - `type: 'exit'` → 调 ctx.stop。
99903
+ * - `type: 'reload'` → 调 ctx.performReload(action)。
99904
+ * - `type: 'ok'` + 哨兵字符串 `__show_help__ / __show_sessions__ /
99905
+ * __show_editor_picker__ / __show_theme_picker__ /
99906
+ * __show_model_picker__ / __show_usage__ /
99907
+ * __show_model_picker__:<alias> / __set_editor__:<cmd> /
99908
+ * __set_theme__:<theme> / __send_as_message__:<text>` →
99909
+ * 分发到 `ctx.showXxx / ctx.sendAsMessage`。
99910
+ * - 其它:把 `message` append 进 transcript。
99911
+ */
99322
99912
  async function dispatchSlashCommand(input, state, buildCtx, addEntry) {
99323
99913
  const parsed = parseSlashInput(input);
99324
99914
  if (!parsed) return false;
@@ -99379,6 +99969,10 @@ async function dispatchSlashCommand(input, state, buildCtx, addEntry) {
99379
99969
  ctx.showEditorPicker();
99380
99970
  return true;
99381
99971
  }
99972
+ if (result.message === "__show_theme_picker__") {
99973
+ ctx.showThemePicker();
99974
+ return true;
99975
+ }
99382
99976
  if (result.message === "__show_model_picker__") {
99383
99977
  ctx.showModelPicker();
99384
99978
  return true;
@@ -99392,6 +99986,18 @@ async function dispatchSlashCommand(input, state, buildCtx, addEntry) {
99392
99986
  ctx.showThinkingPicker(alias);
99393
99987
  return true;
99394
99988
  }
99989
+ if (result.message.startsWith("__set_editor__:")) {
99990
+ const command = result.message.slice(15);
99991
+ await ctx.setEditorCommand(command);
99992
+ return true;
99993
+ }
99994
+ if (result.message.startsWith("__set_theme__:")) {
99995
+ const theme = result.message.slice(14);
99996
+ if (isTheme(theme)) {
99997
+ await ctx.setTheme(theme);
99998
+ return true;
99999
+ }
100000
+ }
99395
100001
  if (result.message.startsWith("__send_as_message__:")) {
99396
100002
  const msg = result.message.slice(20);
99397
100003
  ctx.sendAsMessage(msg);
@@ -99770,72 +100376,6 @@ function dispatchEvent(event, ectx, sendQueued) {
99770
100376
  }
99771
100377
  }
99772
100378
  //#endregion
99773
- //#region src/tui/components/dialogs/choice-picker.ts
99774
- /**
99775
- * ChoicePicker — modal single-select list for slash commands that ask
99776
- * the user to pick from a small set of preset values.
99777
- *
99778
- * Mirrors SessionPickerComponent's container-replacement pattern: host
99779
- * calls `showChoicePicker(...)` which clears the editor container,
99780
- * addChild(picker), setFocus(picker); the picker invokes `onSelect` or
99781
- * `onCancel`, and the host tears it down.
99782
- */
99783
- const CURRENT_MARK = "← current";
99784
- var ChoicePickerComponent = class extends Container {
99785
- focused = false;
99786
- opts;
99787
- selectedIndex;
99788
- constructor(opts) {
99789
- super();
99790
- this.opts = opts;
99791
- const currentIdx = opts.options.findIndex((o) => o.value === opts.currentValue);
99792
- this.selectedIndex = Math.max(currentIdx, 0);
99793
- }
99794
- handleInput(data) {
99795
- if (matchesKey(data, Key.escape)) {
99796
- this.opts.onCancel();
99797
- return;
99798
- }
99799
- if (matchesKey(data, Key.up)) {
99800
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
99801
- return;
99802
- }
99803
- if (matchesKey(data, Key.down)) {
99804
- this.selectedIndex = Math.min(this.opts.options.length - 1, this.selectedIndex + 1);
99805
- return;
99806
- }
99807
- if (matchesKey(data, Key.enter)) {
99808
- const chosen = this.opts.options[this.selectedIndex];
99809
- if (chosen !== void 0) this.opts.onSelect(chosen.value);
99810
- return;
99811
- }
99812
- }
99813
- render(width) {
99814
- const { colors } = this.opts;
99815
- const hint = this.opts.hint ?? "↑↓ navigate · Enter select · Esc cancel";
99816
- const lines = [
99817
- chalk.hex(colors.primary)("─".repeat(width)),
99818
- chalk.hex(colors.primary).bold(` ${this.opts.title}`),
99819
- chalk.hex(colors.textMuted)(` ${hint}`),
99820
- ""
99821
- ];
99822
- for (let i = 0; i < this.opts.options.length; i++) {
99823
- const opt = this.opts.options[i];
99824
- const isSelected = i === this.selectedIndex;
99825
- const isCurrent = opt.value === this.opts.currentValue;
99826
- const pointer = isSelected ? "❯" : " ";
99827
- const labelStyle = isSelected ? chalk.hex(colors.primary).bold : chalk.hex(colors.text);
99828
- let line = chalk.hex(isSelected ? colors.primary : colors.textDim)(` ${pointer} `);
99829
- line += labelStyle(opt.label);
99830
- if (isCurrent) line += " " + chalk.hex(colors.success)(CURRENT_MARK);
99831
- lines.push(line);
99832
- }
99833
- lines.push("");
99834
- lines.push(chalk.hex(colors.primary)("─".repeat(width)));
99835
- return lines;
99836
- }
99837
- };
99838
- //#endregion
99839
100379
  //#region src/tui/panels/editor-picker.ts
99840
100380
  /**
99841
100381
  * `/editor` modal — picks an external editor command.
@@ -99880,12 +100420,22 @@ function showEditorPicker(state, hooks) {
99880
100420
  function closeEditorPicker(state) {
99881
100421
  unmountPanel(state);
99882
100422
  }
99883
- function applyEditorChoice(state, value, hooks) {
99884
- if (value === (state.appState.editorCommand ?? "")) {
100423
+ async function applyEditorChoice(state, value, hooks) {
100424
+ if (value === (state.appState.editorCommand ?? "") && value.length > 0) {
99885
100425
  emitStatus(state, `Editor unchanged: ${value.length > 0 ? value : "auto-detect"}`);
99886
100426
  return;
99887
100427
  }
99888
- setState(state, { editorCommand: value.length > 0 ? value : null }, hooks);
100428
+ const editorCommand = value.length > 0 ? value : null;
100429
+ try {
100430
+ await saveTuiConfig({
100431
+ theme: state.appState.theme,
100432
+ editorCommand
100433
+ });
100434
+ } catch (error) {
100435
+ emitStatus(state, `Failed to save editor: ${error instanceof Error ? error.message : String(error)}`, state.colors.error);
100436
+ return;
100437
+ }
100438
+ setState(state, { editorCommand }, hooks);
99889
100439
  emitStatus(state, value.length > 0 ? `Editor set to "${value}".` : "Editor set to auto-detect ($VISUAL / $EDITOR).");
99890
100440
  }
99891
100441
  //#endregion
@@ -100460,7 +101010,7 @@ var MoonLoader = class extends Text {
100460
101010
  this.ui = ui;
100461
101011
  this.frames = style === "moon" ? MOON_PHASES : BRAILLE_FRAMES;
100462
101012
  this.interval = style === "moon" ? MOON_INTERVAL : BRAILLE_INTERVAL;
100463
- if (colorFn !== void 0) this.colorFn = colorFn;
101013
+ this.colorFn = colorFn;
100464
101014
  this.label = label;
100465
101015
  this.start();
100466
101016
  }
@@ -100579,9 +101129,11 @@ function updateQueueDisplay(state) {
100579
101129
  state.queueContainer.clear();
100580
101130
  const queued = state.queuedMessages;
100581
101131
  if (queued.length === 0) return;
100582
- for (const item of queued) state.queueContainer.addChild(new Text(chalk.cyan.dim(` ❯ ${item.text}`), 0, 0));
101132
+ const accent = chalk.hex(state.colors.accent);
101133
+ const dim = chalk.hex(state.colors.textDim);
101134
+ for (const item of queued) state.queueContainer.addChild(new Text(accent(` ❯ ${item.text}`), 0, 0));
100583
101135
  const hint = state.appState.isCompacting && !state.appState.isStreaming ? " ↑ to edit · will send after compaction" : " ↑ to edit · ctrl-s to steer immediately";
100584
- state.queueContainer.addChild(new Text(chalk.dim(hint), 0, 0));
101136
+ state.queueContainer.addChild(new Text(dim(hint), 0, 0));
100585
101137
  }
100586
101138
  //#endregion
100587
101139
  //#region src/tui/reverse-rpc/approval/adapter.ts
@@ -100919,9 +101471,18 @@ function truncateOneLine(text, max) {
100919
101471
  }
100920
101472
  const DIFF_SUMMARY_MAX_LINES = 10;
100921
101473
  const CONTENT_SUMMARY_MAX_LINES = 10;
100922
- function renderDisplayBlock(block, expanded) {
101474
+ function makeBlockStyles(colors) {
101475
+ return {
101476
+ strong: (s) => chalk.hex(colors.textStrong)(s),
101477
+ dim: (s) => chalk.hex(colors.textDim)(s),
101478
+ accent: (s) => chalk.hex(colors.accent)(s),
101479
+ gutter: (s) => chalk.hex(colors.diffGutter)(s),
101480
+ errorBold: (s) => chalk.bold.hex(colors.error)(s)
101481
+ };
101482
+ }
101483
+ function renderDisplayBlock(block, expanded, s, colors) {
100923
101484
  switch (block.type) {
100924
- case "diff": return renderDiffLinesClustered(block.old_text, block.new_text, block.path, {
101485
+ case "diff": return renderDiffLinesClustered(block.old_text, block.new_text, block.path, colors, {
100925
101486
  contextLines: 3,
100926
101487
  ...expanded ? {} : { maxLines: DIFF_SUMMARY_MAX_LINES }
100927
101488
  });
@@ -100930,42 +101491,42 @@ function renderDisplayBlock(block, expanded) {
100930
101491
  const allLines = highlightLines(block.content, lang);
100931
101492
  const cap = expanded ? allLines.length : CONTENT_SUMMARY_MAX_LINES;
100932
101493
  const shown = allLines.slice(0, cap);
100933
- const lines = [chalk.whiteBright(block.path)];
100934
- for (const [i, line] of shown.entries()) lines.push(chalk.gray(String(i + 1).padStart(4) + " ") + line);
101494
+ const lines = [s.strong(block.path)];
101495
+ for (const [i, line] of shown.entries()) lines.push(s.gutter(String(i + 1).padStart(4) + " ") + line);
100935
101496
  const remaining = allLines.length - shown.length;
100936
- if (remaining > 0) lines.push(chalk.dim(` … ${String(remaining)} more line${remaining > 1 ? "s" : ""} hidden (ctrl+o to expand)`));
101497
+ if (remaining > 0) lines.push(s.dim(` … ${String(remaining)} more line${remaining > 1 ? "s" : ""} hidden (ctrl+o to expand)`));
100937
101498
  return lines;
100938
101499
  }
100939
101500
  case "shell": {
100940
101501
  const lines = [];
100941
- if (block.cwd !== void 0 && block.cwd.length > 0) lines.push(chalk.dim(`cwd: ${block.cwd}`));
100942
- if (block.danger !== void 0) lines.push(chalk.red.bold(`⚠ potentially destructive: ${block.danger}`));
101502
+ if (block.cwd !== void 0 && block.cwd.length > 0) lines.push(s.dim(`cwd: ${block.cwd}`));
101503
+ if (block.danger !== void 0) lines.push(s.errorBold(`⚠ potentially destructive: ${block.danger}`));
100943
101504
  (block.command.length > 0 ? block.command.split("\n") : [""]).forEach((cmdLine, idx) => {
100944
- const prefix = idx === 0 ? chalk.cyan("$") : chalk.dim("·");
100945
- lines.push(`${prefix} ${chalk.whiteBright(cmdLine)}`);
101505
+ const prefix = idx === 0 ? s.accent("$") : s.dim("·");
101506
+ lines.push(`${prefix} ${s.strong(cmdLine)}`);
100946
101507
  });
100947
- if (block.description !== void 0 && block.description.length > 0) lines.push(` ${chalk.dim(block.description)}`);
101508
+ if (block.description !== void 0 && block.description.length > 0) lines.push(` ${s.dim(block.description)}`);
100948
101509
  return lines;
100949
101510
  }
100950
101511
  case "file_op": {
100951
- const lines = [`${chalk.cyan(block.operation.padEnd(5))} ${chalk.whiteBright(block.path)}`];
100952
- if (block.detail !== void 0 && block.detail.length > 0) lines.push(chalk.dim(block.detail));
101512
+ const lines = [`${s.accent(block.operation.padEnd(5))} ${s.strong(block.path)}`];
101513
+ if (block.detail !== void 0 && block.detail.length > 0) lines.push(s.dim(block.detail));
100953
101514
  return lines;
100954
101515
  }
100955
- case "url_fetch": return [`${chalk.cyan((block.method ?? "GET").toUpperCase().padEnd(5))} ${chalk.whiteBright(block.url)}`];
101516
+ case "url_fetch": return [`${s.accent((block.method ?? "GET").toUpperCase().padEnd(5))} ${s.strong(block.url)}`];
100956
101517
  case "search": {
100957
- const lines = [`${chalk.cyan("search")} ${chalk.whiteBright(block.query)}`];
100958
- if (block.scope !== void 0 && block.scope.length > 0) lines.push(chalk.dim(`scope: ${block.scope}`));
101518
+ const lines = [`${s.accent("search")} ${s.strong(block.query)}`];
101519
+ if (block.scope !== void 0 && block.scope.length > 0) lines.push(s.dim(`scope: ${block.scope}`));
100959
101520
  return lines;
100960
101521
  }
100961
101522
  case "invocation": {
100962
- const lines = [`${chalk.cyan(block.kind.padEnd(5))} ${chalk.whiteBright(block.name)}`];
100963
- if (block.description !== void 0 && block.description.length > 0) lines.push(chalk.dim(truncateOneLine(block.description, 200)));
101523
+ const lines = [`${s.accent(block.kind.padEnd(5))} ${s.strong(block.name)}`];
101524
+ if (block.description !== void 0 && block.description.length > 0) lines.push(s.dim(truncateOneLine(block.description, 200)));
100964
101525
  return lines;
100965
101526
  }
100966
- case "brief": return block.text ? block.text.split("\n").map((line) => line.length > 0 ? chalk.whiteBright(line) : "") : [];
100967
- case "background_task": return [chalk.whiteBright(`${block.status} ${block.kind} task ${block.task_id}: ${block.description}`)];
100968
- case "todo": return block.items.map((item) => chalk.whiteBright(`- [${item.status}] ${item.title}`));
101527
+ case "brief": return block.text ? block.text.split("\n").map((line) => line.length > 0 ? s.strong(line) : "") : [];
101528
+ case "background_task": return [s.strong(`${block.status} ${block.kind} task ${block.task_id}: ${block.description}`)];
101529
+ case "todo": return block.items.map((item) => s.strong(`- [${item.status}] ${item.title}`));
100969
101530
  default: return [];
100970
101531
  }
100971
101532
  }
@@ -100982,8 +101543,6 @@ function isDuplicateBriefBlock(block, description) {
100982
101543
  if (blockLines.length <= 1) return false;
100983
101544
  return normalizeApprovalText(blockLines.slice(1).join("\n")) === normalizedDescription;
100984
101545
  }
100985
- const borderColor = chalk.yellow;
100986
- const selectColor = chalk.cyan;
100987
101546
  function headerFor(toolName) {
100988
101547
  switch (toolName) {
100989
101548
  case "Bash": return "Run this command?";
@@ -101002,10 +101561,12 @@ var ApprovalPanelComponent = class extends Container {
101002
101561
  expanded = false;
101003
101562
  onResponse;
101004
101563
  request;
101005
- constructor(request, onResponse) {
101564
+ colors;
101565
+ constructor(request, onResponse, colors) {
101006
101566
  super();
101007
101567
  this.request = request;
101008
101568
  this.onResponse = onResponse;
101569
+ this.colors = colors;
101009
101570
  this.feedbackInput.onSubmit = (value) => {
101010
101571
  this.submit(this.selectedIndex, value);
101011
101572
  };
@@ -101076,21 +101637,27 @@ var ApprovalPanelComponent = class extends Container {
101076
101637
  this.ensureValidSelection();
101077
101638
  this.feedbackInput.focused = this.focused && this.feedbackMode;
101078
101639
  const { data } = this.request;
101640
+ const blockStyles = makeBlockStyles(this.colors);
101641
+ const borderColor = chalk.hex(this.colors.borderFocus);
101642
+ const borderColorBold = chalk.bold.hex(this.colors.borderFocus);
101643
+ const selectColorBold = chalk.bold.hex(this.colors.accent);
101644
+ const dim = chalk.hex(this.colors.textDim);
101645
+ const strong = chalk.hex(this.colors.textStrong);
101079
101646
  const horizontalBar = borderColor("─".repeat(width));
101080
101647
  const indent = (s) => ` ${s}`;
101081
101648
  const title = headerFor(data.tool_name);
101082
- const lines = [horizontalBar, indent(`${borderColor.bold("▶")} ${borderColor.bold(title)}`)];
101649
+ const lines = [horizontalBar, indent(`${borderColorBold("▶")} ${borderColorBold(title)}`)];
101083
101650
  const visibleBlocks = data.display.filter((block) => !isDuplicateBriefBlock(block, data.description)).slice(0, 5);
101084
101651
  const hasExpandable = visibleBlocks.some((block) => block.type === "diff" || block.type === "file_content");
101085
101652
  if (visibleBlocks.length > 0) {
101086
101653
  lines.push("");
101087
101654
  for (const block of visibleBlocks) {
101088
- const blockLines = renderDisplayBlock(block, this.expanded);
101655
+ const blockLines = renderDisplayBlock(block, this.expanded, blockStyles, this.colors);
101089
101656
  for (const line of blockLines) lines.push(indent(line));
101090
101657
  }
101091
101658
  } else if (data.description) {
101092
101659
  lines.push("");
101093
- for (const descLine of data.description.split("\n")) lines.push(indent(chalk.dim(descLine)));
101660
+ for (const descLine of data.description.split("\n")) lines.push(indent(dim(descLine)));
101094
101661
  }
101095
101662
  lines.push("");
101096
101663
  for (let idx = 0; idx < data.choices.length; idx++) {
@@ -101100,14 +101667,14 @@ var ApprovalPanelComponent = class extends Container {
101100
101667
  const num = idx + 1;
101101
101668
  const labelWithNum = `${String(num)}. ${option.label}`;
101102
101669
  if (this.feedbackMode && option.requires_feedback === true && isSelected) lines.push(indent(this.renderInlineFeedbackLine(width - 2, labelWithNum)));
101103
- else if (isSelected) lines.push(indent(`${selectColor.bold("▶")} ${selectColor.bold(labelWithNum)}`));
101104
- else lines.push(indent(chalk.whiteBright(` ${labelWithNum}`)));
101670
+ else if (isSelected) lines.push(indent(`${selectColorBold("▶")} ${selectColorBold(labelWithNum)}`));
101671
+ else lines.push(indent(strong(` ${labelWithNum}`)));
101105
101672
  }
101106
101673
  lines.push("");
101107
- if (this.feedbackMode) lines.push(indent(chalk.dim("Type feedback · ↵ submit.")));
101674
+ if (this.feedbackMode) lines.push(indent(dim("Type feedback · ↵ submit.")));
101108
101675
  else {
101109
101676
  const expandHint = hasExpandable ? ` · ctrl+o ${this.expanded ? "collapse" : "expand"}` : "";
101110
- lines.push(indent(chalk.dim(`↑/↓ select · ${buildNumericHint(data.choices.length)} choose · ↵ confirm${expandHint}`)));
101677
+ lines.push(indent(dim(`↑/↓ select · ${buildNumericHint(data.choices.length)} choose · ↵ confirm${expandHint}`)));
101111
101678
  }
101112
101679
  lines.push(horizontalBar);
101113
101680
  return lines.map((line) => truncateToWidth(line, width));
@@ -101127,7 +101694,8 @@ var ApprovalPanelComponent = class extends Container {
101127
101694
  if (this.selectedIndex < 0 || this.selectedIndex >= count) this.selectedIndex = Math.max(0, Math.min(this.selectedIndex, count - 1));
101128
101695
  }
101129
101696
  renderInlineFeedbackLine(width, labelWithNum) {
101130
- const prefix = `${selectColor.bold("▶")} ${selectColor.bold(labelWithNum)} `;
101697
+ const selectColorBold = chalk.bold.hex(this.colors.accent);
101698
+ const prefix = `${selectColorBold("▶")} ${selectColorBold(labelWithNum)} `;
101131
101699
  const inputWidth = Math.max(4, width - visibleWidth(prefix) + 2);
101132
101700
  const inputLine = this.feedbackInput.render(inputWidth)[0] ?? "> ";
101133
101701
  return prefix + (inputLine.startsWith("> ") ? inputLine.slice(2) : inputLine);
@@ -101148,7 +101716,7 @@ function showApprovalPanel(state, payload) {
101148
101716
  updateActivityPane(state);
101149
101717
  mountPanel(state, new ApprovalPanelComponent({ data: payload }, (response) => {
101150
101718
  state.approvalController.respond(adaptPanelResponse(response));
101151
- }));
101719
+ }, state.colors));
101152
101720
  }
101153
101721
  function hideApprovalPanel(state) {
101154
101722
  state.livePane.pendingApproval = null;
@@ -101601,7 +102169,7 @@ var QuestionDialogComponent = class extends Container {
101601
102169
  if (question === void 0) continue;
101602
102170
  const label = question.header !== void 0 && question.header.length > 0 ? question.header : `Q${String(i + 1)}`;
101603
102171
  if (i === this.currentTab) tabs.push(active(` ${label} `));
101604
- else if (this.isAnswered(i)) tabs.push(chalk.green(`(✓) ${label}`));
102172
+ else if (this.isAnswered(i)) tabs.push(chalk.hex(this.colors.success)(`(✓) ${label}`));
101605
102173
  else tabs.push(dim(`(○) ${label}`));
101606
102174
  }
101607
102175
  const submitLabel = "Submit";
@@ -102362,12 +102930,17 @@ function setupEditor(state, cb) {
102362
102930
  editor.onCtrlS = () => {
102363
102931
  if (!state.appState.isStreaming || state.appState.isCompacting) return;
102364
102932
  const text = editor.getText().trim();
102365
- if (text.length > 0) {
102933
+ const queuedTexts = state.queuedMessages.map((m) => m.text);
102934
+ clearQueue(state);
102935
+ const parts = [];
102936
+ for (const q of queuedTexts) {
102937
+ const trimmed = q.trim();
102938
+ if (trimmed.length > 0) parts.push(trimmed);
102939
+ }
102940
+ if (text.length > 0) parts.push(text);
102941
+ if (parts.length > 0) {
102366
102942
  editor.setText("");
102367
- cb.onSteerFromInput(text);
102368
- } else {
102369
- const first = cb.onSteerDequeueFirst();
102370
- if (first !== void 0) cb.onSteerFromInput(first);
102943
+ cb.onSteerFromInput(parts);
102371
102944
  }
102372
102945
  cb.onAfterQueueMutation();
102373
102946
  state.ui.requestRender();
@@ -102507,7 +103080,8 @@ var KimiTUI = class {
102507
103080
  this.state = createTUIState({
102508
103081
  client,
102509
103082
  initialAppState: initialState,
102510
- startup: options.startup
103083
+ startup: options.startup,
103084
+ ...options.resolvedTheme !== void 0 ? { resolvedTheme: options.resolvedTheme } : {}
102511
103085
  });
102512
103086
  this.stateHooks = {
102513
103087
  syncFooter: () => this.state.footer.setState(this.state.appState),
@@ -102557,14 +103131,14 @@ var KimiTUI = class {
102557
103131
  this.state.ui.setFocus(this.state.editor);
102558
103132
  this.state.ui.start();
102559
103133
  attachFooterFeed(this.state, buildFooterFeedHandlers(this.state));
102560
- if (this.state.startupState === "picker") {
102561
- bootstrapFromPicker(this.state, this.buildSessionBootstrapHooks());
102562
- return;
102563
- }
102564
103134
  if (this.state.startupNotice !== void 0) {
102565
103135
  emitMuted(this.state, this.state.startupNotice);
102566
103136
  this.state.startupNotice = void 0;
102567
103137
  }
103138
+ if (this.state.startupState === "picker") {
103139
+ bootstrapFromPicker(this.state, this.buildSessionBootstrapHooks());
103140
+ return;
103141
+ }
102568
103142
  if (shouldReplayHistory) await hydrateTranscriptFromReplay(this.state, this.stateHooks, this.state.appState.sessionId);
102569
103143
  this.startWireSubscription();
102570
103144
  fetchSessions(this.state);
@@ -102729,11 +103303,14 @@ var KimiTUI = class {
102729
103303
  showHelpPanel: () => showHelpPanel(this.state),
102730
103304
  showSessionPicker: () => showSessionPicker(this.state, this.buildSessionHooks()),
102731
103305
  showEditorPicker: () => showEditorPicker(this.state, this.stateHooks),
103306
+ showThemePicker: () => showThemePicker(this.state, this.stateHooks),
102732
103307
  showModelPicker: () => showModelPicker(this.state, this.stateHooks),
102733
103308
  showThinkingPicker: (alias) => showThinkingPicker(this.state, alias, this.stateHooks),
102734
103309
  showUsage: () => {
102735
103310
  showUsage(this.state);
102736
103311
  },
103312
+ setEditorCommand: (command) => applyEditorChoice(this.state, command, this.stateHooks),
103313
+ setTheme: (theme) => applyThemeChoice(this.state, theme, this.stateHooks),
102737
103314
  sendAsMessage: (text) => sendMessage(this.state, (e) => this.addEntry(e), text),
102738
103315
  activateSkill: (name, args, fullPrompt) => sendSkillActivation(this.state, (e) => this.addEntry(e), name, args, fullPrompt),
102739
103316
  performReload: (action) => performReload(this.state, action, this.buildInputHooks())
@@ -102767,6 +103344,16 @@ function toInitialSessionIntent(opts) {
102767
103344
  }
102768
103345
  async function runShell(opts, version) {
102769
103346
  const ctx = await createKimiAgent();
103347
+ let tuiConfig;
103348
+ let configWarning;
103349
+ try {
103350
+ tuiConfig = await loadTuiConfig();
103351
+ } catch (error) {
103352
+ if (!(error instanceof TuiConfigParseError)) throw error;
103353
+ tuiConfig = error.fallback;
103354
+ configWarning = error.message;
103355
+ }
103356
+ const resolvedTheme = tuiConfig.theme === "auto" ? await detectTerminalTheme() : tuiConfig.theme;
102770
103357
  const workDir = process.cwd();
102771
103358
  const initialState = {
102772
103359
  model: ctx.model,
@@ -102783,18 +103370,22 @@ async function runShell(opts, version) {
102783
103370
  isReplaying: false,
102784
103371
  streamingPhase: "idle",
102785
103372
  streamingStartTime: 0,
102786
- theme: "dark",
103373
+ theme: tuiConfig.theme,
102787
103374
  version,
102788
- editorCommand: null,
103375
+ editorCommand: tuiConfig.editorCommand,
102789
103376
  availableModels: ctx.availableModels,
102790
103377
  sessionTitle: null
102791
103378
  };
102792
- const tui = new KimiTUI(ctx.client, initialState, { startup: {
102793
- initialSession: toInitialSessionIntent(opts),
102794
- requestedPlanMode: opts.plan,
102795
- requestedYolo: opts.yolo,
102796
- syncSessionRuntime: ctx.syncSessionRuntime
102797
- } });
103379
+ const tui = new KimiTUI(ctx.client, initialState, {
103380
+ startup: {
103381
+ initialSession: toInitialSessionIntent(opts),
103382
+ requestedPlanMode: opts.plan,
103383
+ requestedYolo: opts.yolo,
103384
+ startupNotice: configWarning,
103385
+ syncSessionRuntime: ctx.syncSessionRuntime
103386
+ },
103387
+ resolvedTheme
103388
+ });
102798
103389
  tui.onExit = async () => {
102799
103390
  const sessionId = tui.getCurrentSessionId();
102800
103391
  const hasContent = tui.hasSessionContent();