@moonshot-ai/kimi-code 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.mjs CHANGED
@@ -22,6 +22,7 @@ import * as fs$10 from "node:fs";
22
22
  import Vt, { appendFileSync, chmodSync, closeSync, constants, createReadStream, createWriteStream as createWriteStream$1, existsSync, fsyncSync, mkdirSync, openSync, promises, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
23
23
  import * as path$10 from "node:path";
24
24
  import path$1, { basename as basename$1, dirname as dirname$2, extname, isAbsolute as isAbsolute$1, join as join$1, posix, relative as relative$1, resolve as resolve$1, sep, win32 } from "node:path";
25
+ import { Blob as Blob$1, Buffer as Buffer$2, File as File$1 } from "node:buffer";
25
26
  import jn, { access, appendFile, chmod, copyFile, cp, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, unlink, utimes, writeFile as writeFile$1 } from "node:fs/promises";
26
27
  import { z } from "zod";
27
28
  import { execFile, execFileSync, execSync, spawn, spawnSync } from "node:child_process";
@@ -36,18 +37,17 @@ import { EventEmitter as EventEmitter$1 } from "node:events";
36
37
  import { StringDecoder } from "node:string_decoder";
37
38
  import Mi from "assert";
38
39
  import so from "node:assert";
39
- import { parse as parse$2, stringify } from "smol-toml";
40
+ import { TomlError, parse as parse$2, stringify } from "smol-toml";
40
41
  import "zod/v3";
41
42
  import * as z4mini from "zod/v4-mini";
42
43
  import { fileURLToPath, pathToFileURL } from "node:url";
43
- import { Buffer as Buffer$2 } from "node:buffer";
44
44
  import { AsyncLocalStorage } from "node:async_hooks";
45
45
  import process$1 from "node:process";
46
- import { CURSOR_MARKER, CombinedAutocompleteProvider, Container, Editor, Image, Input, Key, Markdown, ProcessTerminal, Spacer, TUI, Text, decodeKittyPrintable, deleteAllKittyImages, fuzzyFilter, getCapabilities, isKeyRelease, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
46
+ import { CURSOR_MARKER, CombinedAutocompleteProvider, Container, Editor, Image, Input, Key, Markdown, ProcessTerminal, SelectList, Spacer, TUI, Text, decodeKittyPrintable, deleteAllKittyImages, fuzzyFilter, fuzzyMatch, getCapabilities, isKeyRelease, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
47
47
  import chalk, { chalkStderr } from "chalk";
48
48
  import { Command, Option } from "commander";
49
49
  import { createInterface } from "node:readline/promises";
50
- import { gt, valid } from "semver";
50
+ import { gt, gte, valid } from "semver";
51
51
  import { highlight, supportsLanguage } from "cli-highlight";
52
52
  import { clearLine, cursorTo, emitKeypressEvents, moveCursor } from "node:readline";
53
53
  //#region \0rolldown/runtime.js
@@ -8742,6 +8742,10 @@ function isToolResultOnly(message) {
8742
8742
  if (!Array.isArray(content) || content.length === 0) return false;
8743
8743
  return content.every((block) => block.type === "tool_result");
8744
8744
  }
8745
+ const OMITTED_MEDIA_PLACEHOLDER = {
8746
+ audio_url: "(audio omitted: not supported by this provider)",
8747
+ video_url: "(video omitted: not supported by this provider)"
8748
+ };
8745
8749
  const SUPPORTED_B64_MEDIA_TYPES = new Set([
8746
8750
  "image/png",
8747
8751
  "image/jpeg",
@@ -8787,6 +8791,14 @@ function toolResultToBlock(toolCallId, content) {
8787
8791
  text: part.text
8788
8792
  });
8789
8793
  } else if (part.type === "image_url") blocks.push(imageUrlPartToAnthropic(part.imageUrl.url));
8794
+ else if (part.type === "audio_url" || part.type === "video_url") {
8795
+ const placeholder = OMITTED_MEDIA_PLACEHOLDER[part.type];
8796
+ const last = blocks.at(-1);
8797
+ if (!(last?.type === "text" && last.text === placeholder)) blocks.push({
8798
+ type: "text",
8799
+ text: placeholder
8800
+ });
8801
+ }
8790
8802
  return {
8791
8803
  type: "tool_result",
8792
8804
  tool_use_id: toolCallId,
@@ -8825,6 +8837,13 @@ function convertMessage$3(message, model) {
8825
8837
  type: "thinking",
8826
8838
  thinking: part.think
8827
8839
  });
8840
+ } else if (part.type === "audio_url" || part.type === "video_url") {
8841
+ const placeholder = OMITTED_MEDIA_PLACEHOLDER[part.type];
8842
+ const last = blocks.at(-1);
8843
+ if (!(last?.type === "text" && last.text === placeholder)) blocks.push({
8844
+ type: "text",
8845
+ text: placeholder
8846
+ });
8828
8847
  }
8829
8848
  if (message.toolCalls.length > 0) for (const tc of message.toolCalls) {
8830
8849
  let toolInput = {};
@@ -10319,7 +10338,7 @@ var require_gaxios = /* @__PURE__ */ __commonJSMin(((exports) => {
10319
10338
  }
10320
10339
  static async #getFetch() {
10321
10340
  const hasWindow = typeof window !== "undefined" && !!window;
10322
- this.#fetch ||= hasWindow ? window.fetch : (await import("./src-CeUdnMA4.mjs")).default;
10341
+ this.#fetch ||= hasWindow ? window.fetch : (await import("./src-D5Ej2mZZ.mjs")).default;
10323
10342
  return this.#fetch;
10324
10343
  }
10325
10344
  /**
@@ -45333,6 +45352,16 @@ function normalizeOpenAIFinishReason(raw) {
45333
45352
  }
45334
45353
  }
45335
45354
  /**
45355
+ * Shared wording for tool-result media that cannot live inside the tool
45356
+ * message itself and is reattached as a follow-up user message instead.
45357
+ */
45358
+ const TOOL_RESULT_MEDIA_PROMPT = "Attached media from tool result:";
45359
+ const TOOL_RESULT_MEDIA_PLACEHOLDER = "(see attached media)";
45360
+ /** A content part that is neither plain text nor reasoning. */
45361
+ function isMediaPart(part) {
45362
+ return part.type !== "text" && part.type !== "think";
45363
+ }
45364
+ /**
45336
45365
  * Convert tool-role message content according to the chosen strategy.
45337
45366
  */
45338
45367
  function convertToolMessageContent(message, conversion) {
@@ -45389,14 +45418,11 @@ var KimiFiles = class {
45389
45418
  const mimeType = guessMimeTypeFromExt(filename);
45390
45419
  if (mimeType === void 0 || !mimeType.startsWith("video/")) throw new ChatProviderError(`KimiFiles.uploadVideo: file extension does not indicate a video type: ${filename}`);
45391
45420
  const data = await fs$10.promises.readFile(input);
45392
- const blob = new Blob([new Uint8Array(data)], { type: mimeType });
45393
- file = new File([blob], filename, { type: mimeType });
45421
+ file = new File$1([new Blob$1([new Uint8Array(data)], { type: mimeType })], filename, { type: mimeType });
45394
45422
  } else {
45395
45423
  if (!input.mimeType.startsWith("video/")) throw new ChatProviderError(`Expected a video mime type, got ${input.mimeType}`);
45396
45424
  const filename = input.filename ?? guessFilename(input.mimeType);
45397
- const bytes = input.data instanceof Uint8Array ? input.data : new Uint8Array(input.data);
45398
- const blob = new Blob([bytes], { type: input.mimeType });
45399
- file = new File([blob], filename, { type: input.mimeType });
45425
+ file = new File$1([new Blob$1([input.data instanceof Uint8Array ? input.data : new Uint8Array(input.data)], { type: input.mimeType })], filename, { type: input.mimeType });
45400
45426
  }
45401
45427
  let uploaded;
45402
45428
  try {
@@ -45840,8 +45866,6 @@ const OPENAI_CHAT_TOOL_CALL_ID_POLICY = {
45840
45866
  normalize: (id) => sanitizeToolCallId(id, 64),
45841
45867
  maxLength: 64
45842
45868
  };
45843
- const TOOL_RESULT_IMAGE_PROMPT = "Attached image(s) from tool result:";
45844
- const TOOL_RESULT_IMAGE_PLACEHOLDER = "(see attached image)";
45845
45869
  function extractReasoningContent(source, explicitKey) {
45846
45870
  if (typeof source !== "object" || source === null) return void 0;
45847
45871
  const record = source;
@@ -45898,45 +45922,46 @@ function convertMessage$1(message, reasoningKey, toolMessageConversion) {
45898
45922
  if (reasoningContent) result[reasoningKey ?? DEFAULT_OUTBOUND_REASONING_KEY] = reasoningContent;
45899
45923
  return result;
45900
45924
  }
45925
+ const OMITTED_AUDIO_PLACEHOLDER$1 = "(audio omitted: not supported by this provider)";
45926
+ const OMITTED_VIDEO_PLACEHOLDER$1 = "(video omitted: not supported by this provider)";
45901
45927
  function convertToolMessageContentForChat(message, conversion) {
45902
45928
  const content = convertToolMessageContent(message, conversion);
45903
- if (typeof content === "string" && content.length === 0 && message.content.some((part) => part.type === "image_url")) return TOOL_RESULT_IMAGE_PLACEHOLDER;
45904
- return content;
45929
+ if (typeof content !== "string") return content;
45930
+ const lines = content.length > 0 ? [content] : [];
45931
+ if (message.content.some((part) => part.type === "audio_url")) lines.push(OMITTED_AUDIO_PLACEHOLDER$1);
45932
+ if (message.content.some((part) => part.type === "video_url")) lines.push(OMITTED_VIDEO_PLACEHOLDER$1);
45933
+ if (lines.length === 0 && message.content.some((part) => part.type === "image_url")) return TOOL_RESULT_MEDIA_PLACEHOLDER;
45934
+ return lines.join("\n");
45905
45935
  }
45906
45936
  function toolResultImageParts(message) {
45907
45937
  const images = [];
45908
45938
  for (const part of message.content) {
45909
45939
  if (part.type !== "image_url") continue;
45910
- images.push({
45911
- type: "image_url",
45912
- image_url: part.imageUrl.id === void 0 ? { url: part.imageUrl.url } : {
45913
- url: part.imageUrl.url,
45914
- id: part.imageUrl.id
45915
- }
45916
- });
45940
+ const converted = convertContentPart(part);
45941
+ if (converted !== null) images.push(converted);
45917
45942
  }
45918
45943
  return images;
45919
45944
  }
45920
- function appendToolResultImagesMessage(messages, pendingToolResultImages) {
45921
- if (pendingToolResultImages.length === 0) return;
45945
+ function appendToolResultMediaMessage(messages, pendingToolResultMedia) {
45946
+ if (pendingToolResultMedia.length === 0) return;
45922
45947
  messages.push({
45923
45948
  role: "user",
45924
45949
  content: [{
45925
45950
  type: "text",
45926
- text: TOOL_RESULT_IMAGE_PROMPT
45927
- }, ...pendingToolResultImages]
45951
+ text: TOOL_RESULT_MEDIA_PROMPT
45952
+ }, ...pendingToolResultMedia]
45928
45953
  });
45929
- pendingToolResultImages.length = 0;
45954
+ pendingToolResultMedia.length = 0;
45930
45955
  }
45931
- function convertHistoryMessages(history, reasoningKey, toolMessageConversion) {
45956
+ function convertHistoryMessages$1(history, reasoningKey, toolMessageConversion) {
45932
45957
  const messages = [];
45933
- const pendingToolResultImages = [];
45958
+ const pendingToolResultMedia = [];
45934
45959
  for (const msg of history) {
45935
- if (msg.role !== "tool") appendToolResultImagesMessage(messages, pendingToolResultImages);
45960
+ if (msg.role !== "tool") appendToolResultMediaMessage(messages, pendingToolResultMedia);
45936
45961
  messages.push(convertMessage$1(msg, reasoningKey, toolMessageConversion));
45937
- if (msg.role === "tool") pendingToolResultImages.push(...toolResultImageParts(msg));
45962
+ if (msg.role === "tool") pendingToolResultMedia.push(...toolResultImageParts(msg));
45938
45963
  }
45939
- appendToolResultImagesMessage(messages, pendingToolResultImages);
45964
+ appendToolResultMediaMessage(messages, pendingToolResultMedia);
45940
45965
  return messages;
45941
45966
  }
45942
45967
  var OpenAILegacyStreamedMessage = class {
@@ -46074,7 +46099,7 @@ var OpenAILegacyChatProvider = class {
46074
46099
  content: systemPrompt
46075
46100
  });
46076
46101
  const normalizedHistory = normalizeToolCallIdsForProvider(history, OPENAI_CHAT_TOOL_CALL_ID_POLICY);
46077
- messages.push(...convertHistoryMessages(normalizedHistory, this._reasoningKey, this._toolMessageConversion));
46102
+ messages.push(...convertHistoryMessages$1(normalizedHistory, this._reasoningKey, this._toolMessageConversion));
46078
46103
  const kwargs = normalizeGenerationKwargs(this._model, this._generationKwargs);
46079
46104
  let reasoningEffort = this._reasoningEffort;
46080
46105
  if (reasoningEffort === void 0 && kwargs["reasoning_effort"] === void 0) {
@@ -46310,6 +46335,8 @@ function formatResponsesFailedResponse(response) {
46310
46335
  const reason = incompleteDetails === void 0 ? void 0 : readStringField(incompleteDetails, "reason");
46311
46336
  return reason === void 0 ? "Unknown error (no error details in response)" : `incomplete: ${reason}`;
46312
46337
  }
46338
+ const OMITTED_AUDIO_PLACEHOLDER = "(audio omitted: unsupported audio format)";
46339
+ const OMITTED_VIDEO_PLACEHOLDER = "(video omitted: not supported by this provider)";
46313
46340
  function contentPartsToInputItems(parts) {
46314
46341
  const items = [];
46315
46342
  for (const part of parts) switch (part.type) {
@@ -46328,11 +46355,19 @@ function contentPartsToInputItems(parts) {
46328
46355
  break;
46329
46356
  case "audio_url": {
46330
46357
  const mapped = mapAudioUrlToInputItem(part.audioUrl.url);
46331
- if (mapped !== null) items.push(mapped);
46358
+ items.push(mapped ?? {
46359
+ type: "input_text",
46360
+ text: OMITTED_AUDIO_PLACEHOLDER
46361
+ });
46332
46362
  break;
46333
46363
  }
46334
- case "think":
46335
- case "video_url": break;
46364
+ case "video_url":
46365
+ items.push({
46366
+ type: "input_text",
46367
+ text: OMITTED_VIDEO_PLACEHOLDER
46368
+ });
46369
+ break;
46370
+ case "think": break;
46336
46371
  }
46337
46372
  return items;
46338
46373
  }
@@ -46362,11 +46397,19 @@ function messageContentToFunctionOutputItems(content) {
46362
46397
  break;
46363
46398
  case "audio_url": {
46364
46399
  const mapped = mapAudioUrlToInputItem(part.audioUrl.url);
46365
- if (mapped !== null) items.push(mapped);
46400
+ items.push(mapped ?? {
46401
+ type: "input_text",
46402
+ text: OMITTED_AUDIO_PLACEHOLDER
46403
+ });
46366
46404
  break;
46367
46405
  }
46368
- case "think":
46369
- case "video_url": break;
46406
+ case "video_url":
46407
+ items.push({
46408
+ type: "input_text",
46409
+ text: OMITTED_VIDEO_PLACEHOLDER
46410
+ });
46411
+ break;
46412
+ case "think": break;
46370
46413
  }
46371
46414
  return items;
46372
46415
  }
@@ -46399,11 +46442,19 @@ function mapAudioUrlToInputItem(url) {
46399
46442
  function convertMessage(message, modelName, toolMessageConversion) {
46400
46443
  let role = message.role;
46401
46444
  if (usesOpenAIResponsesDeveloperRole(modelName) && role === "system") role = "developer";
46402
- if (role === "tool") return [{
46403
- call_id: message.toolCallId ?? "",
46404
- output: toolMessageConversion === "extract_text" ? extractText(message) : messageContentToFunctionOutputItems(message.content),
46405
- type: "function_call_output"
46406
- }];
46445
+ if (role === "tool") {
46446
+ const callId = message.toolCallId ?? "";
46447
+ let output;
46448
+ if (toolMessageConversion === "extract_text") {
46449
+ const text = extractText(message);
46450
+ output = text.length === 0 && message.content.some(isMediaPart) ? TOOL_RESULT_MEDIA_PLACEHOLDER : text;
46451
+ } else output = messageContentToFunctionOutputItems(message.content);
46452
+ return [{
46453
+ call_id: callId,
46454
+ output,
46455
+ type: "function_call_output"
46456
+ }];
46457
+ }
46407
46458
  const result = [];
46408
46459
  if (message.content.length > 0) {
46409
46460
  const pendingParts = [];
@@ -46474,6 +46525,35 @@ function convertTool(tool) {
46474
46525
  strict: false
46475
46526
  };
46476
46527
  }
46528
+ /**
46529
+ * Convert the history, buffering tool-result media when `extract_text`
46530
+ * flattens tool outputs to plain strings. The buffered media items are
46531
+ * reattached as a single user message after each run of consecutive tool
46532
+ * messages — mirroring the OpenAI Chat Completions provider.
46533
+ */
46534
+ function convertHistoryMessages(history, modelName, toolMessageConversion) {
46535
+ const input = [];
46536
+ const pendingToolResultMedia = [];
46537
+ const flushPendingMedia = () => {
46538
+ if (pendingToolResultMedia.length === 0) return;
46539
+ input.push({
46540
+ type: "message",
46541
+ role: "user",
46542
+ content: [{
46543
+ type: "input_text",
46544
+ text: TOOL_RESULT_MEDIA_PROMPT
46545
+ }, ...pendingToolResultMedia]
46546
+ });
46547
+ pendingToolResultMedia.length = 0;
46548
+ };
46549
+ for (const msg of history) {
46550
+ if (msg.role !== "tool") flushPendingMedia();
46551
+ input.push(...convertMessage(msg, modelName, toolMessageConversion));
46552
+ if (msg.role === "tool" && toolMessageConversion === "extract_text") pendingToolResultMedia.push(...messageContentToFunctionOutputItems(msg.content.filter(isMediaPart)));
46553
+ }
46554
+ flushPendingMedia();
46555
+ return input;
46556
+ }
46477
46557
  var OpenAIResponsesStreamedMessage = class {
46478
46558
  _id = null;
46479
46559
  _usage = null;
@@ -46735,16 +46815,8 @@ var OpenAIResponsesChatProvider = class {
46735
46815
  }
46736
46816
  async generate(systemPrompt, tools, history, options) {
46737
46817
  const input = [];
46738
- if (systemPrompt) {
46739
- const sysItem = {
46740
- role: "system",
46741
- content: systemPrompt
46742
- };
46743
- if (usesOpenAIResponsesDeveloperRole(this._model)) sysItem["role"] = "developer";
46744
- input.push(sysItem);
46745
- }
46746
46818
  const normalizedHistory = normalizeToolCallIdsForProvider(history, OPENAI_RESPONSES_TOOL_CALL_ID_POLICY);
46747
- for (const msg of normalizedHistory) input.push(...convertMessage(msg, this._model, this._toolMessageConversion));
46819
+ input.push(...convertHistoryMessages(normalizedHistory, this._model, this._toolMessageConversion));
46748
46820
  const kwargs = { ...this._generationKwargs };
46749
46821
  const reasoningEffort = kwargs["reasoning_effort"];
46750
46822
  delete kwargs["reasoning_effort"];
@@ -46766,6 +46838,7 @@ var OpenAIResponsesChatProvider = class {
46766
46838
  stream: this._stream,
46767
46839
  ...kwargs
46768
46840
  };
46841
+ if (systemPrompt) createParams["instructions"] = systemPrompt;
46769
46842
  if (!("responses" in client) || typeof client.responses?.create !== "function") throw new Error("OpenAI SDK version does not support Responses API. Upgrade to >=4.x with responses support.");
46770
46843
  return new OpenAIResponsesStreamedMessage(await client.responses.create(createParams, options?.signal ? { signal: options.signal } : void 0), this._stream);
46771
46844
  } catch (error) {
@@ -47221,7 +47294,7 @@ const REDACTED_KEYS = new Set([
47221
47294
  "bearer"
47222
47295
  ]);
47223
47296
  const SAFE_KEY_RE = /^[\w.-]+$/;
47224
- const ELLIPSIS$8 = "…";
47297
+ const ELLIPSIS$9 = "…";
47225
47298
  const TRUNCATED_TAIL = ` …truncated`;
47226
47299
  const REDACTED = "[REDACTED]";
47227
47300
  const RAW_SECRET_PATTERNS = [
@@ -47260,7 +47333,7 @@ function redactCtx(ctx) {
47260
47333
  return walk(ctx, 0);
47261
47334
  }
47262
47335
  function truncate$2(value, max) {
47263
- return value.length <= max ? value : value.slice(0, max - 1) + ELLIPSIS$8;
47336
+ return value.length <= max ? value : value.slice(0, max - 1) + ELLIPSIS$9;
47264
47337
  }
47265
47338
  function serializeValue(raw) {
47266
47339
  if (typeof raw === "string") return redactString(raw);
@@ -47450,7 +47523,6 @@ var RotatingFileSink = class {
47450
47523
  pending = [];
47451
47524
  dropped = 0;
47452
47525
  closed = false;
47453
- failed = false;
47454
47526
  lastStderrNotice = 0;
47455
47527
  currentBytes = -1;
47456
47528
  directorySynced = false;
@@ -47507,7 +47579,6 @@ var RotatingFileSink = class {
47507
47579
  await syncDir(dirname(this.options.path));
47508
47580
  this.directorySynced = true;
47509
47581
  }
47510
- this.failed = false;
47511
47582
  return true;
47512
47583
  } catch (error) {
47513
47584
  this.noteFailure(error);
@@ -47591,7 +47662,6 @@ var RotatingFileSink = class {
47591
47662
  return line;
47592
47663
  }
47593
47664
  noteFailure(error) {
47594
- this.failed = true;
47595
47665
  const now = Date.now();
47596
47666
  if (now - this.lastStderrNotice < STDERR_NOTICE_INTERVAL_MS) return;
47597
47667
  this.lastStderrNotice = now;
@@ -49156,8 +49226,8 @@ function buildBackgroundTaskNotificationBody(info) {
49156
49226
  //#endregion
49157
49227
  //#region ../../packages/agent-core/src/utils/abort.ts
49158
49228
  var import_retry = /* @__PURE__ */ __toESM(require_retry$4(), 1);
49159
- function abortError$1() {
49160
- const error = /* @__PURE__ */ new Error("Aborted");
49229
+ function abortError$1(message = "Aborted") {
49230
+ const error = new Error(message);
49161
49231
  error.name = "AbortError";
49162
49232
  return error;
49163
49233
  }
@@ -54296,7 +54366,7 @@ function applyCompletionBudget(args) {
54296
54366
  return args.provider.withMaxCompletionTokens(cap);
54297
54367
  }
54298
54368
  //#endregion
54299
- //#region ../../packages/agent-core/src/agent/compaction/compaction-instruction.md
54369
+ //#region ../../packages/agent-core/src/agent/compaction/compaction-instruction.md?raw
54300
54370
  var compaction_instruction_default = "\n--- This message is a direct task, not part of the above conversation ---\n\nYou are now given a task to compact this conversation context according to specific priorities and output requirements.\n\nOutput text only. DO NOT CALL ANY TOOLS. Calling tools will be rejected and fails the task. You already have all the information you need in the conversation history. You have only one chance.\n\nThe goal of compaction is to keep essential code patterns, technical details, and architectural decisions for continuing development without losing context after the above messages are cleared work.\n\n{{ customInstruction }}\n\n<!-- Compression Priorities (in order) -->\n\n1. **Current Task State**: What is being worked on RIGHT NOW\n2. **Errors & Solutions**: All encountered errors and their resolutions\n3. **Code Evolution**: Final working versions only (remove intermediate attempts)\n4. **System Context**: Project structure, dependencies, environment setup\n5. **Design Decisions**: Architectural choices and their rationale\n6. **TODO Items**: Unfinished tasks and known issues\n\n<!-- Required Output Structure -->\n\n## Current Focus\n\n[What we're working on now]\n\n## Environment\n\n- [Key setup/config points]\n- ...\n\n## Completed Tasks\n\n- [Task]: [Brief outcome]\n- ...\n\n## Active Issues\n\n- [Issue]: [Status/Next steps]\n- ...\n\n## Code State\n\n### [Critical file name]\n\n[Brief description of the file's purpose and current state]\n\n```\n[The latest version of critical code snippets in this file, <20 lines]\n```\n\n### [Critical file name]\n\n- [Useful classes/methods/functions]: [Brief description/usage]\n- ...\n\n<!-- Omit non-critical code, intermediate attempts, and resolved errors -->\n\n## Important Context\n\n- [Any crucial information not covered above]\n- ...\n\n## All User Messages\n\n- [Detailed non tool use user message]\n- ...\n";
54301
54371
  //#endregion
54302
54372
  //#region ../../packages/agent-core/src/agent/compaction/render-messages.ts
@@ -54426,7 +54496,7 @@ function closeObjectNodes(value) {
54426
54496
  for (const child of Object.values(node)) closeObjectNodes(child);
54427
54497
  }
54428
54498
  //#endregion
54429
- //#region ../../packages/agent-core/src/tools/builtin/state/todo-list.md
54499
+ //#region ../../packages/agent-core/src/tools/builtin/state/todo-list.md?raw
54430
54500
  var todo_list_default = "Use this tool to maintain a structured TODO list as you work through a multi-step task. Use it proactively and often when progress tracking helps the current work. This is especially useful in Plan mode, long-running investigations, and implementation tasks with several tool calls.\n\n**When to use:**\n- Multi-step tasks that span several tool calls\n- Tracking investigation progress across a large codebase search\n- Planning a sequence of edits before making them\n- After receiving new multi-step instructions, capture the requirements as todos\n- Before starting a tracked task, mark exactly one item as `in_progress`\n- Immediately after finishing a tracked task, mark it `done`; do not batch completions at the end\n\n**When NOT to use:**\n- Single-shot answers that complete in one or two tool calls\n- Trivial requests where tracking adds no clarity\n- Purely conversational or informational replies\n\n**Avoid churn:**\n- Do not re-call this tool when nothing meaningful has changed since the last call — update the list only after real progress.\n- When unsure of the current state, call query mode first (omit `todos`) to check the list before deciding what to update.\n- If no available tool can move any task forward, tell the user where you are stuck instead of repeatedly re-ordering the same todos.\n\n**How to use:**\n- Call with `todos: [...]` to replace the full list. Statuses: pending / in_progress / done.\n- Call with no arguments to retrieve the current list without changing it.\n- Call with `todos: []` to clear the list.\n- Keep titles short and actionable (e.g. \"Read session-control.ts\", \"Add planMode flag to TurnManager\").\n- Update statuses as you make progress.\n- When work is underway, keep exactly one task `in_progress`.\n- Only mark a task `done` when it is fully accomplished.\n- Never mark a task `done` if tests are failing, implementation is partial, unresolved errors remain, or required files/dependencies could not be found.\n- If you encounter a blocker, keep the blocked task `in_progress` or add a new pending task describing what must be resolved.\n";
54431
54501
  //#endregion
54432
54502
  //#region ../../packages/agent-core/src/tools/builtin/state/todo-list.ts
@@ -54630,7 +54700,13 @@ var FullCompaction = class {
54630
54700
  if (data.source === "manual") this.compactionCountInTurn = 0;
54631
54701
  else this.compactionCountInTurn += 1;
54632
54702
  if (this.compactionCountInTurn > this.strategy.maxCompactionPerTurn) return;
54633
- if (this.agent.records.restoring) return;
54703
+ if (this.agent.records.restoring) {
54704
+ this.agent.replayBuilder.push({
54705
+ type: "compaction",
54706
+ instruction: data.instruction
54707
+ });
54708
+ return;
54709
+ }
54634
54710
  const compactedCount = this.strategy.computeCompactCount(this.agent.context.history, data.source);
54635
54711
  if (compactedCount === 0) throw new KimiError(ErrorCodes.COMPACTION_UNABLE, "No prefix that can be compacted in current history.");
54636
54712
  this.agent.records.logRecord({
@@ -54660,6 +54736,7 @@ var FullCompaction = class {
54660
54736
  this.markCanceled();
54661
54737
  }
54662
54738
  markCanceled() {
54739
+ this.agent.replayBuilder.patchLast("compaction", { result: "cancelled" });
54663
54740
  if (!this.compacting) return;
54664
54741
  this.agent.records.logRecord({ type: "full_compaction.cancel" });
54665
54742
  this.compacting.abortController.abort();
@@ -56376,8 +56453,12 @@ var ConfigState = class {
56376
56453
  return this._modelAlias;
56377
56454
  }
56378
56455
  get thinkingLevel() {
56456
+ if (this._thinkingLevel === "off" && this.alwaysThinkingModel) return resolveThinkingEffort("on", this.agent.kimiConfig?.thinking);
56379
56457
  return this._thinkingLevel;
56380
56458
  }
56459
+ get alwaysThinkingModel() {
56460
+ return this.tryResolvedProviderConfig()?.alwaysThinking === true;
56461
+ }
56381
56462
  get profileName() {
56382
56463
  return this._profileName;
56383
56464
  }
@@ -56570,26 +56651,32 @@ var ContextMemory = class {
56570
56651
  stoppedAtCompaction: stoppedAtBoundary
56571
56652
  } });
56572
56653
  }
56573
- applyCompaction(summary) {
56654
+ applyCompaction(result) {
56574
56655
  this.agent.records.logRecord({
56575
56656
  type: "context.apply_compaction",
56576
- ...summary
56657
+ ...result
56577
56658
  });
56659
+ this.agent.replayBuilder.patchLast("compaction", { result: {
56660
+ summary: result.summary,
56661
+ compactedCount: result.compactedCount,
56662
+ tokensBefore: result.tokensBefore,
56663
+ tokensAfter: result.tokensAfter
56664
+ } });
56578
56665
  this._history = [{
56579
56666
  role: "assistant",
56580
56667
  content: [{
56581
56668
  type: "text",
56582
- text: summary.summary
56669
+ text: result.summary
56583
56670
  }],
56584
56671
  toolCalls: [],
56585
56672
  origin: { kind: "compaction_summary" }
56586
- }, ...this._history.slice(summary.compactedCount)];
56673
+ }, ...this._history.slice(result.compactedCount)];
56587
56674
  this.openSteps.clear();
56588
56675
  this.flushDeferredMessagesIfToolExchangeClosed();
56589
- this._tokenCount = summary.tokensAfter;
56676
+ this._tokenCount = result.tokensAfter;
56590
56677
  this.tokenCountCoveredMessageCount = this._history.length;
56591
56678
  this.agent.microCompaction.reset();
56592
- this.agent.injection.onContextCompacted(summary.compactedCount);
56679
+ this.agent.injection.onContextCompacted(result.compactedCount);
56593
56680
  this.agent.emitStatusUpdated();
56594
56681
  }
56595
56682
  data() {
@@ -58024,6 +58111,31 @@ var InjectionManager = class {
58024
58111
  }
58025
58112
  };
58026
58113
  //#endregion
58114
+ //#region ../../packages/agent-core/src/agent/permission/policies/agent-swarm-exclusive-deny.ts
58115
+ var AgentSwarmExclusiveDenyPermissionPolicy = class {
58116
+ name = "agent-swarm-exclusive-deny";
58117
+ evaluate(context) {
58118
+ const toolCalls = context.toolCalls;
58119
+ const agentSwarmCount = toolCalls.filter((toolCall) => toolCall.name === "AgentSwarm").length;
58120
+ if (agentSwarmCount === 0) return;
58121
+ if (agentSwarmCount === 1 && toolCalls.length === 1) return;
58122
+ return {
58123
+ kind: "deny",
58124
+ message: agentSwarmCount > 1 ? multipleAgentSwarmDeniedMessage(toolCalls.length > agentSwarmCount) : mixedAgentSwarmDeniedMessage(),
58125
+ reason: {
58126
+ agent_swarm_tool_calls: agentSwarmCount,
58127
+ tool_calls: toolCalls.length
58128
+ }
58129
+ };
58130
+ }
58131
+ };
58132
+ function multipleAgentSwarmDeniedMessage(hasOtherToolCalls) {
58133
+ return `AgentSwarm must be called one swarm at a time. Multiple AgentSwarm calls are not forbidden, but issue them sequentially: call one AgentSwarm, wait for its result, then call the next; or merge the work into a single AgentSwarm when one swarm can cover it.${hasOtherToolCalls ? " AgentSwarm also must not be combined with other tools in the same response." : ""}`;
58134
+ }
58135
+ function mixedAgentSwarmDeniedMessage() {
58136
+ return "AgentSwarm must be the only tool call in a model response. Retry with a single AgentSwarm call by itself, then call any other tools after it returns.";
58137
+ }
58138
+ //#endregion
58027
58139
  //#region ../../packages/agent-core/src/agent/permission/policies/auto-mode-approve.ts
58028
58140
  var AutoModeApprovePermissionPolicy = class {
58029
58141
  agent;
@@ -58466,11 +58578,7 @@ function parseGitDir(content, markerParent) {
58466
58578
  //#endregion
58467
58579
  //#region ../../packages/agent-core/src/agent/permission/policies/file-access-ask.ts
58468
58580
  var SensitiveFileAccessAskPermissionPolicy = class {
58469
- agent;
58470
58581
  name = "sensitive-file-access-ask";
58471
- constructor(agent) {
58472
- this.agent = agent;
58473
- }
58474
58582
  evaluate(context) {
58475
58583
  const access = fileAccesses(context).find((fileAccess) => isSensitiveFile(fileAccess.path));
58476
58584
  if (access === void 0) return;
@@ -60720,6 +60828,7 @@ var YoloModeApprovePermissionPolicy = class {
60720
60828
  function createPermissionDecisionPolicies(agent) {
60721
60829
  return [
60722
60830
  new PreToolCallHookPermissionPolicy(agent),
60831
+ new AgentSwarmExclusiveDenyPermissionPolicy(),
60723
60832
  new AutoModeAskUserQuestionDenyPermissionPolicy(agent),
60724
60833
  new PlanModeGuardDenyPermissionPolicy(agent),
60725
60834
  new UserConfiguredDenyPermissionPolicy(agent),
@@ -60729,7 +60838,7 @@ function createPermissionDecisionPolicies(agent) {
60729
60838
  new UserConfiguredAllowPermissionPolicy(agent),
60730
60839
  new ExitPlanModeReviewAskPermissionPolicy(agent),
60731
60840
  new PlanModeToolApprovePermissionPolicy(agent),
60732
- new SensitiveFileAccessAskPermissionPolicy(agent),
60841
+ new SensitiveFileAccessAskPermissionPolicy(),
60733
60842
  new GitControlPathAccessAskPermissionPolicy(agent),
60734
60843
  new YoloModeApprovePermissionPolicy(agent),
60735
60844
  new SwarmModeAgentSwarmApprovePermissionPolicy(agent),
@@ -62006,12 +62115,22 @@ var AgentRecords = class {
62006
62115
  //#region ../../packages/agent-core/src/agent/replay/index.ts
62007
62116
  var ReplayBuilder = class {
62008
62117
  agent;
62118
+ captureLiveRecords = false;
62009
62119
  records = [];
62010
62120
  constructor(agent) {
62011
62121
  this.agent = agent;
62012
62122
  }
62013
62123
  push(record) {
62014
- if (this.agent.records.restoring) this.records.push(record);
62124
+ if (this.captureLiveRecords || this.agent.records.restoring) this.records.push({
62125
+ ...record,
62126
+ time: this.agent.records.restoring?.time ?? Date.now()
62127
+ });
62128
+ }
62129
+ patchLast(type, patch) {
62130
+ if (this.agent.records.restoring) {
62131
+ const last = this.records.at(-1);
62132
+ if (last && last.type === type) Object.assign(last, patch);
62133
+ }
62015
62134
  }
62016
62135
  removeLastMessages(removedMessages) {
62017
62136
  if (removedMessages.size === 0) return;
@@ -65487,7 +65606,7 @@ function isUserActivatableSkillType(type) {
65487
65606
  return isInlineSkillType(type) || type === "flow";
65488
65607
  }
65489
65608
  function isSupportedSkillType(type) {
65490
- return isUserActivatableSkillType(type);
65609
+ return isUserActivatableSkillType(type) || type === "reference";
65491
65610
  }
65492
65611
  function summarizeSkill(skill) {
65493
65612
  return {
@@ -65496,7 +65615,8 @@ function summarizeSkill(skill) {
65496
65615
  path: skill.path,
65497
65616
  source: skill.source,
65498
65617
  type: skill.metadata.type,
65499
- disableModelInvocation: skill.metadata.disableModelInvocation
65618
+ disableModelInvocation: skill.metadata.disableModelInvocation,
65619
+ isSubSkill: skill.metadata.isSubSkill
65500
65620
  };
65501
65621
  }
65502
65622
  //#endregion
@@ -65697,8 +65817,8 @@ function isRecord$11(value) {
65697
65817
  return typeof value === "object" && value !== null && !Array.isArray(value);
65698
65818
  }
65699
65819
  //#endregion
65700
- //#region ../../packages/agent-core/src/skill/builtin/custom-theme.md
65701
- var custom_theme_default = "---\nname: custom-theme\ndescription: Create or edit a kimi-code custom color theme — a JSON file under ~/.kimi-code/themes/ that recolors the TUI. Use when the user wants their own theme, asks for a specific palette or mood, or wants to tweak an existing custom theme's colors.\n---\n\n# Create a kimi-code custom theme (custom-theme)\n\nHelp the user design, write, and apply a custom color theme for the kimi-code TUI. A theme is a single JSON file; the TUI ships with `dark`, `light`, and `auto`, and any file the user adds becomes selectable alongside them.\n\n## What a theme is\n\n- A theme lives at `~/.kimi-code/themes/<name>.json` (or `$KIMI_CODE_HOME/themes/<name>.json` when that variable is set). Create the `themes/` directory if it doesn't exist.\n- **The filename is the theme name**: `ember.json` shows up in the `/theme` picker as `Custom: ember`.\n- Shape:\n\n ```json\n {\n \"name\": \"ember\",\n \"displayName\": \"Ember\",\n \"colors\": {\n \"primary\": \"#83A598\",\n \"accent\": \"#FE8019\"\n }\n }\n ```\n\n - `name` (required), `displayName` (optional), `base` (optional: `\"dark\"` default, or `\"light\"`), `colors` (each value a 6-digit hex `#RRGGBB`).\n- **Partial themes are fine**: any token you leave out falls back to the **base** palette (`dark` by default; set `\"base\": \"light\"` for a light theme), so you can recolor just a few tokens or all of them.\n\n## Source of truth: the docs token reference\n\nBefore choosing colors, use **FetchURL** to fetch the official custom-theme docs as the authoritative list of tokens and what each controls:\n\n```\nhttps://moonshotai.github.io/kimi-code/en/customization/themes.html\n```\n\nOnly set tokens from this set — unknown keys are silently ignored at load. If FetchURL is unavailable or the fetch fails, fall back to the embedded reference below (it mirrors the same tokens) and tell the user you're working from the built-in list rather than the live docs.\n\n## Color tokens (what each controls)\n\n| Token | Controls |\n| --- | --- |\n| `primary` | The most-used color: links, inline code, the selected item in nearly every dialog, the focused editor border, plan/\"running\" badges, spinners |\n| `accent` | Secondary highlight: approval `▶` prefix, device-code box, image placeholder, BTW / queue panes, registry import |\n| `text` | Body text: dialog bodies, todo titles, footer model label, Markdown headings, assistant/tool message bullets, list bullets |\n| `textStrong` | Emphasized / bold text: input dialogs, status messages |\n| `textDim` | Secondary, dimmed text (the most widely used dim shade): thinking, hints, descriptions, completed todos, Markdown quotes, footer status bar |\n| `textMuted` | Faintest text: counters, scroll info, descriptions, Markdown link URLs, code-block borders |\n| `border` | Pane and editor borders, Markdown horizontal rule |\n| `borderFocus` | Focus / attention border (currently only the approval panel) |\n| `success` | Success state: `✓`, \"enabled\", completed |\n| `warning` | Warning state: auto/yolo badges, stale markers, plan-mode hint |\n| `error` | Error state: error messages, failed tool output |\n| `diffAdded` | Diff added lines |\n| `diffRemoved` | Diff removed lines |\n| `diffAddedStrong` | Diff intra-line changed words, added (bold) |\n| `diffRemovedStrong` | Diff intra-line changed words, removed (bold) |\n| `diffGutter` | Diff line-number gutter |\n| `diffMeta` | Diff meta / hunk headers |\n| `roleUser` | User message bullet and text, skill-activation name (the one role color with its own hue) |\n\n## Workflow\n\n1. **Ask the user what they want first — before choosing any colors.** Clarify, in one short exchange:\n - **Light or dark?** A light theme (dark text on a light background) or a dark theme (light text on a dark background). This sets the whole direction, so settle it first. For a light theme, set `\"base\": \"light\"` so the tokens you leave out inherit the light palette instead of dark.\n - **What style / mood?** e.g. warm vs cool, vivid vs muted, high vs low contrast, a named vibe (\"nord\", \"solarized\", \"sunset\"), or a base to start from (an existing theme, or `dark` / `light`).\n - **Any specific colors?** Whether they have exact hex values to anchor on (a brand color, a preferred `primary`, etc.).\n\n Use **AskUserQuestion** for the discrete choices (light vs dark, a few style options); use a plain question for free-form input like specific hex values. Don't start picking colors until you at least know light-vs-dark and the rough style.\n2. **Pick a starting point.**\n - Tweaking an existing custom theme: **Read** `~/.kimi-code/themes/<name>.json` first — never overwrite a theme you haven't read.\n - Starting fresh: build a `colors` object from the token table. You can `ls ~/.kimi-code/themes/` and Read one of the user's existing themes as a reference for the format.\n3. **Choose colors deliberately.**\n - Every value is a 6-digit hex `#RRGGBB` (not 3-digit, not a named color).\n - Keep contrast usable against the user's terminal background: don't let `text` / `textDim` sit too close to the background, and keep `success` / `warning` / `error` clearly distinguishable from each other.\n - `primary` is the most-seen color (links, selection, focus) — make it readable and distinct from `text`.\n - `roleUser` is the one role color meant to stand on its own — give it a distinct hue.\n4. **Write the file** to `~/.kimi-code/themes/<name>.json` with **Write** for a new theme (pick a short kebab-case filename). When editing an existing theme, prefer **Edit** on just the color(s) that change so the rest stays intact — and back it up first (see Don'ts).\n5. **Validate.** Confirm the file is valid JSON and every `colors` value matches `^#[0-9a-fA-F]{6}$`. A quick check with **Bash**:\n\n ```bash\n node -e 'const p=require(\"os\").homedir()+\"/.kimi-code/themes/<name>.json\";const c=(require(p).colors)||{};const bad=Object.entries(c).filter(([,v])=>!/^#[0-9a-fA-F]{6}$/.test(v));console.log(bad.length?[\"invalid:\",...bad]:\"all hex valid\")'\n ```\n\n Invalid values are silently skipped at load (they fall back to the base palette; not fatal), but fix them so the theme renders as intended.\n6. **Tell the user how to apply it** (next section).\n\n## Applying the theme\n\n- The `/theme` picker re-scans the themes directory every time it opens, so a newly added file shows up **without restarting** — tell the user to run `/theme` and choose `Custom: <name>`.\n- Or set it in `tui.toml`: `theme = \"<name>\"`.\n- **Editing the active theme**: changes to the theme that's *currently in use* are not auto-reloaded. Tell the user to run **`/reload-tui`** (or switch to another theme and back). Re-selecting the **same** theme in `/theme` is a no-op (\"Theme unchanged\").\n\n## Don'ts\n\n- Don't invent token names — only use the documented set; unknown keys are silently ignored.\n- Don't write 3-digit hex or named colors — use full `#RRGGBB`.\n- Before overwriting an existing theme file, **read it and back it up** (e.g. `cp <name>.json \"<name>.json.$(date +%Y%m%d-%H%M%S).bak\"`) so the user can recover.\n- Don't tell the user to restart the app to apply a theme — `/theme` or `/reload-tui` is enough.\n";
65820
+ //#region ../../packages/agent-core/src/skill/builtin/custom-theme.md?raw
65821
+ var custom_theme_default = "---\nname: custom-theme\ndescription: Create or edit a kimi-code custom color theme — a JSON file under the resolved KIMI_CODE_HOME data directory that recolors the TUI. Use when the user wants their own theme, asks for a specific palette or mood, or wants to tweak an existing custom theme's colors.\n---\n\n# Create a kimi-code custom theme (custom-theme)\n\nHelp the user design, write, and apply a custom color theme for the kimi-code TUI. A theme is a single JSON file; the TUI ships with `dark`, `light`, and `auto`, and any file the user adds becomes selectable alongside them.\n\n## Rules of engagement\n\n- **Never write a theme until the user has explicitly clarified what they want.** This skill may only run after the user has confirmed light vs dark, the style or mood, any specific colors they care about, and the intended filename. If any of these are missing, ask before creating files.\n- **Never assume the data directory is `~/.kimi-code`.** Always resolve `$KIMI_CODE_HOME` first with the Bash command below.\n- **Never edit a live theme file in place.** Always create a `.json.new` candidate, validate it, back up the old file, and then `mv` it into place.\n- **Never overwrite an existing theme without reading it first.** Read, back up, then overwrite only after the user confirms.\n\n## Where a theme lives\n\nThe kimi-code runtime resolves the data directory as `KIMI_CODE_HOME` first, falling back to `~/.kimi-code`. Theme files live inside the `themes/` subdirectory of that data directory.\n\nBefore doing anything, resolve the actual data root with Bash so you don't write to the wrong place. Check whether `KIMI_CODE_HOME` is set and fall back to `~/.kimi-code` when it is empty:\n\n```bash\necho \"$KIMI_CODE_HOME\"\necho \"$HOME/.kimi-code\"\n```\n\nUse the first line when it is non-empty; otherwise use the second line. In the rest of this skill, `<KIMI_CODE_HOME>` means that resolved data root — **never assume `~/.kimi-code`**. Theme files live at `<KIMI_CODE_HOME>/themes/<name>.json`. Create the `themes/` directory if it doesn't exist.\n\n## What a theme is\n\n- A theme lives at `<KIMI_CODE_HOME>/themes/<name>.json`.\n- **The filename is the theme name**: `ember.json` shows up in the `/theme` picker as `Custom: ember`.\n- Shape:\n\n ```json\n {\n \"name\": \"ember\",\n \"displayName\": \"Ember\",\n \"colors\": {\n \"primary\": \"#83A598\",\n \"accent\": \"#FE8019\"\n }\n }\n ```\n\n - `name` (required), `displayName` (optional), `base` (optional: `\"dark\"` default, or `\"light\"`), `colors` (each value a 6-digit hex `#RRGGBB`).\n- **Partial themes are fine**: any token you leave out falls back to the **base** palette (`dark` by default; set `\"base\": \"light\"` for a light theme), so you can recolor just a few tokens or all of them.\n\n## Source of truth: the docs token reference\n\nBefore choosing colors, use **FetchURL** to fetch the official custom-theme docs as the authoritative list of tokens and what each controls:\n\n```\nhttps://moonshotai.github.io/kimi-code/en/customization/themes.html\n```\n\nOnly set tokens from this set — unknown keys are silently ignored at load. If FetchURL is unavailable or the fetch fails, fall back to the embedded reference below (it mirrors the same tokens) and tell the user you're working from the built-in list rather than the live docs.\n\n## Color tokens (what each controls)\n\n| Token | Controls |\n| --- | --- |\n| `primary` | The most-used color: links, inline code, the selected item in nearly every dialog, the focused editor border, plan/\"running\" badges, spinners |\n| `accent` | Secondary highlight: approval `▶` prefix, device-code box, image placeholder, BTW / queue panes, registry import |\n| `text` | Body text: dialog bodies, todo titles, footer model label, Markdown headings, assistant/tool message bullets, list bullets |\n| `textStrong` | Emphasized / bold text: input dialogs, status messages |\n| `textDim` | Secondary, dimmed text (the most widely used dim shade): thinking, hints, descriptions, completed todos, Markdown quotes, footer status bar |\n| `textMuted` | Faintest text: counters, scroll info, descriptions, Markdown link URLs, code-block borders |\n| `border` | Pane and editor borders, Markdown horizontal rule |\n| `borderFocus` | Focus / attention border (currently only the approval panel) |\n| `success` | Success state: `✓`, \"enabled\", completed |\n| `warning` | Warning state: auto/yolo badges, stale markers, plan-mode hint |\n| `error` | Error state: error messages, failed tool output |\n| `diffAdded` | Diff added lines |\n| `diffRemoved` | Diff removed lines |\n| `diffAddedStrong` | Diff intra-line changed words, added (bold) |\n| `diffRemovedStrong` | Diff intra-line changed words, removed (bold) |\n| `diffGutter` | Diff line-number gutter |\n| `diffMeta` | Diff meta / hunk headers |\n| `roleUser` | User message bullet and text, skill-activation name (the one role color with its own hue) |\n\n## Workflow\n\n1. **Ask the user what they want first — before choosing any colors.** Clarify, in one short exchange:\n - **Light or dark?** A light theme (dark text on a light background) or a dark theme (light text on a dark background). This sets the whole direction, so settle it first. For a light theme, set `\"base\": \"light\"` so the tokens you leave out inherit the light palette instead of dark.\n - **What style / mood?** e.g. warm vs cool, vivid vs muted, high vs low contrast, a named vibe (\"nord\", \"solarized\", \"sunset\"), or a base to start from (an existing theme, or `dark` / `light`).\n - **Any specific colors?** Whether they have exact hex values to anchor on (a brand color, a preferred `primary`, etc.).\n\n For the discrete choices (light vs dark, a few style options), prefer **AskUserQuestion** if it is available. If you are running in **auto mode** and `AskUserQuestion` is unavailable, ask the same question as a plain-text message with clear numbered or bulleted options, and wait for the user's reply. Don't start picking colors until you at least know light-vs-dark and the rough style.\n\n2. **Resolve the actual theme directory and current theme(s).**\n - Resolve the data root by checking `echo \"$KIMI_CODE_HOME\"`; if empty, use `echo \"$HOME/.kimi-code\"`. Use `<root>/themes` for every subsequent step.\n - If tweaking an existing custom theme, **Read** `<KIMI_CODE_HOME>/themes/<name>.json` first — never overwrite a theme you haven't read.\n - Starting fresh: build a `colors` object from the token table. You can `ls <KIMI_CODE_HOME>/themes/` and Read one of the user's existing themes as a reference for the format.\n\n3. **Pick a starting point and choose colors deliberately.**\n - Every value is a 6-digit hex `#RRGGBB` (not 3-digit, not a named color).\n - Keep contrast usable against the user's terminal background: don't let `text` / `textDim` sit too close to the background, and keep `success` / `warning` / `error` clearly distinguishable from each other.\n - `primary` is the most-seen color (links, selection, focus) — make it readable and distinct from `text`.\n - `roleUser` is the one role color meant to stand on its own — give it a distinct hue.\n\n4. **Create a candidate file; never edit the live theme in place.**\n - Use Bash to create a candidate. If the target theme already exists, copy it verbatim: `cp <name>.json <name>.json.new` (inside `<KIMI_CODE_HOME>/themes/`). If it doesn't exist, use **Write** to create a minimal skeleton named `<name>.json.new`.\n - Use **Edit** on the candidate to change only the intended keys. Keep every existing entry, comment, and formatting intact.\n\n5. **Validate the candidate before overwriting.**\n - Read the candidate with **Read** to visually confirm it is well-formed JSON and that every `colors` value is a full 6-digit hex `#RRGGBB` (not 3-digit, not a named color).\n - Invalid hex values are silently skipped at load (they fall back to the base palette), but fix them so the theme renders as intended.\n\n6. **Back up and overwrite.**\n - Back up the old file first — **always** create a new timestamped backup and never overwrite an existing backup: `cp <name>.json \"<name>.json.$(date +%Y%m%d-%H%M%S).bak\"`.\n - If the target didn't exist, skip the backup.\n - Overwrite with the candidate: `mv <name>.json.new <name>.json`.\n\n7. **Tell the user how to apply it** (next section).\n\n## Applying the theme\n\n- The `/theme` picker re-scans the themes directory every time it opens, so a newly added file shows up **without restarting** — tell the user to run `/theme` and choose `Custom: <name>`.\n- Or set it in `tui.toml`: `theme = \"<name>\"`.\n- **Editing the active theme**: changes to the theme that's *currently in use* are not auto-reloaded. Tell the user to run **`/reload-tui`** (or switch to another theme and back). Re-selecting the **same** theme in `/theme` is a no-op (\"Theme unchanged\").\n\n## Don'ts\n\n- **Don't start creating or editing a theme until the user has clarified light/dark, style/mood, any specific colors, and the filename.** If anything is unclear, ask — don't guess.\n- Don't invent token names — only use the documented set; unknown keys are silently ignored.\n- Don't write 3-digit hex or named colors — use full `#RRGGBB`.\n- Never edit the live theme file in place; work through a candidate and validate before `mv`.\n- Before overwriting an existing theme file, **read it and back it up** so the user can recover.\n- Don't tell the user to restart the app to apply a theme — `/theme` or `/reload-tui` is enough.\n";
65702
65822
  //#endregion
65703
65823
  //#region ../../packages/agent-core/src/skill/builtin/custom-theme.ts
65704
65824
  const PSEUDO_PATH$3 = "builtin://custom-theme";
@@ -65719,7 +65839,7 @@ const CUSTOM_THEME_SKILL = {
65719
65839
  }
65720
65840
  };
65721
65841
  //#endregion
65722
- //#region ../../packages/agent-core/src/skill/builtin/import-from-cc-codex.md
65842
+ //#region ../../packages/agent-core/src/skill/builtin/import-from-cc-codex.md?raw
65723
65843
  var import_from_cc_codex_default = "---\nname: import-from-cc-codex\ndescription: Import Claude Code and Codex instructions, skills, and MCP settings into Kimi Code.\ndisable-model-invocation: true\n---\n\n# Import from Claude Code and Codex\n\nThe user invoked `/import-from-cc-codex` (or `/skill:import-from-cc-codex`).\nHelp them migrate selected local Claude Code and Codex assets into Kimi Code.\nThis skill is intentionally conservative: it imports only instructions, skills,\nand MCP server declarations from `.claude` / `.codex` surfaces, with a user\npreview before any write.\n\n## Non-negotiable rules\n\n- Do **not** migrate `.agents` content. Kimi Code already supports `.agents`\n skills and AGENTS files by default.\n- Do **not** migrate Claude custom commands (`.claude/commands/**`). They are\n out of scope for this importer.\n- Do **not** migrate credentials, OAuth tokens, sessions, history, logs, hooks,\n plugins, plugin caches, output styles, or custom agents/subagents.\n- Do **not** run or install anything from the source directories.\n- Do **not** write anything until the user has chosen what to migrate, reviewed\n the final preview, and explicitly confirmed applying it.\n- Only write under Kimi Code targets:\n - User-global: `$KIMI_CODE_HOME` if set, otherwise `~/.kimi-code`.\n - Project instructions/skills: `<project root>/.kimi-code`, where the project\n root is the nearest parent directory containing `.git`; if no `.git` exists,\n use the current working directory.\n - Project-local MCP: `<cwd>/.kimi-code/mcp.json`, because Kimi reads the\n current working directory's Kimi-specific MCP file, not every project-root\n `.kimi-code/mcp.json` from subdirectories.\n- Preserve existing Kimi files. Never overwrite existing skills or replace an\n existing AGENTS.md / mcp.json wholesale.\n\n## Conversation flow\n\n### 1. Ask what to migrate first\n\nBefore reading source files, ask the user which categories to migrate. Use\n`AskUserQuestion` when available; otherwise ask in plain text and stop. Offer a\nmulti-select choice:\n\n- Instructions (`AGENTS.md` / `CLAUDE.md`)\n- Skills\n- MCP settings\n- All of the above\n\nIf the user already gave a preference in the invocation arguments, present it as\nthe default/recommended choice, but still ask for confirmation of the categories.\nIf the user dismisses or refuses the question, stop.\n\n### 2. Scan only the chosen categories\n\nResolve paths explicitly; `~` is the real OS home, and Kimi home follows\n`$KIMI_CODE_HOME` before `~/.kimi-code`.\n\nUser-level sources:\n\n- Claude instructions:\n - `~/.claude/AGENTS.md`\n - `~/.claude/CLAUDE.md`\n- Claude skills:\n - `~/.claude/skills/`\n- Claude MCP candidates:\n - `~/.claude.json` only when MCP is selected. Claude Code stores user-level\n MCP declarations there; do not read it for instruction/skill-only imports.\n- Codex instructions:\n - `~/.codex/AGENTS.md`\n - `~/.codex/CLAUDE.md` if present\n- Codex skills:\n - `~/.codex/skills/`\n- Codex MCP candidates:\n - `~/.codex/config.toml`\n\nProject-level sources, rooted at the project root:\n\n- Claude instructions:\n - `<project root>/.claude/AGENTS.md`\n - `<project root>/.claude/CLAUDE.md`\n- Claude skills:\n - `<project root>/.claude/skills/`\n- Codex instructions:\n - `<project root>/.codex/AGENTS.md`\n - `<project root>/.codex/CLAUDE.md` if present\n- Codex skills:\n - `<project root>/.codex/skills/`\n- Codex MCP candidates:\n - `<project root>/.codex/config.toml`\n\nDo not scan project-root `AGENTS.md`, project-root `CLAUDE.md`, `.agents/**`, or\nproject-root `.mcp.json` in this skill. `AGENTS.md` and `.agents/**` are already\nKimi-readable, and project-root `.mcp.json` is already read by Kimi as a\nClaude-compatible MCP file.\n\n### 3. Build an import plan\n\nCreate a plan with three sections: instructions, skills, and MCP. Include exact\nsource and target paths.\n\n#### Instructions plan\n\nMap user-level instruction sources to:\n\n- `$KIMI_CODE_HOME/AGENTS.md`, or `~/.kimi-code/AGENTS.md` if the env var is not\n set.\n\nMap project-level instruction sources to:\n\n- `<project root>/.kimi-code/AGENTS.md`\n\nAppend imported instruction content as marked blocks. Do not duplicate a block\nthat already exists in the target file.\n\nUse this marker shape:\n\n```md\n<!-- Imported from Claude Code: /absolute/source/path -->\n\n<source content>\n\n<!-- End imported from Claude Code: /absolute/source/path -->\n```\n\nFor Codex, use `Imported from Codex` / `End imported from Codex`.\n\nIf a source file is empty, skip it and report it as skipped. If the target exists\nand cannot be read as UTF-8 text, stop before writing and report the blocker.\n\n#### Skills plan\n\nMap user-level skill sources to:\n\n- `$KIMI_CODE_HOME/skills/`, or `~/.kimi-code/skills/` if the env var is not set.\n\nMap project-level skill sources to:\n\n- `<project root>/.kimi-code/skills/`\n\nRecognize these skill shapes under `.claude/skills/` or `.codex/skills/`:\n\n- Directory bundle: `<skill-name>/SKILL.md`\n- Flat markdown skill: `<skill-name>.md`\n\nCopy the entire directory bundle or flat markdown file. Preserve supporting\nfiles inside bundles. Do not copy hidden directories, `node_modules`, caches, or\nplugin-managed folders.\n\nBefore planning a copy:\n\n- Read a bundle's `SKILL.md` enough to verify that directory skills have\n frontmatter with non-empty `name` and `description`, because Kimi requires\n those fields for directory skills.\n- If the target top-level entry already exists, skip it; do not overwrite.\n- If two source entries would write the same target path, keep the first one in\n this order and report the later one as skipped:\n 1. project Claude\n 2. project Codex\n 3. user Claude\n 4. user Codex\n- Warn when a source skill uses Claude/Codex-specific fields or syntax that Kimi\n may not interpret the same way, such as `allowed-tools`, `disallowed-tools`,\n `context: fork`, `agent`, `hooks`, `paths`, dynamic shell injection with\n ``!`command` ``, or `agents/openai.yaml`. Preserve the file; do not rewrite it\n unless the user explicitly asks.\n\nDo not convert `.claude/commands/*.md`. Commands are out of scope.\n\n#### MCP plan\n\nDo not edit `mcp.json` directly in this import skill. Prepare MCP entries for\nmanual follow-up with `/mcp-config`; that built-in skill is user-invocable only,\nso you must not try to call it through the `Skill` tool.\n\nFor the preview, collect MCP candidates and normalize them into Kimi's MCP shape\nwhen possible:\n\n```json\n{\n \"mcpServers\": {\n \"name\": {\n \"command\": \"...\",\n \"args\": [\"...\"],\n \"env\": { \"KEY\": \"VALUE\" }\n }\n }\n}\n```\n\nClaude user MCP:\n\n- Read `~/.claude.json` only if MCP was selected.\n- Look for a top-level `mcpServers` object.\n- Keep stdio entries with `command`; keep HTTP entries with `url`.\n- Preserve `args`, `env`, `cwd`, `enabled`, `enabledTools`, `disabledTools`,\n `startupTimeoutMs`, `toolTimeoutMs`, `headers`, and `bearerTokenEnvVar` when\n present and valid.\n- Drop unsupported or malformed entries and report why.\n\nCodex MCP:\n\n- Read selected `config.toml` files only if MCP was selected.\n- Look for `[mcp_servers.<name>]` tables.\n- Map Codex fields to Kimi fields:\n - `command` -> `command`\n - `args` -> `args`\n - `env` -> `env`\n - `cwd` -> `cwd`\n - `url` -> `url`\n - `bearer_token_env_var` -> `bearerTokenEnvVar`\n - `enabled` -> `enabled`\n - `enabled_tools` -> `enabledTools`\n - `disabled_tools` -> `disabledTools`\n - `startup_timeout_sec` -> `startupTimeoutMs` in milliseconds\n - `tool_timeout_sec` -> `toolTimeoutMs` in milliseconds\n - `http_headers` -> `headers`\n- Drop unsupported Codex-only fields and report them, especially `required`,\n `default_tools_approval_mode`, `tools.<tool>.approval_mode`,\n `env_vars`, `env_http_headers`, and `experimental_environment`.\n- Do not import project-root `.mcp.json`; Kimi already reads it.\n\nFor each MCP candidate, choose the target scope in the preview:\n\n- User-level source -> user-global MCP target (`$KIMI_CODE_HOME/mcp.json` or\n `~/.kimi-code/mcp.json`).\n- Project-level source -> project-local Kimi MCP target (`<cwd>/.kimi-code/mcp.json`). If `<cwd>` is not the project root, call this out in the preview so the user understands when Kimi will load it.\n\nWarn that stdio MCP entries spawn commands at session start, and the user should\nonly import MCP servers they trust. Warn if an MCP entry contains apparent\nliteral secrets in `env`, `headers`, or token-like fields; prefer env-var\nreferences.\n\nAfter the user confirms applying the final preview, do not write MCP config and\ndo not invoke `mcp-config` programmatically. Instead, finish the non-MCP writes\nand show a copy-pasteable manual follow-up for the user, including:\n\n- the `/mcp-config` command they should run,\n- target scope and target path,\n- the normalized JSON entry or entries to add,\n- collision policy: keep existing Kimi entries on name conflict,\n- the reminder that unrelated entries must be preserved.\n\nMake it clear that MCP import is pending until the user manually runs\n`/mcp-config` with the prepared entries.\n\n### 4. Show the final preview and stop\n\nAfter scanning, show a concise final preview grouped by target file/directory:\n\n- Will append instruction blocks\n- Will copy skill bundles/files\n- Will leave these MCP entries pending for a manual `/mcp-config` follow-up\n- Already present / skipped\n- Warnings and blockers\n\nThen ask for explicit confirmation before writing. Use a clear choice such as:\n\n- Apply import\n- Cancel\n\nIf there are blockers, do not offer apply; explain what must be fixed first.\n\n### 5. Apply only after confirmation\n\nWhen the user confirms:\n\n- Create target directories with private permissions where possible.\n- Append instruction blocks without duplicating existing imported source blocks.\n- Copy skills without overwriting existing target entries.\n- Do not write MCP entries. Show the prepared `/mcp-config` follow-up command\n and mark MCP import as pending user action.\n- Report exactly what changed and what was skipped.\n- Tell the user to start a new session (for example `/new`) or restart Kimi Code\n for newly imported skills, instructions, and MCP servers to be picked up.\n\n## Output style\n\nBe brief but precise. Use absolute paths in previews and summaries. Prefer a\nsmall table or bullet list over long prose. If nothing is found for a selected\ncategory, say so and do not treat it as an error.\n";
65724
65844
  //#endregion
65725
65845
  //#region ../../packages/agent-core/src/skill/builtin/import-from-cc-codex.ts
@@ -65741,8 +65861,8 @@ const IMPORT_FROM_CC_CODEX_SKILL = {
65741
65861
  }
65742
65862
  };
65743
65863
  //#endregion
65744
- //#region ../../packages/agent-core/src/skill/builtin/mcp-config.md
65745
- var mcp_config_default = "---\nname: mcp-config\ndescription: Configure MCP servers and handle MCP OAuth login.\n---\n\n# Interactive MCP server configuration\n\nThe user invoked this skill through `/mcp-config` or `/skill:mcp-config`.\nEither they want to log into an MCP server that asked for OAuth, or they\nwant to edit the `mcp.json` that lists MCP servers. The work is small and\nlocal — handle it on this turn yourself, no agents or planning todos.\n\nPick the flow from the user's message and your tool list:\n\n- An `mcp__<server>__authenticate` tool is in your list, the user says\n \"log in\" / \"auth\" / \"sign in\", they invoke `/mcp-config login\n <server>`, or they quote a `needs-auth` status → **Login**.\n- Add / edit / remove / list of an `mcp.json` entry → **Config edit**.\n- Bare `/mcp-config` with no `authenticate` tool in your list →\n **Config edit**. If there were a pending login, the authenticate tool\n would be in your list.\n\n## Login\n\nEach MCP server in `needs-auth` exposes one `mcp__<server>__authenticate`\ntool. Call it for the server the user means — its own description owns\nthe OAuth UX (printing the URL, blocking on the callback, reconnecting on\nsuccess). Surface its output verbatim, including the authorization URL\nunchanged; the URL contains state and PKCE parameters that break if\nedited.\n\nIf the user named a server that has no authenticate tool, say so in one\nsentence and stop — do **not** fall into config edit. They're trying to\nlog in to a server that isn't currently waiting for login; quietly\nrewriting `mcp.json` would be the wrong fix. If multiple authenticate\ntools exist and the user didn't name one, ask which.\n\n## Config edit\n\nConfig lives in three files; on key collision, later entries in this\nprecedence order override earlier ones:\n\n- User-global: `~/.kimi-code/mcp.json` (or `$KIMI_CODE_HOME/mcp.json` if\n set). Use for servers you want everywhere.\n- Project-root: `<project root>/.mcp.json`, where project root is found\n by walking up from `<cwd>` to the nearest `.git`. Use for\n Claude-compatible, repo-shared, or cross-agent servers.\n- Project-local: `<cwd>/.kimi-code/mcp.json`. Use for Kimi-specific\n overrides in the current working directory.\n\nMention once that project-root and project-local stdio entries spawn\ncommands at session start, so they should only live in trusted repos.\n\nAll three files wrap their entries the same way:\n\n```json\n{ \"mcpServers\": { \"<name>\": { /* entry */ } } }\n```\n\nA minimal stdio entry needs `command` (+ optional `args`, `env`, `cwd`).\nFor project-root `.mcp.json`, stdio entries run from the project root by\ndefault; relative `cwd` values are resolved against the directory that\ncontains `.mcp.json`.\nA minimal http entry needs `url`; add `bearerTokenEnvVar: \"ENV_NAME\"` for\nservers that authenticate with a static bearer token from the\nenvironment. Servers that use OAuth take no token field — the login flow\nabove handles them. `transport` is inferred from `command` vs `url`, so\nomit it. For less common fields (`enabled`, `startupTimeoutMs`,\n`toolTimeoutMs`, `enabledTools`, `disabledTools`, `headers`) the source of\ntruth is `McpServerStdioConfigSchema` / `McpServerHttpConfigSchema` in\n`packages/agent-core/src/config/schema.ts`.\n\nIf the user only wants to **see** what's configured, read all three files,\nshow a merged view with enough source-path context to inspect or remove a\nserver from the file that actually declared it, and stop — no scope\nprompt, no write.\n\nFor changes, the flow is:\n\n1. **Pick a scope.** Infer it from the user's words when you can\n (global / everywhere / all projects → user-global; root / repo /\n shared / cross-agent / Claude / `.mcp.json` → project-root; cwd /\n current directory / Kimi-specific / `.kimi-code` → project-local). When\n the request is genuinely scope-less, use one `AskUserQuestion` to ask\n user-global vs project-root vs project-local, defaulting to\n user-global. Use plain text for every other question — `AskUserQuestion`\n is a poor fit for free-form input. If the user dismisses the scope\n question, stop; you can't safely guess where they wanted the change.\n2. **Read and announce.** Read the target file (a missing or empty file\n is fine; you'll create `{ \"mcpServers\": {} }`). If JSON parsing fails,\n surface the error verbatim and stop — silently overwriting a broken\n file could destroy work. Then show the user the target path, what's\n currently in it, and the entry you're about to write or delete. This\n is for transparency, not a confirmation gate — the Edit/Write\n permission prompt is the real gate, and your message is what gives\n the user context when that prompt appears. In yolo / afk modes there\n is no prompt, which is those modes' explicit contract.\n3. **Write and tell them how to reload MCP servers.** Preserve unrelated\n entries and the `mcpServers` wrapper. MCP servers load at session\n start, so tell the user to start a new session (for example `/new`) or\n restart `kimi-code` for the change to take effect.\n\n## Secrets\n\nDon't store secrets (tokens, keys, passwords) as literals in\n`mcp.json` — it's a plain config file on disk. http servers should use\n`bearerTokenEnvVar` to reference an env var instead; if a stdio entry\nmust inline one in `env`, warn the user before writing.\n";
65864
+ //#region ../../packages/agent-core/src/skill/builtin/mcp-config.md?raw
65865
+ var mcp_config_default = "---\nname: mcp-config\ndescription: Configure MCP servers and handle MCP OAuth login.\n---\n\n# Interactive MCP server configuration\n\nThe user invoked this skill through `/mcp-config` or `/skill:mcp-config`.\nEither they want to log into an MCP server that asked for OAuth, or they\nwant to edit the `mcp.json` that lists MCP servers. The work is small and\nlocal — handle it on this turn yourself, no agents or planning todos.\n\nPick the flow from the user's message and your tool list:\n\n- An `mcp__<server>__authenticate` tool is in your list, the user says\n \"log in\" / \"auth\" / \"sign in\", they invoke `/mcp-config login\n <server>`, or they quote a `needs-auth` status → **Login**.\n- Add / edit / remove / list of an `mcp.json` entry → **Config edit**.\n- Bare `/mcp-config` with no `authenticate` tool in your list →\n **Config edit**. If there were a pending login, the authenticate tool\n would be in your list.\n\n## Login\n\nEach MCP server in `needs-auth` exposes one `mcp__<server>__authenticate`\ntool. Call it for the server the user means — its own description owns\nthe OAuth UX (printing the URL, blocking on the callback, reconnecting on\nsuccess). Surface its output verbatim, including the authorization URL\nunchanged; the URL contains state and PKCE parameters that break if\nedited.\n\nIf the user named a server that has no authenticate tool, say so in one\nsentence and stop — do **not** fall into config edit. They're trying to\nlog in to a server that isn't currently waiting for login; quietly\nrewriting `mcp.json` would be the wrong fix. If multiple authenticate\ntools exist and the user didn't name one, ask which.\n\n## Config edit\n\nConfig lives in three files; on key collision, later entries in this\nprecedence order override earlier ones.\n\nThe kimi-code runtime resolves the user-global directory as `KIMI_CODE_HOME`\nfirst, falling back to `~/.kimi-code`. Before touching the user-global file,\nresolve the actual directory with Bash so you don't read or write the wrong\none. Check whether `KIMI_CODE_HOME` is set and fall back to `~/.kimi-code`\nwhen it is empty:\n\n```bash\necho \"$KIMI_CODE_HOME\"\necho \"$HOME/.kimi-code\"\n```\n\nUse the first line when it is non-empty; otherwise use the second line. In the\nrest of this skill, `<KIMI_CODE_HOME>` means that resolved data root —\n**never assume `~/.kimi-code`**.\n\n- User-global: `<KIMI_CODE_HOME>/mcp.json`. Use for servers you want\n everywhere.\n- Project-root: `<project root>/.mcp.json`, where project root is found\n by walking up from `<cwd>` to the nearest `.git`. Use for\n Claude-compatible, repo-shared, or cross-agent servers.\n- Project-local: `<cwd>/.kimi-code/mcp.json`. Use for Kimi-specific\n overrides in the current working directory.\n\nMention once that project-root and project-local stdio entries spawn\ncommands at session start, so they should only live in trusted repos.\n\nAll three files wrap their entries the same way:\n\n```json\n{ \"mcpServers\": { \"<name>\": { /* entry */ } } }\n```\n\nA minimal stdio entry needs `command` (+ optional `args`, `env`, `cwd`).\nFor project-root `.mcp.json`, stdio entries run from the project root by\ndefault; relative `cwd` values are resolved against the directory that\ncontains `.mcp.json`.\nA minimal http entry needs `url`; add `bearerTokenEnvVar: \"ENV_NAME\"` for\nservers that authenticate with a static bearer token from the\nenvironment. Servers that use OAuth take no token field — the login flow\nabove handles them. `transport` is inferred from `command` vs `url`, so\nomit it. For less common fields (`enabled`, `startupTimeoutMs`,\n`toolTimeoutMs`, `enabledTools`, `disabledTools`, `headers`) the source of\ntruth is `McpServerStdioConfigSchema` / `McpServerHttpConfigSchema` in\n`packages/agent-core/src/config/schema.ts`.\n\nIf the user only wants to **see** what's configured, read all three files,\nshow a merged view with enough source-path context to inspect or remove a\nserver from the file that actually declared it, and stop — no scope\nprompt, no write.\n\nFor changes, the flow is:\n\n1. **Pick a scope.** Infer it from the user's words when you can\n (global / everywhere / all projects → user-global; root / repo /\n shared / cross-agent / Claude / `.mcp.json` → project-root; cwd /\n current directory / Kimi-specific / `.kimi-code` → project-local). When\n the request is genuinely scope-less, use one `AskUserQuestion` to ask\n user-global vs project-root vs project-local, defaulting to\n user-global. Use plain text for every other question — `AskUserQuestion`\n is a poor fit for free-form input. If the user dismisses the scope\n question, stop; you can't safely guess where they wanted the change.\n2. **Read and announce.** Read the target file (a missing or empty file\n is fine; you'll create `{ \"mcpServers\": {} }`). If JSON parsing fails,\n surface the error verbatim and stop — silently overwriting a broken\n file could destroy work. Then show the user the target path, what's\n currently in it, and the entry you're about to write or delete. This\n is for transparency, not a confirmation gate — the Edit/Write\n permission prompt is the real gate, and your message is what gives\n the user context when that prompt appears. In yolo / afk modes there\n is no prompt, which is those modes' explicit contract.\n3. **Write and tell them how to reload MCP servers.** Preserve unrelated\n entries and the `mcpServers` wrapper. MCP servers load at session\n start, so tell the user to start a new session (for example `/new`) or\n restart `kimi-code` for the change to take effect.\n\n## Secrets\n\nDon't store secrets (tokens, keys, passwords) as literals in\n`mcp.json` — it's a plain config file on disk. http servers should use\n`bearerTokenEnvVar` to reference an env var instead; if a stdio entry\nmust inline one in `env`, warn the user before writing.\n";
65746
65866
  //#endregion
65747
65867
  //#region ../../packages/agent-core/src/skill/builtin/mcp-config.ts
65748
65868
  const PSEUDO_PATH$1 = "builtin://mcp-config";
@@ -65763,13 +65883,13 @@ const MCP_CONFIG_SKILL = {
65763
65883
  }
65764
65884
  };
65765
65885
  //#endregion
65766
- //#region ../../packages/agent-core/src/skill/builtin/sub-skill/consolidate/SKILL.md
65767
- var SKILL_default$2 = "---\nname: sub-skill.consolidate\ndescription: Apply an approved sub-skill grouping by moving user-specified skills into a parent bundle, with timestamped backups of every modified directory.\ndisable-model-invocation: true\n---\n\n# Consolidate sub-skills (`sub-skill.consolidate`)\n\nExecute the reorganization by moving user-specified skills into a parent bundle, forming a sub-skill hierarchy.\n\n## When to use\n\n- The user has approved a grouping proposal (typically from `sub-skill.review`) and wants to apply it.\n- Migrating standalone skills into a new or existing parent bundle.\n\n## Process\n\n1. **Confirm the plan.** Restate which skills will move and where, and ask the user to confirm **before** making any file changes.\n2. **Back up every original skill directory.** Before moving anything, create a timestamped backup of each skill directory that will be modified.\n - For a skill at `<root>/<skill-name>/SKILL.md`, back up the entire `<skill-name>` directory:\n ```bash\n cp -r <skill-name> \"<skill-name>.$(date +%Y%m%d-%H%M%S).bak\"\n ```\n - Keep all backups; never overwrite an existing backup file.\n3. **Create or update the parent bundle.**\n - If the parent does not exist, create `<parent-name>/SKILL.md` with `has-sub-skill: true` in the frontmatter.\n - If the parent already exists, ensure its frontmatter includes `has-sub-skill: true`.\n4. **Move child skills into the parent.** Move each child skill's entire directory under the parent bundle.\n - Example: `web-search/` → `web-research/web-search/`\n5. **Verify the result.** List the new directory structure and confirm each moved skill still has a valid `SKILL.md` with required frontmatter (`name` and `description`).\n6. **Report the change.** Summarize what was moved, the new structure, and where backups are located.\n\n## Don'ts\n\n- **Never move skills without backing up first.**\n- **Never overwrite an existing backup** — always use a fresh timestamped suffix.\n- **Don't drop frontmatter or payload files** during the move; the entire directory must be preserved.\n- **Don't create deeply nested hierarchies** (3+ levels) unless the user explicitly requests it.\n";
65886
+ //#region ../../packages/agent-core/src/skill/builtin/sub-skill/consolidate/SKILL.md?raw
65887
+ var SKILL_default$2 = "---\nname: consolidate\ndescription: Apply an approved sub-skill grouping by moving user-specified skills into a parent bundle, with timestamped backups of every modified directory.\ndisable-model-invocation: true\n---\n\n# Consolidate sub-skills (`sub-skill.consolidate`)\n\nExecute the reorganization by moving user-specified skills into a parent bundle, forming a sub-skill hierarchy.\n\n## When to use\n\n- The user has approved a grouping proposal (typically from `sub-skill.review`) and wants to apply it.\n- Migrating standalone skills into a new or existing parent bundle.\n\n## Process\n\n1. **Confirm the plan.** Restate which skills will move and where, and ask the user to confirm **before** making any file changes.\n2. **Back up every original skill directory.** Before moving anything, create a timestamped backup of each skill directory that will be modified.\n - For a skill at `<root>/<skill-name>/SKILL.md`, back up the entire `<skill-name>` directory:\n ```bash\n cp -r <skill-name> \"<skill-name>.$(date +%Y%m%d-%H%M%S).bak\"\n ```\n - Keep all backups; never overwrite an existing backup file.\n3. **Create or update the parent bundle.**\n - If the parent does not exist, create `<parent-name>/SKILL.md` with `has-sub-skill: true` in the frontmatter.\n - If the parent already exists, ensure its frontmatter includes `has-sub-skill: true`.\n4. **Move child skills into the parent.** Move each child skill's entire directory under the parent bundle.\n - Example: `web-search/` → `web-research/web-search/`\n5. **Keep documentation directory alignment.** When moving documentation, references, examples, assets, or other payload directories, align them with the new skill directory layout.\n - Preserve relative links from `SKILL.md` to files such as `references/`, `assets/`, `examples/`, or templates.\n - If a child skill moves from `<root>/<child>/` to `<root>/<parent>/<child>/`, its documentation payload should move with that child unless the approved plan says otherwise.\n - Do not leave documentation in the old location or merge unrelated documentation directories together.\n6. **Verify the result.** List the new directory structure and confirm each moved skill still has a valid `SKILL.md` with required frontmatter (`name` and `description`). Check documentation directory alignment and relative links after the move.\n7. **Report the change.** Summarize what was moved, the new structure, any documentation directories that moved, and where backups are located.\n\n## Don'ts\n\n- **Never move skills without backing up first.**\n- **Never overwrite an existing backup** — always use a fresh timestamped suffix.\n- **Don't drop frontmatter or payload files** during the move; the entire directory must be preserved.\n- **Don't break documentation directory alignment** — references, assets, examples, and templates must stay aligned with the skill directory that uses them.\n- **Don't create deeply nested hierarchies** (3+ levels) unless the user explicitly requests it.\n";
65768
65888
  //#endregion
65769
- //#region ../../packages/agent-core/src/skill/builtin/sub-skill/review/SKILL.md
65770
- var SKILL_default$1 = "---\nname: sub-skill.review\ndescription: Analyze the available skill set and recommend candidate groups that could be consolidated into sub-skill bundles. Read-only — proposes a plan, does not move files.\ndisable-model-invocation: true\n---\n\n# Review sub-skills (`sub-skill.review`)\n\nAnalyze the current skill inventory and identify candidate groups that could be consolidated into sub-skill bundles. This sub-skill is **read-only**: it produces a proposal for the user to review before any file changes are made by `sub-skill.consolidate`.\n\n## When to use\n\n- The user asks to review, reorganize, or audit the skill inventory.\n- There are many loosely-related skills that might benefit from hierarchical grouping.\n- The user wants to evaluate whether a new skill should be a sub-skill of an existing parent.\n\n## Process\n\n1. **List the current inventory.** Use the skill registry (or scan the configured skill roots) to get a full list of skill names, descriptions, and source scopes.\n2. **Categorize by domain.** Group skills by functional domain — e.g. file operations, web tools, collaboration, observability.\n3. **Detect coupling.** Identify skills that are frequently used together or share similar `whenToUse` conditions.\n4. **Flag granularity issues.** Call out skills that are too fine-grained (e.g. one skill per CLI flag) or too broad to land in any single domain.\n5. **Propose sub-skill structures.** For each candidate group, list:\n - The parent skill name and a one-line description.\n - The children that should move under it.\n - Whether the parent needs `has-sub-skill: true` (it does, if children should be discovered).\n6. **Output a summary report.** Present findings as a concise grouped list with rationale, and stop. Do **not** edit any file — that's `sub-skill.consolidate`'s job.\n\n## Criteria for a good sub-skill grouping\n\n- **Shared context.** Children operate within the same domain or workflow.\n- **Composable entry point.** The parent gives a natural top-level handle; children handle specifics.\n- **Shallow nesting.** Prefer 2 levels (parent → child). Avoid 3+ unless strictly necessary.\n- **Backward compatibility.** Existing skill names should ideally remain discoverable.\n\n## Example output format\n\n```\nProposed sub-skill: web-research\n - Parent: web-research (has-sub-skill: true)\n - Children:\n - web-search → move under web-research/search\n - fetch-url → move under web-research/fetch\n - Rationale: Both deal with online information retrieval and are often chained together.\n```\n";
65889
+ //#region ../../packages/agent-core/src/skill/builtin/sub-skill/review/SKILL.md?raw
65890
+ var SKILL_default$1 = "---\nname: review\ndescription: Analyze the available skill set and recommend candidate groups that could be consolidated into sub-skill bundles. Read-only — proposes a plan, does not move files.\ndisable-model-invocation: true\n---\n\n# Review sub-skills (`sub-skill.review`)\n\nAnalyze the current skill inventory and identify candidate groups that could be consolidated into sub-skill bundles. This sub-skill is **read-only**: it produces a proposal for the user to review before any file changes are made by `sub-skill.consolidate`.\n\n## When to use\n\n- The user asks to review, reorganize, or audit the skill inventory.\n- There are many loosely-related skills that might benefit from hierarchical grouping.\n- The user wants to evaluate whether a new skill should be a sub-skill of an existing parent.\n\n## Process\n\n1. **List the current inventory.** Use the skill registry (or scan the configured skill roots) to get a full list of skill names, descriptions, and source scopes.\n2. **Categorize by domain.** Group skills by functional domain — e.g. file operations, web tools, collaboration, observability.\n3. **Detect coupling.** Identify skills that are frequently used together or share similar `whenToUse` conditions.\n4. **Flag granularity issues.** Call out skills that are too fine-grained (e.g. one skill per CLI flag) or too broad to land in any single domain.\n5. **Propose sub-skill structures.** For each candidate group, list:\n - The parent skill name and a one-line description.\n - The children that should move under it.\n - Any documentation, reference, example, asset, or template directories that must move with each child so the final directory layout stays aligned.\n - Whether the parent needs `has-sub-skill: true` (it does, if children should be discovered).\n6. **Output a summary report.** Present findings as a concise grouped list with rationale, and stop. Do **not** edit any file — that's `sub-skill.consolidate`'s job.\n\n## Criteria for a good sub-skill grouping\n\n- **Shared context.** Children operate within the same domain or workflow.\n- **Composable entry point.** The parent gives a natural top-level handle; children handle specifics.\n- **Shallow nesting.** Prefer 2 levels (parent → child). Avoid 3+ unless strictly necessary.\n- **Backward compatibility.** Existing skill names should ideally remain discoverable.\n\n## Example output format\n\n```\nProposed sub-skill: web-research\n - Parent: web-research (has-sub-skill: true)\n - Children:\n - web-search → move under web-research/search\n - fetch-url → move under web-research/fetch\n - Documentation alignment: move each child's references/examples/assets with that child.\n - Rationale: Both deal with online information retrieval and are often chained together.\n```\n";
65771
65891
  //#endregion
65772
- //#region ../../packages/agent-core/src/skill/builtin/sub-skill/SKILL.md
65892
+ //#region ../../packages/agent-core/src/skill/builtin/sub-skill/SKILL.md?raw
65773
65893
  var SKILL_default = "---\nname: sub-skill\ndescription: Discover and reorganize the skill inventory into hierarchical sub-skill bundles. Use when the user asks to review, group, or consolidate skills into a parent bundle.\ndisable-model-invocation: true\nhas-sub-skill: true\n---\n\n# Sub-skill\n\nContainer skill for analyzing the local skill inventory and reorganizing it into hierarchical sub-skill bundles (`has-sub-skill: true` parents with children inside).\n\n## When to use\n\n- The user asks to review, reorganize, or consolidate skills.\n- There are many loosely-related skills that might benefit from hierarchical grouping.\n- The user wants to evaluate whether a new skill should be a sub-skill of an existing one.\n\n## Sub-skills\n\n- **`sub-skill.review`** — Analyze the current inventory and propose candidate sub-skill groupings.\n- **`sub-skill.consolidate`** — Apply an approved grouping by moving skills into a parent bundle, with timestamped backups.\n\nThe usual flow is `sub-skill.review` first (read-only proposal), then `sub-skill.consolidate` after the user approves a plan. Never run consolidate without an explicit go-ahead from the user.\n";
65774
65894
  //#endregion
65775
65895
  //#region ../../packages/agent-core/src/skill/builtin/sub-skill.ts
@@ -65782,6 +65902,7 @@ function makeBuiltin(body, dirName, pseudoPath, extraMetadata = {}) {
65782
65902
  });
65783
65903
  return {
65784
65904
  ...parsed,
65905
+ name: dirName,
65785
65906
  path: pseudoPath,
65786
65907
  dir: pseudoPath,
65787
65908
  metadata: {
@@ -65795,11 +65916,17 @@ const SUB_SKILL_PARENT = makeBuiltin(SKILL_default, "sub-skill", "builtin://sub-
65795
65916
  disableModelInvocation: true,
65796
65917
  "has-sub-skill": true
65797
65918
  });
65798
- const SUB_SKILL_REVIEW = makeBuiltin(SKILL_default$1, "sub-skill.review", "builtin://sub-skill/review", { disableModelInvocation: true });
65799
- const SUB_SKILL_CONSOLIDATE = makeBuiltin(SKILL_default$2, "sub-skill.consolidate", "builtin://sub-skill/consolidate", { disableModelInvocation: true });
65919
+ const SUB_SKILL_REVIEW = makeBuiltin(SKILL_default$1, "sub-skill.review", "builtin://sub-skill/review", {
65920
+ disableModelInvocation: true,
65921
+ isSubSkill: true
65922
+ });
65923
+ const SUB_SKILL_CONSOLIDATE = makeBuiltin(SKILL_default$2, "sub-skill.consolidate", "builtin://sub-skill/consolidate", {
65924
+ disableModelInvocation: true,
65925
+ isSubSkill: true
65926
+ });
65800
65927
  //#endregion
65801
- //#region ../../packages/agent-core/src/skill/builtin/update-config.md
65802
- var update_config_default = "---\nname: update-config\ndescription: Inspect or edit kimi-code's own config — `config.toml` (model, provider, permission, hooks) and `tui.toml` (theme, editor, notifications, auto-update). Use when the user asks what a setting does or wants to change one.\n---\n\n# Configure kimi-code (update-config)\n\nHelp the user inspect, change, and validate kimi-code's configuration files. The files are **TOML** with **snake_case** keys.\n\n## The two config files\n\nkimi-code has two TOML config files, both under `~/.kimi-code/` (or under `KIMI_CODE_HOME` when set), both snake_case, but with different ownership — decide which one the user means before doing anything:\n\n- **`config.toml`** — agent / runtime settings: `default_model`, `providers`, `models`, `thinking`, `permission`, `hooks`, `loop_control`, etc.\n- **`tui.toml`** — terminal-UI / client preferences: `theme`, `[editor].command`, `[notifications]`, `[upgrade].auto_install` (auto-update). These can usually also be changed with the interactive commands `/config`, `/theme`, `/editor`, which is easier — prefer pointing the user at those.\n\nThe \"read → copy → Edit → validate → back up → overwrite\" flow below applies to both files; only **which reload command applies** differs (see Capability 4).\n\n## Prerequisite 1: the official docs are the single source of truth\n\nBefore touching any config, use **FetchURL** to fetch the official config docs as the one authoritative reference for fields (key names, types, allowed values, owning section):\n\n```\nhttps://moonshotai.github.io/kimi-code/en/configuration/config-files.html\n```\n\n- Use the **snake_case key names and sections exactly as documented** — don't invent them, don't guess camelCase.\n- If FetchURL is unavailable or the fetch fails, tell the user plainly that you can't reach the online docs, and ask them to paste the relevant section or confirm whether to proceed from what you already know. **Never edit blindly without an authoritative reference.**\n\n## Prerequisite 2: read the target file before any change\n\nBefore any modification, use **Read** on the target config file (decide whether it's `config.toml` or `tui.toml` per the above):\n\n- Location: `~/.kimi-code/config.toml` or `~/.kimi-code/tui.toml` (under `$KIMI_CODE_HOME/` when set). For other scopes/files, defer to the official docs.\n- A missing or empty file is fine — you'll create a minimal skeleton later.\n- If the file exists but **fails to parse as TOML**, report the error verbatim and **stop** — never overwrite a broken file in place (it could destroy the user's existing config).\n\n---\n\n## Capability 1: explain configuration (read-only, no file changes)\n\nWhen the user asks \"what config is there\", \"what does this setting do\", or \"how do I use it\":\n\n1. Fetch the official docs (Prerequisite 1).\n2. Read the current `config.toml` (Prerequisite 2).\n3. Answer against both: list the relevant sections / keys, what each is for, **current value vs default**, and the allowed-value range; say which file and section each lives in.\n4. Present it as a compact grouped list or table. **Stay read-only — write no files.**\n\n## Capability 2: make changes for the user (copy → Edit → validate → back up → overwrite)\n\nDon't edit the target file in place, and **don't rewrite it from scratch** — instead copy it, Edit the copy, and keep the original out of any broken state the whole time:\n\n1. **Clarify intent**: which key, what value, and which file (`config.toml` or `tui.toml`). Ask in one line if ambiguous; for discrete choices (e.g. scope) AskUserQuestion is fine, but use plain questions for free-form input.\n2. **Read the target file** (Prerequisite 2): Read it to understand the current state and confirm it parses.\n3. **Copy out a candidate (do not create from scratch)**: use **Bash** to copy the target verbatim — `cp config.toml config-new.toml` (same directory, `-new` suffix; for tui.toml, `cp tui.toml tui-new.toml`). **Leave the original untouched for now.**\n - Only when the target doesn't exist (nothing to copy) should you use **Write** to create a minimal skeleton candidate (e.g. just the comment line `# ~/.kimi-code/config.toml`).\n4. **Edit the candidate**: use the **Edit** tool on the candidate to **change/add only the target key** — never rewrite the whole file. That way every existing section, entry, comment, and bit of formatting stays exactly as-is; only what should change changes. The candidate is identical to the original, so use the content you read in step 2 to locate the Edit anchor. Check the change against the official docs (key / section / value type / allowed values, snake_case).\n5. **Validate the candidate** (see Capability 3, via `kimi doctor`). **If anything fails, keep Editing the candidate and re-validate, looping until it all passes.**\n6. **Back up and overwrite** (only after validation fully passes):\n - **Back up the old file — always create a new timestamped backup, keep all of them, never overwrite an existing backup.** Copy this exactly with **Bash** (for config.toml): `cp config.toml \"config.toml.$(date +%Y%m%d-%H%M%S).bak\"`; for tui.toml: `cp tui.toml \"tui.toml.$(date +%Y%m%d-%H%M%S).bak\"`. Skip the backup only if the target didn't exist.\n - Overwrite with the candidate: `mv config-new.toml config.toml`.\n - If reload errors after the overwrite, the user can recover from **the most recent timestamped backup**.\n7. Tell the user how to apply it (see Capability 4).\n\n## Capability 3: validate the candidate file (must pass before overwrite)\n\nUse **`kimi doctor`** to validate the candidate you wrote — it doesn't start the TUI and doesn't modify any file; it runs kimi's own parser + schema (syntax and schema together), so it's the authoritative check. Pick the subcommand by which file you changed, and pass the **candidate** path explicitly:\n\n- changed `config.toml` → `kimi doctor config <config-new.toml path>`\n- changed `tui.toml` → `kimi doctor tui <tui-new.toml path>`\n\nWhen a path is passed explicitly the file must exist (your candidate does, so that's fine). **Exit code 0 = pass (valid or skipped); non-zero = a specified file is missing or the config is invalid** — show the output verbatim, fix the candidate, and re-run, looping until it's 0.\n\nThen do two checks `kimi doctor` can't:\n\n1. **Cross-check values against the official docs** (single source of truth): are the key / section / enum values as documented, and snake_case? doctor guarantees \"schema-valid\", but \"valid yet not what the user wanted\" (e.g. a misspelled model alias) needs the docs.\n2. **Completeness**: every existing entry is still present (the candidate fully replaces the target — a dropped line is a deletion).\n\n> To also check whether the currently **active** config is OK overall, run `kimi doctor` with no path (it checks the default `config.toml` + `tui.toml`, showing a missing one as skipped).\n\n## Capability 4: tell the user how to apply changes\n\nOnce local validation passes, tell the user how to make the change take effect — **the reload command depends on which file you changed**:\n\n- changed **`config.toml`** → run **`/reload`** in the TUI (reloads the session and applies `config.toml`; it also reloads `tui.toml`).\n- changed **`tui.toml`** → run **`/reload-tui`** (reloads only `tui.toml`, lighter); `/reload` works too (reloads both).\n- changed both → a single **`/reload`** covers it.\n\nNote: `/reload` is available **only when idle** — if a reply is streaming, press Esc / Ctrl-C to stop first. `kimi doctor` already validated the schema before the overwrite, so reload should apply cleanly; if it still errors, follow the message to fix it or recover from the most recent timestamped backup. If you don't want to reload now, the **next new session** picks it up automatically.\n\n## Don'ts\n\n- **Always back up before overwriting**, with a **timestamped name and all history kept** — don't skip the backup, don't keep only a single `.bak`, don't overwrite an old backup.\n- Don't drop unrelated entries (the candidate fully replaces the target — a dropped line is a deletion).\n- When you can't reach the docs / have no authoritative reference, don't edit by guessing.\n";
65928
+ //#region ../../packages/agent-core/src/skill/builtin/update-config.md?raw
65929
+ var update_config_default = "---\nname: update-config\ndescription: Inspect or edit kimi-code's own config — `config.toml` (model, provider, permission, hooks) and `tui.toml` (theme, editor, notifications, auto-update). Use when the user asks what a setting does or wants to change one.\n---\n\n# Configure kimi-code (update-config)\n\nHelp the user inspect, change, and validate kimi-code's configuration files. The files are **TOML** with **snake_case** keys.\n\n## The two config files\n\nkimi-code has two TOML config files, both under `<KIMI_CODE_HOME>/`, both snake_case, but with different ownership — decide which one the user means before doing anything.\n\nThe runtime resolves the data directory as `KIMI_CODE_HOME` first, falling back to `~/.kimi-code`. Before doing anything, resolve the actual directory with Bash so you don't write to the wrong place. Check whether `KIMI_CODE_HOME` is set and fall back to `~/.kimi-code` when it is empty:\n\n```bash\necho \"$KIMI_CODE_HOME\"\necho \"$HOME/.kimi-code\"\n```\n\nUse the first line when it is non-empty; otherwise use the second line. In the rest of this skill, `<KIMI_CODE_HOME>` means that resolved root — **never assume `~/.kimi-code`**.\n\n- **`config.toml`** — agent / runtime settings: `default_model`, `providers`, `models`, `thinking`, `permission`, `hooks`, `loop_control`, etc.\n- **`tui.toml`** — terminal-UI / client preferences: `theme`, `[editor].command`, `[notifications]`, `[upgrade].auto_install` (auto-update). These can usually also be changed with the interactive commands `/config`, `/theme`, `/editor`, which is easier — prefer pointing the user at those.\n\nThe \"read → copy → Edit → validate → back up → overwrite\" flow below applies to both files; only **which reload command applies** differs (see Capability 4).\n\n## Prerequisite 1: the official docs are the single source of truth\n\nBefore touching any config, use **FetchURL** to fetch the official config docs as the one authoritative reference for fields (key names, types, allowed values, owning section):\n\n```\nhttps://moonshotai.github.io/kimi-code/en/configuration/config-files.html\n```\n\n- Use the **snake_case key names and sections exactly as documented** — don't invent them, don't guess camelCase.\n- If FetchURL is unavailable or the fetch fails, tell the user plainly that you can't reach the online docs, and ask them to paste the relevant section or confirm whether to proceed from what you already know. **Never edit blindly without an authoritative reference.**\n\n## Prerequisite 2: read the target file before any change\n\nBefore any modification, use **Read** on the target config file (decide whether it's `config.toml` or `tui.toml` per the above):\n\n- Location: `<KIMI_CODE_HOME>/config.toml` or `<KIMI_CODE_HOME>/tui.toml`. For other scopes/files, defer to the official docs.\n- A missing or empty file is fine — you'll create a minimal skeleton later.\n- If the file exists but **fails to parse as TOML**, report the error verbatim and **stop** — never overwrite a broken file in place (it could destroy the user's existing config).\n\n---\n\n## Capability 1: explain configuration (read-only, no file changes)\n\nWhen the user asks \"what config is there\", \"what does this setting do\", or \"how do I use it\":\n\n1. Fetch the official docs (Prerequisite 1).\n2. Read the current `config.toml` (Prerequisite 2).\n3. Answer against both: list the relevant sections / keys, what each is for, **current value vs default**, and the allowed-value range; say which file and section each lives in.\n4. Present it as a compact grouped list or table. **Stay read-only — write no files.**\n\n## Capability 2: make changes for the user (copy → Edit → validate → back up → overwrite)\n\nDon't edit the target file in place, and **don't rewrite it from scratch** — instead copy it, Edit the copy, and keep the original out of any broken state the whole time:\n\n1. **Clarify intent**: which key, what value, and which file (`config.toml` or `tui.toml`). Ask in one line if ambiguous; for discrete choices (e.g. scope) AskUserQuestion is fine, but use plain questions for free-form input.\n2. **Read the target file** (Prerequisite 2): Read it to understand the current state and confirm it parses.\n3. **Copy out a candidate (do not create from scratch)**: use **Bash** to copy the target verbatim — `cp config.toml config-new.toml` (same directory, `-new` suffix; for tui.toml, `cp tui.toml tui-new.toml`). **Leave the original untouched for now.**\n - Only when the target doesn't exist (nothing to copy) should you use **Write** to create a minimal skeleton candidate (e.g. just the comment line `# <KIMI_CODE_HOME>/config.toml`).\n4. **Edit the candidate**: use the **Edit** tool on the candidate to **change/add only the target key** — never rewrite the whole file. That way every existing section, entry, comment, and bit of formatting stays exactly as-is; only what should change changes. The candidate is identical to the original, so use the content you read in step 2 to locate the Edit anchor. Check the change against the official docs (key / section / value type / allowed values, snake_case).\n5. **Validate the candidate** (see Capability 3, via `kimi doctor`). **If anything fails, keep Editing the candidate and re-validate, looping until it all passes.**\n6. **Back up and overwrite** (only after validation fully passes):\n - **Back up the old file — always create a new timestamped backup, keep all of them, never overwrite an existing backup.** Copy this exactly with **Bash** (for config.toml): `cp config.toml \"config.toml.$(date +%Y%m%d-%H%M%S).bak\"`; for tui.toml: `cp tui.toml \"tui.toml.$(date +%Y%m%d-%H%M%S).bak\"`. Skip the backup only if the target didn't exist.\n - Overwrite with the candidate: `mv config-new.toml config.toml`.\n - If reload errors after the overwrite, the user can recover from **the most recent timestamped backup**.\n7. Tell the user how to apply it (see Capability 4).\n\n## Capability 3: validate the candidate file (must pass before overwrite)\n\nUse **`kimi doctor`** to validate the candidate you wrote — it doesn't start the TUI and doesn't modify any file; it runs kimi's own parser + schema (syntax and schema together), so it's the authoritative check. Pick the subcommand by which file you changed, and pass the **candidate** path explicitly:\n\n- changed `config.toml` → `kimi doctor config <config-new.toml path>`\n- changed `tui.toml` → `kimi doctor tui <tui-new.toml path>`\n\nWhen a path is passed explicitly the file must exist (your candidate does, so that's fine). **Exit code 0 = pass (valid or skipped); non-zero = a specified file is missing or the config is invalid** — show the output verbatim, fix the candidate, and re-run, looping until it's 0.\n\nThen do two checks `kimi doctor` can't:\n\n1. **Cross-check values against the official docs** (single source of truth): are the key / section / enum values as documented, and snake_case? doctor guarantees \"schema-valid\", but \"valid yet not what the user wanted\" (e.g. a misspelled model alias) needs the docs.\n2. **Completeness**: every existing entry is still present (the candidate fully replaces the target — a dropped line is a deletion).\n\n> To also check whether the currently **active** config is OK overall, run `kimi doctor` with no path (it checks the default `config.toml` + `tui.toml`, showing a missing one as skipped).\n\n## Capability 4: tell the user how to apply changes\n\nOnce local validation passes, tell the user how to make the change take effect — **the reload command depends on which file you changed**:\n\n- changed **`config.toml`** → run **`/reload`** in the TUI (reloads the session and applies `config.toml`; it also reloads `tui.toml`).\n- changed **`tui.toml`** → run **`/reload-tui`** (reloads only `tui.toml`, lighter); `/reload` works too (reloads both).\n- changed both → a single **`/reload`** covers it.\n\nNote: `/reload` is available **only when idle** — if a reply is streaming, press Esc / Ctrl-C to stop first. `kimi doctor` already validated the schema before the overwrite, so reload should apply cleanly; if it still errors, follow the message to fix it or recover from the most recent timestamped backup. If you don't want to reload now, the **next new session** picks it up automatically.\n\n## Don'ts\n\n- **Always back up before overwriting**, with a **timestamped name and all history kept** — don't skip the backup, don't keep only a single `.bak`, don't overwrite an old backup.\n- Don't drop unrelated entries (the candidate fully replaces the target — a dropped line is a deletion).\n- When you can't reach the docs / have no authoritative reference, don't edit by guessing.\n";
65803
65930
  //#endregion
65804
65931
  //#region ../../packages/agent-core/src/skill/builtin/update-config.ts
65805
65932
  const PSEUDO_PATH = "builtin://update-config";
@@ -65864,7 +65991,7 @@ async function discoverSkills(options) {
65864
65991
  const warn = options.onWarning ?? (() => {});
65865
65992
  const skip = options.onSkippedByPolicy ?? (() => {});
65866
65993
  const byName = /* @__PURE__ */ new Map();
65867
- async function walkSkillDir(dirPath, root, isTopLevel, depth) {
65994
+ async function walkSkillDir(dirPath, root, isTopLevel, depth, subSkillParentName) {
65868
65995
  if (depth > MAX_SKILL_SCAN_DEPTH) return;
65869
65996
  let entries;
65870
65997
  try {
@@ -65881,7 +66008,7 @@ async function discoverSkills(options) {
65881
66008
  if (entry === "node_modules" || entry.startsWith(".")) continue;
65882
66009
  if (await isDir(entryPath)) subdirs.push(entry);
65883
66010
  }
65884
- const allowedSubSkillBundles = /* @__PURE__ */ new Set();
66011
+ const allowedSubSkillBundles = /* @__PURE__ */ new Map();
65885
66012
  for (const entry of directorySkills) {
65886
66013
  const skill = await parseAndRegister({
65887
66014
  parse,
@@ -65891,9 +66018,10 @@ async function discoverSkills(options) {
65891
66018
  root,
65892
66019
  onDiscoveredSkill: options.onDiscoveredSkill,
65893
66020
  warn,
65894
- skip
66021
+ skip,
66022
+ subSkillParentName
65895
66023
  });
65896
- if (skill !== void 0 && hasSubSkillEnabled(skill)) allowedSubSkillBundles.add(entry);
66024
+ if (skill !== void 0 && hasSubSkillEnabled(skill)) allowedSubSkillBundles.set(entry, skill.name);
65897
66025
  }
65898
66026
  if (isTopLevel) {
65899
66027
  if (root.plugin !== void 0) {
@@ -65933,7 +66061,8 @@ async function discoverSkills(options) {
65933
66061
  }
65934
66062
  for (const entry of subdirs) {
65935
66063
  if (directorySkills.has(entry) && !allowedSubSkillBundles.has(entry)) continue;
65936
- await walkSkillDir(path.join(dirPath, entry), root, false, depth + 1);
66064
+ const allowedSubSkillParentName = allowedSubSkillBundles.get(entry);
66065
+ await walkSkillDir(path.join(dirPath, entry), root, false, depth + 1, allowedSubSkillParentName ?? subSkillParentName);
65937
66066
  }
65938
66067
  }
65939
66068
  for (const root of options.roots) await walkSkillDir(root.path, root, true, 0);
@@ -65997,11 +66126,20 @@ async function pushProvidedRoot(out, root, isDir, realpath) {
65997
66126
  }
65998
66127
  async function parseAndRegister(input) {
65999
66128
  try {
66000
- const skill = await input.parse({
66129
+ const parsed = await input.parse({
66001
66130
  skillMdPath: input.skillMdPath,
66002
66131
  skillDirName: input.skillDirName,
66003
66132
  source: input.root.source
66004
66133
  });
66134
+ const subSkillParentName = input.subSkillParentName;
66135
+ const skill = subSkillParentName !== void 0 ? {
66136
+ ...parsed,
66137
+ name: qualifySubSkillName(subSkillParentName, parsed.name),
66138
+ metadata: {
66139
+ ...parsed.metadata,
66140
+ isSubSkill: true
66141
+ }
66142
+ } : parsed;
66005
66143
  const discovered = input.root.plugin === void 0 ? skill : {
66006
66144
  ...skill,
66007
66145
  plugin: input.root.plugin
@@ -66009,7 +66147,7 @@ async function parseAndRegister(input) {
66009
66147
  input.onDiscoveredSkill?.(discovered);
66010
66148
  const key = normalizeSkillName(discovered.name);
66011
66149
  if (!input.byName.has(key)) input.byName.set(key, discovered);
66012
- return skill;
66150
+ return discovered;
66013
66151
  } catch (error) {
66014
66152
  if (error instanceof UnsupportedSkillTypeError) input.skip({
66015
66153
  path: input.skillMdPath,
@@ -66021,6 +66159,10 @@ async function parseAndRegister(input) {
66021
66159
  return;
66022
66160
  }
66023
66161
  }
66162
+ function qualifySubSkillName(parentName, skillName) {
66163
+ if (skillName === parentName || skillName.startsWith(`${parentName}.`)) return skillName;
66164
+ return `${parentName}.${skillName}`;
66165
+ }
66024
66166
  function hasSubSkillEnabled(skill) {
66025
66167
  const nested = skill.metadata["metadata"];
66026
66168
  const nestedFlag = typeof nested === "object" && nested !== null ? nested["has-sub-skill"] === true || nested["hasSubSkill"] === true : false;
@@ -66149,7 +66291,7 @@ var SkillRegistry = class {
66149
66291
  }
66150
66292
  getModelSkillListing() {
66151
66293
  const lines = ["DISREGARD any earlier skill listings. Current available skills:"];
66152
- const listing = renderGroupedSkills(this.listInvocableSkills(), formatModelSkill);
66294
+ const listing = renderGroupedSkills(this.listInvocableSkills().filter((skill) => skill.metadata.isSubSkill !== true), formatModelSkill);
66153
66295
  if (listing.length > 0) lines.push(listing);
66154
66296
  return lines.length === 1 ? "" : lines.join("\n");
66155
66297
  }
@@ -66292,10 +66434,10 @@ var SkillManager = class {
66292
66434
  }
66293
66435
  };
66294
66436
  //#endregion
66295
- //#region ../../packages/agent-core/src/agent/swarm/enter-reminder.md
66437
+ //#region ../../packages/agent-core/src/agent/swarm/enter-reminder.md?raw
66296
66438
  var enter_reminder_default = "## Swarm Mode\n\nYou are now in \"agent swarm\" mode. The user may send tasks that require a large number of parallel subagents.\n\n## Workflow\n\nYou do not need to use TodoList to record this workflow.\n\n1. First, you may need to do a small amount of exploratory work before deciding how to divide the task across subagents. You may not need subagents during this exploratory phase.\n\n2. After exploring, if you are convinced no subagent is needed to complete the task, tell the user why and wait for further instructions; otherwise, continue with the appropriate delegation.\n\n3. Once you have enough context, do not handle the main work yourself. Use AgentSwarm with a `prompt_template` containing the `{{item}}` placeholder and an `items` array for the requested or appropriate number of subagents, partitioning the problem so each item gives one subagent a distinct part of the work. Pass `subagent_type` when the whole swarm should use a non-default subagent profile.\n\n## Coordination\n\n- Give each subagent a distinct scope of work.\n- Avoid duplicating work across subagents.\n- Avoid assigning conflicting changes or responsibilities to different subagents.\n- Remember that subagents have your full capabilities. Do not overload their prompts with excessive detail; only describe the necessary background and each subagent's specific task.\n- Unless the user explicitly specifies a lower limit, do not try to conserve the number of agents. AgentSwarm supports up to 128 subagents and queues launches automatically, so decompose work as finely as possible while keeping subagent responsibilities non-conflicting; combine tasks only when they are genuinely inseparable. If the subagents only need to read, inspect, or report back without making changes, their scopes may overlap slightly.\n";
66297
66439
  //#endregion
66298
- //#region ../../packages/agent-core/src/agent/swarm/exit-reminder.md
66440
+ //#region ../../packages/agent-core/src/agent/swarm/exit-reminder.md?raw
66299
66441
  var exit_reminder_default = "## Swarm Mode Ended\n\nSwarm Mode has ended. You are no longer required to follow the Swarm Mode workflow.\n\nThe user's next request is likely to be a regular request that does not need AgentSwarm. If the request still benefits from parallel subagents, you may call the AgentSwarm tool, but decide from the new request itself rather than the ended Swarm Mode workflow.\n";
66300
66442
  //#endregion
66301
66443
  //#region ../../packages/agent-core/src/agent/swarm/index.ts
@@ -73815,6 +73957,10 @@ function abortedToolOutput(toolName, signal) {
73815
73957
  }
73816
73958
  async function runToolCallBatch(step, response) {
73817
73959
  if (response.toolCalls.length === 0) return { stopTurn: false };
73960
+ const batchStep = {
73961
+ ...step,
73962
+ toolCalls: response.toolCalls
73963
+ };
73818
73964
  const calls = response.toolCalls.map((toolCall) => preflightToolCall(step.tools, toolCall));
73819
73965
  const scheduler = new ToolScheduler();
73820
73966
  const pendingResults = [];
@@ -73822,19 +73968,19 @@ async function runToolCallBatch(step, response) {
73822
73968
  try {
73823
73969
  for (let index = 0; index < calls.length; index += 1) {
73824
73970
  const call = calls[index];
73825
- const prepared = await prepareToolCall(step, call);
73971
+ const prepared = await prepareToolCall(batchStep, call);
73826
73972
  pendingResults.push(scheduler.add(prepared.task));
73827
73973
  if (prepared.stopBatchAfterThis === true) {
73828
73974
  stopTurn = true;
73829
73975
  for (const skippedCall of calls.slice(index + 1)) {
73830
- const skippedTask = await prepareSkippedToolCall(step, skippedCall);
73976
+ const skippedTask = await prepareSkippedToolCall(batchStep, skippedCall);
73831
73977
  pendingResults.push(scheduler.add(skippedTask));
73832
73978
  }
73833
73979
  break;
73834
73980
  }
73835
73981
  }
73836
73982
  for (const pendingResult of pendingResults) {
73837
- const result = await finalizePendingToolResult(step, await pendingResult);
73983
+ const result = await finalizePendingToolResult(batchStep, await pendingResult);
73838
73984
  if (result.stopTurn === true) stopTurn = true;
73839
73985
  await step.dispatchEvent({
73840
73986
  type: "tool.result",
@@ -73989,6 +74135,7 @@ async function runPrepareToolExecutionHook(step, call) {
73989
74135
  try {
73990
74136
  hookResult = await hooks.prepareToolExecution({
73991
74137
  toolCall,
74138
+ toolCalls: step.toolCalls,
73992
74139
  tool: call.tool,
73993
74140
  args,
73994
74141
  turnId,
@@ -74031,6 +74178,7 @@ async function runAuthorizeToolExecutionHook(step, call, args, execution) {
74031
74178
  try {
74032
74179
  return await hooks.authorizeToolExecution({
74033
74180
  toolCall: call.toolCall,
74181
+ toolCalls: step.toolCalls,
74034
74182
  tool: call.tool,
74035
74183
  args,
74036
74184
  execution,
@@ -74086,6 +74234,7 @@ async function finalizePendingToolResult(step, pendingResult) {
74086
74234
  try {
74087
74235
  const effectiveResult = coerceToolResult(await hooks.finalizeToolResult({
74088
74236
  toolCall: pendingResult.toolCall,
74237
+ toolCalls: step.toolCalls,
74089
74238
  args: pendingResult.args,
74090
74239
  result: pendingResult.result,
74091
74240
  turnId,
@@ -79961,16 +80110,16 @@ function normalizeSourcePath(path) {
79961
80110
  return normalize(path.replaceAll("\\", "/")).replace(/^\.\//, "");
79962
80111
  }
79963
80112
  //#endregion
79964
- //#region ../../packages/agent-core/src/profile/default/agent.yaml
80113
+ //#region ../../packages/agent-core/src/profile/default/agent.yaml?raw
79965
80114
  var agent_default$1 = "name: agent\ndescription: Default Kimi Code agent\n\nsystemPromptPath: ./system.md\npromptVars:\n roleAdditional: ''\n\ntools:\n - Read\n - Write\n - Edit\n - Grep\n - Glob\n - Bash\n - TaskList\n - TaskOutput\n - TaskStop\n - CronCreate\n - CronList\n - CronDelete\n - ReadMediaFile\n - TodoList\n - Skill\n - WebSearch\n - Agent\n - AgentSwarm\n - FetchURL\n - AskUserQuestion\n - EnterPlanMode\n - ExitPlanMode\n - CreateGoal\n - GetGoal\n - SetGoalBudget\n - UpdateGoal\n - mcp__*\n\nsubagents:\n coder:\n description: Good at general software engineering tasks.\n explore:\n description: Fast codebase exploration with prompt-enforced read-only behavior.\n plan:\n description: Read-only implementation planning and architecture design.\n";
79966
80115
  //#endregion
79967
- //#region ../../packages/agent-core/src/profile/default/coder.yaml
80116
+ //#region ../../packages/agent-core/src/profile/default/coder.yaml?raw
79968
80117
  var coder_default = "extends: agent\nname: coder\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\nwhenToUse: |\n Use this agent for non-trivial software engineering work that may require reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - Write\n - Edit\n - WebSearch\n - FetchURL\n - mcp__*\n";
79969
80118
  //#endregion
79970
- //#region ../../packages/agent-core/src/profile/default/explore.yaml
80119
+ //#region ../../packages/agent-core/src/profile/default/explore.yaml?raw
79971
80120
  var explore_default = "extends: agent\nname: explore\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n You are a codebase exploration specialist. Your role is EXCLUSIVELY to search, read, and analyze existing code and resources. You do NOT have access to file editing tools.\n\n Your strengths:\n - Rapidly finding files using glob patterns\n - Searching code and text with powerful regex patterns\n - Reading and analyzing file contents\n - Running read-only shell commands (git log, git diff, ls, find, etc.)\n\n Guidelines:\n - Use Glob for broad file pattern matching. Patterns MUST contain a literal anchor (extension or subdirectory); pure wildcards like `*` or `**/*` are rejected by the tool.\n - Use Grep for searching file contents with regex\n - Use Read when you know the specific file path\n - Use Bash ONLY for read-only operations (ls, git status, git log, git diff, find)\n - NEVER use Bash for any file creation or modification commands\n - Adapt your search depth based on the thoroughness level specified by the caller\n - Wherever possible, spawn multiple parallel tool calls for grepping and reading files to maximize speed\n\n If the prompt includes a <git-context> block, use it to orient yourself about the repository state before starting your investigation.\n\n You are meant to be a fast agent. Complete the search request efficiently and report your findings clearly in a structured format.\nwhenToUse: |\n Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (e.g. \"src/**/*.yaml\"), search code for keywords (e.g. \"database connection\"), or answer questions about the codebase (e.g. \"how does the auth module work?\"). When calling this agent, specify the desired thoroughness level: \"quick\" for basic searches, \"medium\" for moderate exploration, or \"thorough\" for comprehensive analysis across multiple locations and naming conventions. Use this agent for any read-only exploration that will clearly require more than 3 search queries. Prefer launching multiple explore agents concurrently when investigating independent questions.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - WebSearch\n - FetchURL\n";
79972
80121
  //#endregion
79973
- //#region ../../packages/agent-core/src/profile/default/init.md
80122
+ //#region ../../packages/agent-core/src/profile/default/init.md?raw
79974
80123
  var init_default = "You are a software engineering expert with many years of programming experience. Please explore the current project directory to understand the project's architecture and main details.\n\nTask requirements:\n1. Analyze the project structure and identify key configuration files (such as pyproject.toml, package.json, Cargo.toml, etc.).\n2. Understand the project's technology stack, build process and runtime architecture.\n3. Identify how the code is organized and main module divisions.\n4. Discover project-specific development conventions, testing strategies, and deployment processes.\n\nAfter the exploration, you should do a thorough summary of your findings and overwrite it into `AGENTS.md` file in the project root. You need to refer to what is already in the file when you do so.\n\nFor your information, `AGENTS.md` is a file intended to be read by AI coding agents. Expect the reader of this file know nothing about the project.\n\nYou should compose this file according to the actual project content. Do not make any assumptions or generalizations. Ensure the information is accurate and useful. You must use the natural language that is mainly used in the project's comments and documentation.\n\nPopular sections that people usually write in `AGENTS.md` are:\n\n- Project overview\n- Build and test commands\n- Code style guidelines\n- Testing instructions\n- Security considerations\n";
79975
80124
  //#endregion
79976
80125
  //#region ../../packages/agent-core/src/profile/default.ts
@@ -80101,7 +80250,7 @@ function formatPlainObject(record) {
80101
80250
  return Object.entries(record).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => `${fieldName(key)}: ${formatValue(value)}`).join("\n");
80102
80251
  }
80103
80252
  //#endregion
80104
- //#region ../../packages/agent-core/src/tools/background/task-list.md
80253
+ //#region ../../packages/agent-core/src/tools/background/task-list.md?raw
80105
80254
  var task_list_default = "List background tasks and their current status.\n\nUse this tool to discover which background tasks exist and where each one\nstands. It is the entry point for inspecting background work: it returns a\ntask ID, status, command, description, and PID for every task it reports,\nplus the exit code and stop reason for tasks that have already finished.\n\nGuidelines:\n\n- After a context compaction, or whenever you are unsure which background\n tasks are running or what their task IDs are, call this tool to\n re-enumerate them instead of guessing a task ID.\n- Prefer the default `active_only=true`, which lists only non-terminal tasks.\n Pass `active_only=false` only when you specifically need to see tasks that\n have already finished. With `active_only=false` the result may also include\n `lost` tasks — tasks left over from a previous process that can no longer be\n inspected or controlled; treat them as already terminated.\n- `limit` caps how many tasks are returned. It accepts a value between 1 and\n 100 and defaults to 20 when omitted.\n- This tool only lists tasks; it does not return their output. Use it first\n to locate the task ID you need, then call `TaskOutput` with that ID to read\n the task's output and details.\n- This tool is read-only and does not change any state, so it is always safe\n to call, including in plan mode.\n";
80106
80255
  //#endregion
80107
80256
  //#region ../../packages/agent-core/src/tools/background/task-list.ts
@@ -80142,7 +80291,7 @@ var TaskListTool = class {
80142
80291
  }
80143
80292
  };
80144
80293
  //#endregion
80145
- //#region ../../packages/agent-core/src/tools/background/task-output.md
80294
+ //#region ../../packages/agent-core/src/tools/background/task-output.md?raw
80146
80295
  var task_output_default = "Retrieve output from a running or completed background task.\n\nUse this after `Bash(run_in_background=true)` or `Agent(run_in_background=true)` when you need to inspect progress or explicitly wait for completion.\n\nGuidelines:\n- Prefer relying on automatic completion notifications. Use this tool only when you need task output before the automatic notification arrives.\n- By default this tool is non-blocking and returns a current status/output snapshot.\n- Use block=true only when you intentionally want to wait for completion or timeout.\n- This tool returns structured task metadata, a fixed-size output preview, and an output_path for the full log.\n- For a terminal task, the metadata also explains why it ended: `status: timed_out` when a task was aborted by its deadline, and `stop_reason` when the task was explicitly stopped. `terminal_reason` is a categorical label for the same event — its value is `timed_out` or `stopped` — and is emitted alongside the matching status / `stop_reason` field. A task that ended on its own emits neither `stop_reason` nor `terminal_reason`.\n- The full, never-truncated log is always available at output_path; use the `Read` tool with that path to page through it, whether or not the preview was truncated.\n- This tool works with the generic background task system and should remain the primary read path for future task types, not just bash.\n";
80147
80296
  //#endregion
80148
80297
  //#region ../../packages/agent-core/src/tools/background/task-output.ts
@@ -80237,7 +80386,7 @@ var TaskOutputTool = class {
80237
80386
  }
80238
80387
  };
80239
80388
  //#endregion
80240
- //#region ../../packages/agent-core/src/tools/background/task-stop.md
80389
+ //#region ../../packages/agent-core/src/tools/background/task-stop.md?raw
80241
80390
  var task_stop_default = "Stop a running background task.\n\nOnly use this when a task must genuinely be cancelled — for a task that is\nfinishing normally, wait for its completion notification or inspect it with\n`TaskOutput` instead of stopping it.\n\nGuidelines:\n- This is a general-purpose stop capability for any background task. It is not\n a bash-specific kill.\n- Stopping a task is destructive: it may leave partial side effects behind.\n Use it with care.\n- If the task has already finished, this tool simply returns its current\n status.\n";
80242
80391
  //#endregion
80243
80392
  //#region ../../packages/agent-core/src/tools/background/task-stop.ts
@@ -80311,7 +80460,7 @@ function pad(n) {
80311
80460
  return String(n).padStart(2, "0");
80312
80461
  }
80313
80462
  //#endregion
80314
- //#region ../../packages/agent-core/src/tools/cron/cron-create.md
80463
+ //#region ../../packages/agent-core/src/tools/cron/cron-create.md?raw
80315
80464
  var cron_create_default = "Schedule a prompt to be enqueued at a future time. Use for both recurring schedules and one-shot reminders.\n\nUses standard 5-field cron in the user's local timezone: minute hour day-of-month month day-of-week. `0 9 * * *` means 9am local — no timezone conversion needed.\n\n## One-shot tasks (recurring: false)\n\nFor \"remind me at X\" or \"at <time>, do Y\" requests — fire once then auto-delete.\nPin minute/hour/day-of-month/month to specific values:\n \"remind me at 2:30pm today to check the deploy\" → cron: \"30 14 <today_dom> <today_month> *\", recurring: false\n \"tomorrow morning, run the smoke test\" → cron: \"57 8 <tomorrow_dom> <tomorrow_month> *\", recurring: false\n\n## Recurring jobs (recurring: true, the default)\n\nFor \"every N minutes\" / \"every hour\" / \"weekdays at 9am\" requests:\n \"*/5 * * * *\" (every 5 min), \"0 * * * *\" (hourly), \"0 9 * * 1-5\" (weekdays at 9am local)\n\n## Avoid the :00 and :30 minute marks when the task allows it\n\nEvery user who asks for \"9am\" gets `0 9`, and every user who asks for \"hourly\" gets `0 *` — which means requests from across the planet land on the API at the same instant. When the user's request is approximate, pick a minute that is NOT 0 or 30:\n \"every morning around 9\" → \"57 8 * * *\" or \"3 9 * * *\" (not \"0 9 * * *\")\n \"hourly\" → \"7 * * * *\" (not \"0 * * * *\")\n \"in an hour or so, remind me to...\" → pick whatever minute you land on, don't round\n\nOnly use minute 0 or 30 when the user names that exact time and clearly means it (\"at 9:00 sharp\", \"at half past\", coordinating with a meeting). When in doubt, nudge a few minutes early or late — the user will not notice, and the fleet will.\n\n## Coalesce semantics\n\nIf the scheduler slept past multiple ideal fire times (laptop closed, long-running turn, etc.), only **one** fire is delivered when it wakes up. The origin carries `coalescedCount` showing how many ideal fires were collapsed into this single delivery. You should treat `coalescedCount > 1` as \"I missed some checks; only the latest state matters\" rather than running the prompt that many times.\n\n## Cron-fire envelope\n\nWhen a cron task fires, the prompt you scheduled is re-injected wrapped in an XML envelope that exposes the fire context:\n\n```\n<cron-fire jobId=\"...\" cron=\"...\" recurring=\"true|false\" coalescedCount=\"N\" stale=\"true|false\">\n<prompt>\nyour original prompt text, verbatim\n</prompt>\n</cron-fire>\n```\n\nThe envelope is parseable. Use `coalescedCount > 1` to know multiple ideal fires were collapsed into a single delivery (treat as \"only the latest state matters\"), and `stale=\"true\"` as a cue that the task is past its 7-day threshold.\n\n## 7-day stale behavior\n\nRecurring tasks that have been alive for more than 7 days fire one\nfinal time with `stale: true` on the envelope, and the system then\nauto-deletes the task. The flag is the model's notice that this is\nthe last delivery. If the schedule is still wanted, call `CronCreate`\nagain with the same `cron` and `prompt` — that resets `createdAt` and\nstarts a fresh 7-day window. One-shot tasks are never marked stale.\n\nBench / acceptance runs can set `KIMI_CRON_NO_STALE=1` to disable the\njudgment entirely.\n\n## Jitter behavior\n\nAnti-herd jitter is applied deterministically per task id:\n - Recurring: ideal fire time is shifted **forward** by an offset ≤ min(10% of the cron period, 15 minutes). A `*/5 * * * *` task can drift up to 30s; a `0 9 * * *` task can drift up to 15 minutes.\n - One-shot: only when the ideal fire lands on `:00` or `:30` of the hour, the fire is pulled **earlier** by ≤ 90 seconds. Other minutes pass through unchanged.\n\nBench / acceptance tests can set `KIMI_CRON_NO_JITTER=1` to disable jitter entirely.\n\n## One-shot vs recurring — when to pick which\n\nUse `recurring: false` for \"remind me at X\" style requests, single deadlines, \"in N minutes do Y\", and any task that should not repeat. Use `recurring: true` for periodic polling (CI status, build watchers, scheduled reports), workday rituals, and anything the user explicitly described as recurring.\n\n## Session lifetime\n\nCron tasks live in the current kimi CLI session. When you exit, they\nare persisted under the session homedir; the next `kimi resume` of the\nsame session reloads them and the scheduler resumes from each task's\n`createdAt`. Fire times that fell during the offline window are\ncollapsed into a single delivery via `coalescedCount` (and recurring\ntasks past their 7-day window arrive with `stale: true` as their final\ndelivery).\n\nTasks do **not** carry over into a brand-new session — they are scoped\nto the resumed session id, not to the working directory.\n\n## Returned fields\n\n`id` (8-hex), `humanSchedule` (English summary), `recurring`,\n`nextFireAt` (local ISO timestamp with numeric offset, or null). `id` is needed by `CronDelete`.\n\n## Tell the user how to cancel or modify\n\nAfter successfully creating a task, proactively tell the user how they can cancel or modify it later. Users have no direct `/cron` command or self-service UI to manage reminders themselves; they must ask the model to make changes (e.g. \"cancel my 9am reminder\" or \"change my daily check to 10am\"). Include the task `id` in your message so the user can reference it.\n";
80316
80465
  /**
80317
80466
  * Hard ceiling on `prompt` byte length (UTF-8). The zod `.max(...)`
@@ -80433,7 +80582,7 @@ function formatOutput(o) {
80433
80582
  ].join("\n");
80434
80583
  }
80435
80584
  //#endregion
80436
- //#region ../../packages/agent-core/src/tools/cron/cron-delete.md
80585
+ //#region ../../packages/agent-core/src/tools/cron/cron-delete.md?raw
80437
80586
  var cron_delete_default = "Cancel a scheduled cron job by id.\n\nUse this tool to remove a cron task previously scheduled with\n`CronCreate`. The `id` is the 8-hex value returned by `CronCreate`, or\nshown in the `id:` column of `CronList` — quote it verbatim, no\nprefix.\n\nBehaviour by task kind:\n\n- **Recurring task** (`recurring: true`): stops all future fires\n immediately. The scheduler picks up the deletion on its next tick.\n- **One-shot task** (`recurring: false`): cancels the pending fire if\n it has not happened yet. One-shots that have already fired\n auto-delete themselves, so calling `CronDelete` on a fired one-shot\n returns \"no cron job with id ...\".\n\nNot-found is reported as an error (not a silent no-op) so you can\ncorrect yourself — typically by calling `CronList` to see which ids\nare actually live, rather than re-trying with the same stale id.\n\nRefresh pattern (use when you want a stale recurring schedule to\ncontinue):\n\nStale recurring tasks are auto-deleted by the system after their final\nfire — there is nothing for `CronDelete` to remove at that point. To\nkeep the schedule running, just call `CronCreate` with the same `cron`\nand `prompt`. Use `CronList`'s `prompt` field to recall the original\ntext after a context compaction.\n\n`CronDelete` remains the right call when you want to cancel a task\nthat is still live (recurring not yet stale, or a one-shot still\npending).\n\nGuidelines:\n\n- Users have no direct `/cron` command or self-service UI to delete\n tasks themselves; they must ask the model to cancel a reminder.\n When deleting on behalf of a user, confirm the action and report\n the result plainly.\n- Cron deletion is irreversible — there is no undo. If you delete the\n wrong task, you must re-create it with `CronCreate`.\n- If the model is unsure which id is current (e.g. after a context\n compaction), call `CronList` first rather than guessing.\n";
80438
80587
  //#endregion
80439
80588
  //#region ../../packages/agent-core/src/tools/cron/cron-delete.ts
@@ -80514,7 +80663,7 @@ var CronDeleteTool = class {
80514
80663
  }
80515
80664
  };
80516
80665
  //#endregion
80517
- //#region ../../packages/agent-core/src/tools/cron/cron-list.md
80666
+ //#region ../../packages/agent-core/src/tools/cron/cron-list.md?raw
80518
80667
  var cron_list_default = "List all cron jobs currently scheduled in this session.\n\nUse this tool to see every pending cron task — both recurring jobs and\none-shot reminders — that you (or the user) have scheduled with\n`CronCreate`. The output is the entry point for inspecting scheduled\nwork: it returns a stable id, the original cron expression, a human\nrendering, the next post-jitter fire time, the recurring flag, the\ntask's age in days, and a stale indicator.\n\nEach record carries:\n\n- `id` — the 8-hex task id. Pass this to `CronDelete` to remove the\n task, or quote it in user-facing messages when asking for\n confirmation.\n- `cron` — the verbatim 5-field cron expression as scheduled.\n- `humanSchedule` — plain-English rendering (e.g. `every 5 minutes`).\n- `prompt` — the scheduled prompt text, JSON-encoded so embedded\n newlines stay on one line. Truncated to 200 UTF-8 bytes with\n `…(truncated)` if longer. Use this to recall what a task is for\n after a context compaction, and as the source for the\n `CronCreate` refresh ritual.\n- `nextFireAt` — local ISO timestamp with an explicit numeric offset\n for the next fire **after jitter has been applied**. The actual fire\n may land slightly before or after a round `:00` / `:30` minute mark\n due to herd-avoidance jitter; this is the value the scheduler will\n compare against, so it reflects what will really happen. `null` if\n the expression has no fire in the next 5 years (should not happen\n for tasks created through `CronCreate`, which validates).\n- `recurring` — `true` for cadenced jobs, `false` for one-shots.\n- `ageDays` — `(now - createdAt) / day`, two decimal places. Useful\n when deciding whether a long-running cron is still relevant.\n- `stale` — `true` when a recurring task is older than 7 days. The\n system **auto-deletes the task after this fire** to bound session\n lifetime; the `stale: true` flag is the model's notice that this is\n the final delivery. To resume the same schedule, call `CronCreate`\n again with the original `cron` and `prompt` (the `prompt` row above\n carries it for exactly this purpose). One-shots are never marked\n stale — they fire at most once by construction.\n `KIMI_CRON_NO_STALE=1` disables the judgment entirely (bench /\n acceptance runs).\n\nGuidelines:\n\n- This tool is read-only and never mutates state, so it is always\n safe to call (including in plan mode).\n- Users cannot directly manage cron tasks themselves; if they want to\n cancel or modify a schedule, route the request through the model\n (i.e. call `CronDelete` or `CronCreate` on their behalf).\n- The empty case returns `cron_jobs: 0\\nNo cron jobs scheduled.`. Cron\n tasks survive a `kimi resume` of the same session but do not bleed\n into new sessions.\n- After a context compaction, or whenever you are unsure which cron\n jobs are live, call this tool to re-enumerate them rather than\n guessing ids from earlier in the conversation.\n- Records are separated by a line containing just `---`, in the\n insertion order they were scheduled.\n";
80519
80668
  //#endregion
80520
80669
  //#region ../../packages/agent-core/src/tools/cron/cron-list.ts
@@ -81170,7 +81319,7 @@ var SubagentBatch = class {
81170
81319
  }
81171
81320
  };
81172
81321
  //#endregion
81173
- //#region ../../packages/agent-core/src/session/summary-continuation.md
81322
+ //#region ../../packages/agent-core/src/session/summary-continuation.md?raw
81174
81323
  var summary_continuation_default = "Your previous response was too brief. Please provide a more comprehensive summary that includes:\n\n1. Specific technical details and implementations\n2. Detailed findings and analysis\n3. All important information that the parent agent should know";
81175
81324
  //#endregion
81176
81325
  //#region ../../packages/agent-core/src/session/subagent-host.ts
@@ -81504,13 +81653,13 @@ function shouldSuppressQueuedAttemptFailureEvent(options, error) {
81504
81653
  return isAbortError$3(error) || options.signal.aborted;
81505
81654
  }
81506
81655
  //#endregion
81507
- //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-background-disabled.md
81656
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-background-disabled.md?raw
81508
81657
  var agent_background_disabled_default = "Background agent execution is disabled for this agent. Do not set `run_in_background=true`.";
81509
81658
  //#endregion
81510
- //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-background-enabled.md
81659
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-background-enabled.md?raw
81511
81660
  var agent_background_enabled_default = "When `run_in_background=true`, the subagent runs detached from this turn. The completion arrives in a later turn as a synthetic user-role message containing its result — you do not need to poll, sleep, or check on its progress. Continue with other work or respond to the user. Never fabricate or predict what the result will say.\n";
81512
81661
  //#endregion
81513
- //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent.md
81662
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent.md?raw
81514
81663
  var agent_default = "Launch a subagent to handle a task. The subagent runs as a same-process loop instance with its own context and wire file.\n\nWriting the prompt:\n- The subagent starts with zero context — it has not seen this conversation. Brief it like a colleague who just walked into the room: state the goal, list what you already know, hand over the specifics.\n- Lookups (read this file, run that test): put the exact path or command in the prompt. The subagent should not have to search for things you already know.\n- Investigations (figure out X, find why Y): give the question, not prescribed steps — fixed steps become dead weight when the premise is wrong.\n- Do not delegate understanding. If the task hinges on a file path or line number, find it yourself first and write it into the prompt.\n\nUsage notes:\n- When the task continues earlier work a subagent already did, prefer resuming that agent (pass its `resume` id) over spawning a fresh instance — the resumed agent keeps its prior context.\n- A subagent's result is only visible to you, not to the user. When the user needs to see what a subagent produced, summarize the relevant parts yourself in your own reply.\n- Subagents use a fixed 30-minute timeout. If one times out, resume the same agent instead of starting over.\n\nWhen NOT to use Agent: skip delegation for trivial work you can do directly — reading a file whose path you already know, searching a small known set of files, or any task that takes only a step or two. Delegation has a context-handoff cost; it pays off only when the task is substantial enough to outweigh it.\n\nOnce a subagent is running, leave that scope to it: do not redo its searches or reads in parallel, and do not abandon it midway and finish the job manually. Both undo the context savings the delegation was meant to buy.\n";
81515
81664
  //#endregion
81516
81665
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent.ts
@@ -81731,8 +81880,8 @@ function buildSubagentDescriptions(subagents) {
81731
81880
  }).join("\n");
81732
81881
  }
81733
81882
  //#endregion
81734
- //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-swarm.md
81735
- var agent_swarm_default = "Launch multiple subagents from one prompt template, existing agent resumes, or both.\n\nUse AgentSwarm when many subagents should run the same kind of task over different inputs. The placeholder is exactly `{{item}}`. For example, with `prompt_template` set to `Review {{item}} for likely regressions.` and `items` set to `[\"src/a.ts\", \"src/b.ts\"]`, AgentSwarm launches two new subagents with those two concrete prompts.\n\nUse `resume_agent_ids` to continue subagents that already exist from earlier work, such as ones that failed or timed out: map each agent id to the prompt for that resumed subagent (usually `continue` if no extra information is needed). You may combine `resume_agent_ids` with `items` in the same call to resume existing subagents and launch new ones. Do not duplicate resumed work in `items`.\n\nUse enough subagents to keep the work focused and parallel. AgentSwarm supports up to 128 subagents, and launches are queued automatically, so it is safe to split large tasks into many clear, independent items.\n";
81883
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-swarm.md?raw
81884
+ var agent_swarm_default = "Launch multiple subagents from one prompt template, existing agent resumes, or both.\n\nUse AgentSwarm when many subagents should run the same kind of task over different inputs. The placeholder is exactly `{{item}}`. For example, with `prompt_template` set to `Review {{item}} for likely regressions.` and `items` set to `[\"src/a.ts\", \"src/b.ts\"]`, AgentSwarm launches two new subagents with those two concrete prompts.\n\nUse `resume_agent_ids` to continue subagents that already exist from earlier work, such as ones that failed or timed out: map each agent id to the prompt for that resumed subagent (usually `continue` if no extra information is needed). You may combine `resume_agent_ids` with `items` in the same call to resume existing subagents and launch new ones. Do not duplicate resumed work in `items`.\n\nUse enough subagents to keep the work focused and parallel. AgentSwarm supports up to 128 subagents, and launches are queued automatically, so it is safe to split large tasks into many clear, independent items.\n\nIf `AgentSwarm` is called, that call must be the only tool call in the response.\n";
81736
81885
  //#endregion
81737
81886
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-swarm.ts
81738
81887
  const DEFAULT_SUBAGENT_TYPE = "coder";
@@ -81892,7 +82041,7 @@ function escapeXmlAttribute(value) {
81892
82041
  return value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
81893
82042
  }
81894
82043
  //#endregion
81895
- //#region ../../packages/agent-core/src/tools/builtin/collaboration/ask-user.md
82044
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/ask-user.md?raw
81896
82045
  var ask_user_default = "Use this tool when you need to ask the user questions with structured options during execution. This allows you to:\n1. Collect user preferences or requirements before proceeding\n2. Resolve ambiguous or underspecified instructions\n3. Let the user decide between implementation approaches as you work\n4. Present concrete options when multiple valid directions exist\n\n**When NOT to use:**\n- When you can infer the answer from context — be decisive and proceed\n- Trivial decisions that don't materially affect the outcome\n\nOverusing this tool interrupts the user's flow. Only use it when the user's input genuinely changes your next action.\n\n**Usage notes:**\n- Users always have an \"Other\" option for custom input — don't create one yourself\n- Use multi_select to allow multiple answers to be selected for a question\n- Keep option labels concise (1-5 words), use descriptions for trade-offs and details\n- Each question should have 2-4 meaningful, distinct options\n- You can ask 1-4 questions at a time; group related questions to minimize interruptions\n- If you recommend a specific option, list it first and append \"(Recommended)\" to its label\n";
81897
82046
  //#endregion
81898
82047
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/ask-user.ts
@@ -82054,7 +82203,7 @@ function isQuestionResponse(result) {
82054
82203
  return typeof answers === "object" && answers !== null && !Array.isArray(answers);
82055
82204
  }
82056
82205
  //#endregion
82057
- //#region ../../packages/agent-core/src/tools/builtin/collaboration/skill-tool.md
82206
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/skill-tool.md?raw
82058
82207
  var skill_tool_default = "Invoke a registered skill from the current skill listing. BLOCKING REQUIREMENT: when a skill from the listing matches the user's request, you MUST call this tool (not free-form text). Do NOT call the same skill repeatedly inside one turn — recursive depth is capped at {{ MAX_SKILL_QUERY_DEPTH }}.";
82059
82208
  var NestedSkillTooDeepError = class extends Error {
82060
82209
  skillName;
@@ -82182,7 +82331,7 @@ function makeCarriageReturnsVisible(text) {
82182
82331
  return text.replaceAll("\r", "\\r");
82183
82332
  }
82184
82333
  //#endregion
82185
- //#region ../../packages/agent-core/src/tools/builtin/file/edit.md
82334
+ //#region ../../packages/agent-core/src/tools/builtin/file/edit.md?raw
82186
82335
  var edit_default = "Perform exact replacements in existing files.\n\n- Edit is mandatory for every incremental change, especially small edits. DO NOT use Write or Bash `sed`.\n- Read the target file before every Edit. DO NOT call Edit from memory, stale context, or a guessed `old_string`.\n- Take `old_string` and `new_string` from the Read output view.\n- Drop the line-number prefix and tab; match only file content.\n- `old_string` must be unique unless `replace_all` is set.\n- If `old_string` is ambiguous, add surrounding context. Use `replace_all` only when every occurrence should change.\n- Multiple Edit calls may run in one response only when they do not target the same file.\n- DO NOT issue consecutive Edit calls on the same file. A previous Edit can invalidate a later Edit's `old_string`, causing `old_string not found`. Read the file again before the next Edit.\n- A write lock serializes same-file edits in response order, but serialization does not make stale `old_string` valid.\n- For pure CRLF files, Read shows LF; use LF in `old_string` and `new_string`, and Edit writes CRLF back.\n- For mixed endings or lone carriage returns, Read shows carriage returns as \\r; include actual \\r escapes in those positions.\n";
82187
82336
  //#endregion
82188
82337
  //#region ../../packages/agent-core/src/tools/builtin/file/edit.ts
@@ -82285,7 +82434,7 @@ var EditTool = class {
82285
82434
  }
82286
82435
  };
82287
82436
  //#endregion
82288
- //#region ../../packages/agent-core/src/tools/builtin/file/glob.md
82437
+ //#region ../../packages/agent-core/src/tools/builtin/file/glob.md?raw
82289
82438
  var glob_default = "Find files (and optionally directories) by glob pattern, sorted by modification time (most recent first).\n\nGood patterns:\n- `*.ts` — files in the current directory matching an extension\n- `src/**/*.ts` — recursive walk with a subdirectory anchor and extension\n- `**/*.py` — recursive walk from the search root for an extension\n- `*.{ts,tsx}` — brace expansion is supported; expanded into `*.ts` and `*.tsx` before walking\n- `{src,test}/**/*.ts` — cartesian brace expansion is supported too\n\nResults are capped at the first 100 matching paths (walk order, not global modification-time order). If a search would return more, a truncation marker is appended with the count of matches seen so far. Refine the pattern (extension, subdirectory) when 100 is not enough, or call again with a narrower anchor.\n\nLarge-directory caveat — avoid recursing into dependency / build output even with an anchor:\n- `node_modules/**/*.js`, `.venv/**/*.py`, `__pycache__/**`, `target/**` all match technically but\n typically produce thousands of results that truncate at the match cap and waste the caller context.\n Prefer specific subpaths like `node_modules/react/src/**/*.js`.\n";
82290
82439
  //#endregion
82291
82440
  //#region ../../packages/agent-core/src/tools/builtin/file/glob.ts
@@ -87012,7 +87161,7 @@ var ToolResultBuilder = class {
87012
87161
  }
87013
87162
  };
87014
87163
  //#endregion
87015
- //#region ../../packages/agent-core/src/tools/builtin/file/grep.md
87164
+ //#region ../../packages/agent-core/src/tools/builtin/file/grep.md?raw
87016
87165
  var grep_default = "Search file contents using regular expressions (powered by ripgrep).\n\nUse Grep when the task is to find unknown content or unknown file locations. Do not use shell `grep` or `rg` directly; this tool applies workspace path policy, output limits, and sensitive-file filtering.\nALWAYS use Grep tool instead of running `grep` or `rg` from a shell — direct shell calls bypass workspace policy, output limits, and sensitive-file filtering.\nIf you already know a concrete file path and need to inspect its contents, use Read directly instead.\n\nWrite patterns in ripgrep regex syntax, which differs from POSIX `grep` syntax. For example, braces are special, so escape them as `\\{` to match a literal `{`.\n\nHidden files (dotfiles such as `.gitlab-ci.yml` or `.eslintrc.json`) are searched by default. To also search files excluded by `.gitignore` (such as `node_modules` or build outputs), set `include_ignored` to `true`. Sensitive files (such as `.env`) are always skipped for safety, even when `include_ignored` is `true`.\n";
87017
87166
  //#endregion
87018
87167
  //#region ../../packages/agent-core/src/tools/builtin/file/grep.ts
@@ -88000,7 +88149,7 @@ function detectFileType(path, header) {
88000
88149
  };
88001
88150
  }
88002
88151
  //#endregion
88003
- //#region ../../packages/agent-core/src/tools/builtin/file/read.md
88152
+ //#region ../../packages/agent-core/src/tools/builtin/file/read.md?raw
88004
88153
  var read_default = "Read a text file from the local filesystem.\n\nIf the user provides a concrete file path to a text file, call Read directly. Do not `Glob`, `ls`, or otherwise pre-check known text file paths; missing or invalid file paths return errors you can handle. Do not use Read for directories; use `ls` via Bash for a known directory, or Glob when you need files/directories matching a pattern. Use `Grep` only when the task is to search for unknown content or locations.\n\nWhen you need several files, prefer to read them in parallel: emit multiple `Read` calls in a single response instead of reading one file per turn.\n\n- Relative paths resolve against the working directory; a path outside the working directory must be absolute.\n- Returns up to {{ MAX_LINES }} lines or {{ MAX_BYTES_KB }} KB per call, whichever comes first; lines longer than {{ MAX_LINE_LENGTH }} chars are truncated mid-line.\n- Page larger files with `line_offset` (1-based start line) and `n_lines`. Omit `n_lines` to read up to the {{ MAX_LINES }}-line cap.\n- Sensitive files (`.env` files, credential stores, SSH keys, and similar secrets) are refused to protect secrets; do not attempt to read them.\n- Only UTF-8 text files can be read. Non-UTF-8 encodings, binary files, and files containing NUL bytes are refused; use `ReadMediaFile` for images or video, and Bash or an MCP tool for other binary formats.\n- Negative line_offset reads from the end of the file (for example, -100 reads the last 100 lines); the absolute value cannot exceed {{ MAX_LINES }}.\n- Output format: `<line-number>\\t<content>` per line.\n- A `<system>...</system>` status block is appended after the file content; it summarizes how much was read (line and byte counts, truncation, line-ending notes) and is not part of the file itself.\n- Pure CRLF files are displayed with LF line endings; `Edit` matches this output and preserves CRLF when writing back.\n- Mixed or lone carriage-return line endings are shown as `\\r` and require exact `Edit.old_string` escapes.\n- After a successful `Edit`/`Write`, do not re-read solely to prove the write landed. When the task depends on an exact file, API, or output shape, inspect the final external contract before finishing.\n";
88005
88154
  //#endregion
88006
88155
  //#region ../../packages/agent-core/src/tools/builtin/file/read.ts
@@ -88307,7 +88456,7 @@ var ReadTool = class {
88307
88456
  }
88308
88457
  };
88309
88458
  //#endregion
88310
- //#region ../../packages/agent-core/src/tools/builtin/file/read-media.md
88459
+ //#region ../../packages/agent-core/src/tools/builtin/file/read-media.md?raw
88311
88460
  var read_media_default = "Read media content from a file.\n\n**Tips:**\n- Make sure you follow the description of each tool parameter.\n- A `<system>` tag is given before the file content; it summarizes the mime type, byte size and, for images, the original pixel dimensions. When outputting coordinates, give relative coordinates first and compute absolute coordinates from the original image size. After generating or editing media via commands or scripts, read the result back before continuing.\n- The system will notify you when there is anything wrong when reading the file.\n- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible.\n- This tool can only read image or video files. To read text files, use the Read tool. To list directories, use `ls` via Bash for a known directory, or Glob for pattern search.\n- If the file doesn't exist or path is invalid, an error will be returned.\n- The maximum size that can be read is {{ MAX_MEDIA_MEGABYTES }}MB. An error will be returned if the file is larger than this limit.\n- The media content will be returned in a form that you can directly view and understand.\n\n**Capabilities**";
88312
88461
  //#endregion
88313
88462
  //#region ../../packages/agent-core/src/tools/builtin/file/read-media.ts
@@ -88469,7 +88618,7 @@ var ReadMediaFileTool = class {
88469
88618
  }
88470
88619
  };
88471
88620
  //#endregion
88472
- //#region ../../packages/agent-core/src/tools/builtin/file/write.md
88621
+ //#region ../../packages/agent-core/src/tools/builtin/file/write.md?raw
88473
88622
  var write_default = "Create, append to, or replace a file entirely.\n\n- The parent directory must already exist.\n- Mode defaults to overwrite; append adds content at EOF without adding a newline.\n- Write is NOT ALLOWED for incremental changes to existing files, including trivial, one-line, quick, or cosmetic edits. Use Edit instead.\n- Use Write only when the file does not exist, you intend a complete replacement, or the new contents have little continuity with the old contents.\n- Read before overwriting an existing file.\n- Write ignores the Read/Edit line-number view. NEVER include line prefixes.\n- Write outputs content literally, including supplied line endings: \\n stays LF, \\r\\n stays CRLF.\n- For new content too large for one call, overwrite the first chunk, then append subsequent chunks. Never chunk Write to modify an existing file.\n";
88474
88623
  //#endregion
88475
88624
  //#region ../../packages/agent-core/src/tools/builtin/file/write.ts
@@ -88565,7 +88714,7 @@ var WriteTool = class {
88565
88714
  }
88566
88715
  };
88567
88716
  //#endregion
88568
- //#region ../../packages/agent-core/src/tools/builtin/goal/create-goal.md
88717
+ //#region ../../packages/agent-core/src/tools/builtin/goal/create-goal.md?raw
88569
88718
  var create_goal_default = "Create a durable, structured goal that the runtime will pursue across multiple turns.\n\nCall `CreateGoal` only when:\n\n- the user explicitly asks you to start a goal or work autonomously toward an outcome, or\n- a host goal-intake prompt asks you to create one.\n\nDo NOT create a goal for greetings, ordinary questions, or vague requests that lack a\nverifiable completion condition. A goal needs a checkable end state.\n\nWhen the request is vague, ask the user for the missing completion criterion before creating\nthe goal. If the user clearly insists after you warn them that the wording is vague or risky,\nrespect that and create the goal.\n\nInclude a `completionCriterion` when the user provides one, or when it can be stated without\ninventing new requirements. Keep `objective` concise; reference long task descriptions by file\npath rather than pasting them.\n\nUse `replace: true` only when the user explicitly wants to abandon the current goal and start a\nnew one.\n";
88570
88719
  //#endregion
88571
88720
  //#region ../../packages/agent-core/src/tools/builtin/goal/create-goal.ts
@@ -88599,7 +88748,7 @@ var CreateGoalTool = class {
88599
88748
  }
88600
88749
  };
88601
88750
  //#endregion
88602
- //#region ../../packages/agent-core/src/tools/builtin/goal/get-goal.md
88751
+ //#region ../../packages/agent-core/src/tools/builtin/goal/get-goal.md?raw
88603
88752
  var get_goal_default = "Read the current goal: its objective, completion criterion, status, budgets (turns, tokens,\ntime, and how much remains), the latest self-report, and the latest evaluator verdict.\n\nUse `GetGoal` before deciding whether to continue working, report completion, report a blocker,\nor respect a pause. It returns `{ \"goal\": null }` when there is no current goal.\n";
88604
88753
  //#endregion
88605
88754
  //#region ../../packages/agent-core/src/tools/builtin/goal/get-goal.ts
@@ -88625,7 +88774,7 @@ var GetGoalTool = class {
88625
88774
  }
88626
88775
  };
88627
88776
  //#endregion
88628
- //#region ../../packages/agent-core/src/tools/builtin/goal/set-goal-budget.md
88777
+ //#region ../../packages/agent-core/src/tools/builtin/goal/set-goal-budget.md?raw
88629
88778
  var set_goal_budget_default = "Set a hard budget limit for the current goal.\n\nUse this only when the user clearly gives a runtime limit, such as:\n\n- \"stop after 20 turns\"\n- \"use no more than 500k tokens\"\n- \"finish within 30 minutes\"\n\nDo not invent limits. Do not call this for vague wording such as \"spend some time\" or\n\"try to be quick\".\n\nIf the user gives a compound time, convert it to one supported unit before calling this tool.\nFor example, \"2 hours and 3 minutes\" can be set as `value: 123, unit: \"minutes\"`.\n\nIf the requested budget is not reasonable, do not set it. Tell the user that the requested\nbudget is not reasonable. Examples include a time budget that is too short to act on, such as\n1 millisecond, or too long for an interactive goal run, such as 1 year.\n\nSupported units:\n\n- `turns`\n- `tokens`\n- `milliseconds`\n- `seconds`\n- `minutes`\n- `hours`\n";
88630
88779
  //#endregion
88631
88780
  //#region ../../packages/agent-core/src/tools/builtin/goal/set-goal-budget.ts
@@ -89813,7 +89962,7 @@ function formatTokens$3(tokens) {
89813
89962
  return `${(tokens / 1e6).toFixed(1)}M`;
89814
89963
  }
89815
89964
  //#endregion
89816
- //#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.md
89965
+ //#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.md?raw
89817
89966
  var update_goal_default = "Set the status of the current goal. This is how you resume, end, or yield an autonomous goal.\n\n- `active` — resume a paused or blocked goal when the user explicitly asks you to work on that goal.\n- `complete` — the objective is satisfied and any stated validation has passed. The goal ends and a completion summary is recorded.\n- `blocked` — an external condition or required user input prevents progress, or the objective cannot be completed as stated. The goal stops but can be resumed later.\n- `paused` — set the goal aside for now (e.g. to hand control back to the user). It can be resumed later.\n\nIf the goal is active and you do not call this, the goal keeps running: after your turn ends you will be prompted to continue. Call `complete` only when all required work is done, any stated validation has passed, and there is no useful next action. Do not call `complete` after only producing a plan, summary, first pass, or partial result. If you call `blocked`, you will be prompted to explain the blocker in your next message. This tool only records the status.\n";
89818
89967
  //#endregion
89819
89968
  //#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.ts
@@ -89874,7 +90023,7 @@ var UpdateGoalTool = class {
89874
90023
  }
89875
90024
  };
89876
90025
  //#endregion
89877
- //#region ../../packages/agent-core/src/tools/builtin/planning/enter-plan-mode.md
90026
+ //#region ../../packages/agent-core/src/tools/builtin/planning/enter-plan-mode.md?raw
89878
90027
  var enter_plan_mode_default = "Use this tool proactively when you're about to start a non-trivial implementation task.\nGetting user sign-off on your approach via ExitPlanMode before writing code prevents wasted effort.\n\nUse it when ANY of these conditions apply:\n\n1. New Feature Implementation - e.g. \"Add a caching layer to the API\"\n2. Multiple Valid Approaches - e.g. \"Optimize database queries\" (indexing vs rewrite vs caching)\n3. Code Modifications - e.g. \"Refactor auth module to support OAuth\"\n4. Architectural Decisions - e.g. \"Add WebSocket support\"\n5. Multi-File Changes - involves more than 2-3 files\n6. Unclear Requirements - need exploration to understand scope\n7. User Preferences Matter - if user input would materially change the implementation approach, use EnterPlanMode to structure the decision\n\nPermission mode notes:\n- EnterPlanMode enters plan mode automatically without an approval prompt in all permission modes.\n- In yolo and manual modes, ExitPlanMode still presents the plan to the user for approval.\n- In auto permission mode, do not use AskUserQuestion; make the best decision from available context.\n- In auto permission mode, ExitPlanMode exits plan mode without asking the user.\n- Use EnterPlanMode only when planning itself adds value.\n\nWhen NOT to use:\n- Single-line or few-line fixes (typos, obvious bugs, small tweaks)\n- User gave very specific, detailed instructions\n- Pure research/exploration tasks\n\n## What Happens in Plan Mode\nIn plan mode, you will:\n1. Identify 2-3 key questions about the codebase that are critical to your plan. If you are not confident about the codebase structure or relevant code paths, use `Agent(subagent_type=\"explore\")` to investigate these questions first - this is strongly recommended for non-trivial tasks.\n2. Explore the codebase using Glob, Grep, Read, and other read-only tools for any remaining quick lookups. Use Bash only when needed; Bash follows the normal permission mode and rules.\n3. Design an implementation approach based on your findings\n4. Write your plan to the current plan file with Write or Edit\n5. Present your plan to the user via ExitPlanMode for approval\n";
89879
90028
  //#endregion
89880
90029
  //#region ../../packages/agent-core/src/tools/builtin/planning/enter-plan-mode.ts
@@ -89936,7 +90085,7 @@ function enteredPlanModeMessage(planPath) {
89936
90085
  ].join("\n");
89937
90086
  }
89938
90087
  //#endregion
89939
- //#region ../../packages/agent-core/src/tools/builtin/planning/exit-plan-mode.md
90088
+ //#region ../../packages/agent-core/src/tools/builtin/planning/exit-plan-mode.md?raw
89940
90089
  var exit_plan_mode_default = "Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval.\n\n## How This Tool Works\n- You should have already written your plan to the plan file specified in the plan mode reminder.\n- This tool does NOT take the plan content as a parameter - it reads the plan from the file you wrote.\n- The user will see the contents of your plan file when they review it. In auto permission mode, the tool reads the file and exits plan mode without asking the user.\n\n## When to Use\nOnly use this tool for tasks that require planning implementation steps. For research tasks (searching files, reading code, understanding the codebase), do NOT use this tool.\n\n## Multiple Approaches\nIf your plan contains multiple alternative approaches:\n- Pass them via the `options` parameter so the user can choose which approach to execute.\n- Each option should have a concise label and a brief description of trade-offs.\n- If you recommend one option, append \"(Recommended)\" to its label.\n- In yolo and manual modes, the user will see all options alongside Reject and Revise choices.\n- Provide up to 3 options; the host adds the standard rejection and revision controls. When the plan offers a real choice, 2-3 distinct approaches work best.\n- Passing a single option is allowed and is equivalent to a plain plan approval (no approach choice is surfaced to the user).\n- Do NOT use \"Reject\", \"Reject and Exit\", \"Revise\", or \"Approve\" as option labels - these are reserved by the system.\n\n## Before Using\n- In auto permission mode, do NOT use AskUserQuestion; make the best decision from available context.\n- In auto permission mode, this tool exits plan mode without asking the user.\n- In yolo and manual modes, this tool still presents the plan to the user for approval.\n- If auto permission mode is not active and you have unresolved questions, use AskUserQuestion first.\n- If auto permission mode is not active and you have multiple approaches and haven't narrowed down yet, consider using AskUserQuestion first to let the user choose, then write a plan for the chosen approach only.\n- Once your plan is finalized, use THIS tool to request approval.\n- Do NOT use AskUserQuestion to ask \"Is this plan OK?\" or \"Should I proceed?\" - that is exactly what ExitPlanMode does.\n- If rejected, revise based on feedback and call ExitPlanMode again.\n";
89941
90090
  //#endregion
89942
90091
  //#region ../../packages/agent-core/src/tools/builtin/planning/exit-plan-mode.ts
@@ -90061,7 +90210,7 @@ function formatPlanForOutput(plan, path) {
90061
90210
  return `Plan mode deactivated. All tools are now available.\n${path !== void 0 ? `Plan saved to: ${path}\n\n` : ""}## Approved Plan:\n${plan}`;
90062
90211
  }
90063
90212
  //#endregion
90064
- //#region ../../packages/agent-core/src/tools/builtin/shell/bash.md
90213
+ //#region ../../packages/agent-core/src/tools/builtin/shell/bash.md?raw
90065
90214
  var bash_default = "Execute a `{{ SHELL_NAME }}` command. Use this for shell semantics — pipes, env, processes, git, package managers, build/test runners, anything genuinely interactive or multi-step.\n\n**Translate these to a dedicated tool instead:**\n- `cat` / `head` / `tail` (known path) → `Read`\n- `sed` / `awk` (in-place edit) → `Edit`\n- `echo > file` / `cat <<EOF` → `Write`\n- `find` / recursive `ls` to locate files by name pattern → `Glob` (plain `ls <known-directory>` is fine for listing a directory)\n- `grep` / `rg` (search file contents) → `Grep`\n- `echo` / `printf` (talk to the user) → just output text directly\n\nThe dedicated tools render in the per-tool permission UI and keep raw stdout out of the conversation; that is why they are worth reaching for whenever one fits.\n\n**Output:**\nThe stdout and stderr will be combined and returned as a string. The output may be truncated if it is too long. If the command failed, the output will end with a `Command failed with exit code: N` line stating the non-zero exit code.\n\nIf `run_in_background=true`, the command will be started as a background task and this tool will return a task ID instead of waiting for command completion. When doing that, you must provide a short `description`. Background commands default to a {{ DEFAULT_BACKGROUND_TIMEOUT_S }}s timeout and `timeout` is capped at {{ MAX_BACKGROUND_TIMEOUT_S }}s; set `disable_timeout=true` only when the task should run without a timeout. You will be automatically notified when the task completes. Use `TaskOutput` for a non-blocking status/output snapshot, and only set `block=true` when you explicitly want to wait for completion. Use `TaskStop` only if the task must be cancelled. If a human user wants to inspect background tasks themselves, point them to the `/tasks` command, which opens an interactive panel; it has no subcommands.\n\n**Guidelines for safety and security:**\n- 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.\n- 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 foreground commands, set the `timeout` argument in seconds. Foreground commands default to {{ DEFAULT_TIMEOUT_S }}s and allow up to {{ MAX_TIMEOUT_S }}s.\n- Avoid using `..` to access files or directories outside of the working directory.\n- Avoid modifying files outside of the working directory unless explicitly instructed to do so.\n- Never run commands that require superuser privileges unless explicitly instructed to do so.\n\n**Guidelines for efficiency:**\n- For multiple related commands, use `&&` to chain them in a single call, e.g. `cd /path && ls -la`\n- Use `;` to run commands sequentially regardless of success/failure\n- Use `||` for conditional execution (run second command only if first fails)\n- Use pipe operations (`|`) and redirections (`>`, `>>`) to chain input and output between commands\n- Always quote file paths containing spaces with double quotes (e.g., cd \"/path with spaces/\")\n- Compose multi-step logic in a single call with `if` / `case` / `for` / `while` control flows.\n- Prefer `run_in_background=true` for long-running builds, tests, watchers, or servers when you need the conversation to continue before the command finishes.\n\n**Commands available:**\nThe following common command categories are usually available. Availability still depends on the host, so when in doubt run `which <command>` first to confirm a command exists before relying on it.\n- Navigation and inspection: `ls`, `pwd`, `cd`, `stat`, `file`, `du`, `df`, `tree`\n- File and directory management: `cp`, `mv`, `rm`, `mkdir`, `touch`, `ln`, `chmod`, `chown`\n- Text and data processing: `wc`, `sort`, `uniq`, `cut`, `tr`, `diff`, `xargs`\n- Archives and compression: `tar`, `gzip`, `gunzip`, `zip`, `unzip`\n- Networking and transfer: `curl`, `wget`, `ping`, `ssh`, `scp`\n- Version control: `git`\n- Process and system: `ps`, `kill`, `top`, `env`, `date`, `uname`, `whoami`\n- Language and package toolchains: `node`, `npm`, `pnpm`, `yarn`, `python`, `pip` (use whichever the project actually relies on)\n";
90066
90215
  //#endregion
90067
90216
  //#region ../../packages/agent-core/src/tools/builtin/shell/bash.ts
@@ -90150,7 +90299,7 @@ var BashTool = class {
90150
90299
  },
90151
90300
  approvalRule: literalRulePattern(this.name, args.command),
90152
90301
  matchesRule: (ruleArgs) => matchesGlobRuleSubject(ruleArgs, args.command),
90153
- execute: ({ signal }) => this.execution(args, signal)
90302
+ execute: ({ signal, onUpdate }) => this.execution(args, signal, onUpdate)
90154
90303
  };
90155
90304
  }
90156
90305
  spawn(effectiveCwd, command) {
@@ -90172,7 +90321,7 @@ var BashTool = class {
90172
90321
  };
90173
90322
  return this.kaos.execWithEnv(shellArgs, mergedEnv);
90174
90323
  }
90175
- async execution(args, signal) {
90324
+ async execution(args, signal, onUpdate) {
90176
90325
  if (signal.aborted) return {
90177
90326
  isError: true,
90178
90327
  output: "Aborted before command started"
@@ -90238,7 +90387,8 @@ var BashTool = class {
90238
90387
  }, timeoutMs);
90239
90388
  try {
90240
90389
  const builder = new ToolResultBuilder();
90241
- const [, exitCode] = await Promise.all([Promise.all([readStreamIntoBuilder(proc.stdout, builder), readStreamIntoBuilder(proc.stderr, builder)]), proc.wait()]);
90390
+ const isTerminating = () => timedOut || aborted || killed;
90391
+ const [, exitCode] = await Promise.all([Promise.all([readStreamIntoBuilder(proc.stdout, builder, "stdout", onUpdate, isTerminating), readStreamIntoBuilder(proc.stderr, builder, "stderr", onUpdate, isTerminating)]), proc.wait()]);
90242
90392
  if (timedOut) {
90243
90393
  const timeoutLabel = timeoutMs % 1e3 === 0 ? `${String(timeoutMs / 1e3)}s` : `${String(timeoutMs)}ms`;
90244
90394
  return builder.error(`Command killed by timeout (${timeoutLabel})`, { brief: `Killed by timeout (${timeoutLabel})` });
@@ -90301,7 +90451,7 @@ var BashTool = class {
90301
90451
  const info = backgroundManager.getTask(taskId);
90302
90452
  if (info && info.status === "running") backgroundManager.stop(taskId, "Timed out");
90303
90453
  })();
90304
- }, timeoutMs);
90454
+ }, timeoutMs).unref?.();
90305
90455
  const status = backgroundManager.getTask(taskId).status;
90306
90456
  const builder = new ToolResultBuilder();
90307
90457
  builder.write(`task_id: ${taskId}\npid: ${String(proc.pid)}\ndescription: ${args.description.trim()}\nstatus: ${status}\nautomatic_notification: true\nnext_step: You will be automatically notified when it completes.
@@ -90311,13 +90461,30 @@ human_shell_hint: Tell the human to run /tasks to open the interactive backgroun
90311
90461
  return builder.ok("Background task started", { brief: `Started ${taskId}` });
90312
90462
  }
90313
90463
  };
90314
- async function readStreamIntoBuilder(stream, builder) {
90464
+ async function readStreamIntoBuilder(stream, builder, kind, onUpdate, suppressPrematureClose) {
90315
90465
  const decoder = new StringDecoder("utf8");
90316
- for await (const chunk of stream) {
90317
- const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
90318
- builder.write(decoder.write(buf));
90466
+ try {
90467
+ for await (const chunk of stream) {
90468
+ const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
90469
+ const text = decoder.write(buf);
90470
+ if (text.length > 0) onUpdate?.({
90471
+ kind,
90472
+ text
90473
+ });
90474
+ builder.write(text);
90475
+ }
90476
+ } catch (error) {
90477
+ if (!isPrematureCloseError(error) || suppressPrematureClose?.() !== true) throw error;
90319
90478
  }
90320
- builder.write(decoder.end());
90479
+ const trailing = decoder.end();
90480
+ if (trailing.length > 0) onUpdate?.({
90481
+ kind,
90482
+ text: trailing
90483
+ });
90484
+ builder.write(trailing);
90485
+ }
90486
+ function isPrematureCloseError(error) {
90487
+ return error instanceof Error && error.code === "ERR_STREAM_PREMATURE_CLOSE";
90321
90488
  }
90322
90489
  function shellQuote(s) {
90323
90490
  return `'${s.replaceAll("'", "'\\''")}'`;
@@ -90337,7 +90504,7 @@ function rewriteWindowsNullRedirect(command) {
90337
90504
  return command.replace(WINDOWS_NUL_REDIRECT, "$1/dev/null");
90338
90505
  }
90339
90506
  //#endregion
90340
- //#region ../../packages/agent-core/src/tools/builtin/web/fetch-url.md
90507
+ //#region ../../packages/agent-core/src/tools/builtin/web/fetch-url.md?raw
90341
90508
  var fetch_url_default = "Fetch content from a URL. Returns the main text content extracted from the page. Use this when you need to read a specific web page.\n\nOnly public `http`/`https` URLs are supported. Requests to private, loopback, or link-local addresses are refused, and responses larger than 10 MiB are rejected.\n";
90342
90509
  //#endregion
90343
90510
  //#region ../../packages/agent-core/src/tools/builtin/web/fetch-url.ts
@@ -90410,7 +90577,7 @@ var FetchURLTool = class {
90410
90577
  }
90411
90578
  };
90412
90579
  //#endregion
90413
- //#region ../../packages/agent-core/src/tools/builtin/web/web-search.md
90580
+ //#region ../../packages/agent-core/src/tools/builtin/web/web-search.md?raw
90414
90581
  var web_search_default = "Search the web for information. Use this when you need up-to-date information from the internet.\n\nEach result includes its title, URL, snippet, and—when available—a publication date. When `include_content` is enabled, the full page content—when available—is appended after the snippet.\n";
90415
90582
  //#endregion
90416
90583
  //#region ../../packages/agent-core/src/tools/builtin/web/web-search.ts
@@ -91843,13 +92010,119 @@ function readConfigFile(filePath) {
91843
92010
  return parseConfigString(readFileSync(filePath, "utf-8"), filePath);
91844
92011
  }
91845
92012
  /**
91846
- * Load the config for runtime consumption: the on-disk config plus any model
91847
- * synthesized from `KIMI_MODEL_*` environment variables. Use this everywhere a
91848
- * value is assigned to the live runtime config; use the raw `readConfigFile`
91849
- * for write-back paths so the synthesized model is never persisted.
92013
+ * Strict read for write paths (read-merge-write must never use a salvaged
92014
+ * config as its base, or the rewrite would drop the user's broken-but-fixable
92015
+ * sections). Re-throws validation failures with a short actionable message —
92016
+ * UIs surface it directly — instead of the raw validation details.
92017
+ */
92018
+ function readConfigFileForUpdate(filePath) {
92019
+ try {
92020
+ return readConfigFile(filePath);
92021
+ } catch (error) {
92022
+ if (error instanceof KimiError && error.code === ErrorCodes.CONFIG_INVALID) throw new KimiError(ErrorCodes.CONFIG_INVALID, `Cannot change settings while ${filePath} is invalid — fix it first (run \`kimi doctor\` for details).`, { cause: error });
92023
+ throw error;
92024
+ }
92025
+ }
92026
+ /**
92027
+ * Lenient variant of `loadRuntimeConfig` that never throws: schema errors
92028
+ * drop only the offending sections (whole entry for `providers`/`models`,
92029
+ * whole top-level section otherwise) and a bad KIMI_MODEL_* env overlay is
92030
+ * skipped, each reported as a warning. A file that cannot be used at all
92031
+ * additionally sets `fileError` so startup can fail fast while mid-run
92032
+ * reloads degrade. Runtime read paths use this; write paths must keep using
92033
+ * the strict readers so a broken file is never silently rewritten.
92034
+ */
92035
+ function loadRuntimeConfigSafe(filePath, env = process.env) {
92036
+ const fileWarnings = [];
92037
+ let fileError;
92038
+ let config = getDefaultConfig();
92039
+ let text;
92040
+ try {
92041
+ text = existsSync(filePath) ? readFileSync(filePath, "utf-8") : void 0;
92042
+ } catch (error) {
92043
+ fileError = new KimiError(ErrorCodes.CONFIG_INVALID, `Failed to read ${filePath}: ${describeUnknownError(error)}`, { cause: error });
92044
+ fileWarnings.push(`Failed to read ${filePath}: ${describeUnknownError(error)}.`);
92045
+ }
92046
+ if (text !== void 0 && text.trim().length > 0) {
92047
+ let data;
92048
+ try {
92049
+ data = parse$2(text);
92050
+ } catch (error) {
92051
+ fileError = new KimiError(ErrorCodes.CONFIG_INVALID, `Invalid TOML in ${filePath}: ${describeUnknownError(error)}`, { cause: error });
92052
+ fileWarnings.push(`Invalid TOML in ${filePath}: ${describeTomlSyntaxError(error)}.`);
92053
+ }
92054
+ if (data !== void 0) {
92055
+ const raw = cloneRecord(data);
92056
+ const transformed = transformTomlData(data);
92057
+ transformed["raw"] = raw;
92058
+ const salvaged = salvageConfigData(transformed);
92059
+ if (salvaged.config === void 0) {
92060
+ fileError = new KimiError(ErrorCodes.CONFIG_INVALID, `Invalid configuration in ${filePath}: ${formatConfigValidationError(salvaged.error)}`, { cause: salvaged.error });
92061
+ fileWarnings.push(`Invalid configuration in ${filePath}: ${formatConfigValidationError(salvaged.error)}.`);
92062
+ } else {
92063
+ config = salvaged.config;
92064
+ if (salvaged.dropped.length > 0) fileWarnings.push(`Ignored invalid config in ${filePath}: ${salvaged.dropped.join(", ")}. Run \`kimi doctor\` for details.`);
92065
+ }
92066
+ }
92067
+ }
92068
+ const envWarnings = [];
92069
+ try {
92070
+ config = applyEnvModelConfig(config, env);
92071
+ } catch (error) {
92072
+ envWarnings.push(`Ignoring KIMI_MODEL_* environment overrides: ${describeUnknownError(error)}`);
92073
+ }
92074
+ return {
92075
+ config,
92076
+ fileWarnings,
92077
+ envWarnings,
92078
+ fileError
92079
+ };
92080
+ }
92081
+ /** Sections keyed by user-chosen names where single entries can be dropped. */
92082
+ const ENTRY_KEYED_SECTIONS = new Set(["providers", "models"]);
92083
+ function salvageConfigData(transformed) {
92084
+ const dropped = [];
92085
+ for (;;) {
92086
+ const result = KimiConfigSchema.safeParse(transformed);
92087
+ if (result.success) return {
92088
+ config: result.data,
92089
+ dropped
92090
+ };
92091
+ let deletedAny = false;
92092
+ for (const issue of result.error.issues) {
92093
+ const [section, entry] = issue.path;
92094
+ if (typeof section !== "string" || !(section in transformed)) continue;
92095
+ const sectionValue = transformed[section];
92096
+ if (ENTRY_KEYED_SECTIONS.has(section) && typeof entry === "string" && isPlainObject$1(sectionValue)) {
92097
+ if (entry in sectionValue) {
92098
+ delete sectionValue[entry];
92099
+ dropped.push(`${camelToSnake$2(section)}.${entry}`);
92100
+ deletedAny = true;
92101
+ }
92102
+ continue;
92103
+ }
92104
+ delete transformed[section];
92105
+ dropped.push(camelToSnake$2(section));
92106
+ deletedAny = true;
92107
+ }
92108
+ if (!deletedAny) return {
92109
+ config: void 0,
92110
+ dropped,
92111
+ error: result.error
92112
+ };
92113
+ }
92114
+ }
92115
+ function describeUnknownError(error) {
92116
+ return error instanceof Error ? error.message : String(error);
92117
+ }
92118
+ /**
92119
+ * One-line summary of a smol-toml parse error: first message line plus the
92120
+ * line/column location, without the multi-line code-frame block.
91850
92121
  */
91851
- function loadRuntimeConfig(filePath, env = process.env) {
91852
- return applyEnvModelConfig(readConfigFile(filePath), env);
92122
+ function describeTomlSyntaxError(error) {
92123
+ const firstLine = describeUnknownError(error).split("\n", 1)[0] ?? "";
92124
+ if (error instanceof TomlError) return `${firstLine} (line ${error.line}, column ${error.column})`;
92125
+ return firstLine;
91853
92126
  }
91854
92127
  function parseConfigString(tomlText, filePath = "config.toml") {
91855
92128
  if (tomlText.trim().length === 0) return getDefaultConfig();
@@ -121101,6 +121374,32 @@ var McpConnectionManager = class {
121101
121374
  this.initialLoad = initialLoad;
121102
121375
  return initialLoad;
121103
121376
  }
121377
+ async connect(name, config) {
121378
+ const previous = this.entries.get(name);
121379
+ if (previous !== void 0) await this.closeClient(previous);
121380
+ const disabled = config.enabled === false;
121381
+ const entry = {
121382
+ name,
121383
+ config,
121384
+ attemptId: 0,
121385
+ status: disabled ? "disabled" : "pending"
121386
+ };
121387
+ this.entries.set(name, entry);
121388
+ this.emit(entry);
121389
+ if (!disabled) await this.connectOne(entry, this.beginConnectAttempt(entry));
121390
+ }
121391
+ async remove(name) {
121392
+ const entry = this.entries.get(name);
121393
+ if (entry === void 0) return false;
121394
+ await this.closeClient(entry);
121395
+ entry.status = "disabled";
121396
+ entry.tools = void 0;
121397
+ entry.enabledNames = void 0;
121398
+ entry.error = void 0;
121399
+ this.emit(entry);
121400
+ this.entries.delete(name);
121401
+ return true;
121402
+ }
121104
121403
  waitForInitialLoad(signal) {
121105
121404
  signal?.throwIfAborted();
121106
121405
  if (signal === void 0) return this.initialLoad;
@@ -121450,6 +121749,20 @@ function mergeCallerMcpServers(base, callerServers) {
121450
121749
  //#endregion
121451
121750
  //#region ../../packages/agent-core/src/session/index.ts
121452
121751
  const BACKGROUND_KEEP_ALIVE_ON_EXIT_ENV = "KIMI_CODE_BACKGROUND_KEEP_ALIVE_ON_EXIT";
121752
+ const ACTIVE_TURN_CLOSE_TIMEOUT_MS = 8e3;
121753
+ async function waitForSettlementOrTimeout(promise, timeoutMs) {
121754
+ let timeout;
121755
+ try {
121756
+ return await Promise.race([promise.then(() => true, () => true), new Promise((resolve) => {
121757
+ timeout = setTimeout(() => {
121758
+ resolve(false);
121759
+ }, timeoutMs);
121760
+ timeout.unref?.();
121761
+ })]);
121762
+ } finally {
121763
+ if (timeout !== void 0) clearTimeout(timeout);
121764
+ }
121765
+ }
121453
121766
  var Session$1 = class {
121454
121767
  options;
121455
121768
  rpc;
@@ -121541,6 +121854,7 @@ var Session$1 = class {
121541
121854
  async close() {
121542
121855
  try {
121543
121856
  await Promise.allSettled(Array.from(this.readyAgents(), async (agent) => agent.cron?.stop()));
121857
+ await this.cancelActiveTurnsOnClose();
121544
121858
  await this.stopBackgroundTasksOnExit();
121545
121859
  await this.flushMetadata();
121546
121860
  await this.triggerSessionEnd("exit");
@@ -121564,12 +121878,46 @@ var Session$1 = class {
121564
121878
  }
121565
121879
  }
121566
121880
  }
121881
+ async cancelActiveTurnsOnClose() {
121882
+ const backgroundAgentIds = this.activeBackgroundAgentIds();
121883
+ const cancellations = [];
121884
+ for (const [agentId, entry] of this.agents) {
121885
+ if (!(entry instanceof Agent$1) || backgroundAgentIds.has(agentId)) continue;
121886
+ cancellations.push(this.cancelAgentTurnOnClose(entry));
121887
+ }
121888
+ await Promise.allSettled(cancellations);
121889
+ }
121890
+ activeBackgroundAgentIds() {
121891
+ const agentIds = /* @__PURE__ */ new Set();
121892
+ for (const agent of this.readyAgents()) for (const task of agent.background.list(true)) if (task.kind === "agent" && task.agentId !== void 0) agentIds.add(task.agentId);
121893
+ return agentIds;
121894
+ }
121895
+ async cancelAgentTurnOnClose(agent) {
121896
+ if (!agent.turn.hasActiveTurn) return;
121897
+ let waitForTurn;
121898
+ try {
121899
+ waitForTurn = agent.turn.waitForCurrentTurn();
121900
+ } catch (error) {
121901
+ this.log.debug("active turn wait unavailable during session close", {
121902
+ agentType: agent.type,
121903
+ agentHomedir: agent.homedir,
121904
+ error
121905
+ });
121906
+ return;
121907
+ }
121908
+ agent.turn.cancel(void 0, abortError$1("Session closed"));
121909
+ if (!await waitForSettlementOrTimeout(waitForTurn, ACTIVE_TURN_CLOSE_TIMEOUT_MS)) this.log.warn("timed out waiting for active turn to cancel during session close", {
121910
+ agentType: agent.type,
121911
+ agentHomedir: agent.homedir,
121912
+ timeoutMs: ACTIVE_TURN_CLOSE_TIMEOUT_MS
121913
+ });
121914
+ }
121567
121915
  async stopBackgroundTasksOnExit() {
121568
121916
  if (resolveConfigValue({
121569
121917
  env: process.env,
121570
121918
  envKey: BACKGROUND_KEEP_ALIVE_ON_EXIT_ENV,
121571
121919
  configValue: this.options.background?.keepAliveOnExit,
121572
- defaultValue: true,
121920
+ defaultValue: false,
121573
121921
  parseEnv: parseBooleanEnv
121574
121922
  })) return;
121575
121923
  await Promise.all(Array.from(this.readyAgents(), (agent) => agent.background.stopAll("Session closed")));
@@ -136951,6 +137299,7 @@ var ProviderManager = class {
136951
137299
  providerName,
136952
137300
  provider,
136953
137301
  modelCapabilities: resolveModelCapabilities(alias, provider),
137302
+ alwaysThinking: (alias.capabilities ?? []).some((c) => c.trim().toLowerCase() === "always_thinking"),
136954
137303
  maxOutputSize: alias.maxOutputSize
136955
137304
  };
136956
137305
  }
@@ -138335,9 +138684,11 @@ var LocalKaos = class LocalKaos {
138335
138684
  name = "local";
138336
138685
  osEnv;
138337
138686
  _cwd;
138338
- constructor(osEnv, cwd) {
138687
+ _envLayers;
138688
+ constructor(osEnv, cwd, envLayers = []) {
138339
138689
  this._cwd = normalize(cwd ?? process.cwd());
138340
138690
  this.osEnv = osEnv;
138691
+ this._envLayers = envLayers;
138341
138692
  }
138342
138693
  /**
138343
138694
  * Construct a fresh `LocalKaos` after probing the host environment.
@@ -138350,7 +138701,10 @@ var LocalKaos = class LocalKaos {
138350
138701
  return new LocalKaos(await detectEnvironmentFromNode());
138351
138702
  }
138352
138703
  withCwd(cwd) {
138353
- return new LocalKaos(this.osEnv, cwd);
138704
+ return new LocalKaos(this.osEnv, cwd, this._envLayers);
138705
+ }
138706
+ withEnv(env) {
138707
+ return new LocalKaos(this.osEnv, this._cwd, [...this._envLayers, env]);
138354
138708
  }
138355
138709
  _resolvePath(path) {
138356
138710
  if (isAbsolute(path)) return normalize(path);
@@ -138543,7 +138897,8 @@ var LocalKaos = class LocalKaos {
138543
138897
  "pipe",
138544
138898
  "pipe"
138545
138899
  ],
138546
- detached: !isWindows
138900
+ detached: !isWindows,
138901
+ env: this._buildExecEnv()
138547
138902
  });
138548
138903
  await waitForSpawn(child);
138549
138904
  return new LocalProcess(child);
@@ -138559,11 +138914,20 @@ var LocalKaos = class LocalKaos {
138559
138914
  "pipe"
138560
138915
  ],
138561
138916
  detached: !isWindows,
138562
- env
138917
+ env: this._buildExecEnv(env)
138563
138918
  });
138564
138919
  await waitForSpawn(child);
138565
138920
  return new LocalProcess(child);
138566
138921
  }
138922
+ _buildExecEnv(invocationEnv) {
138923
+ if (this._envLayers.length === 0) return invocationEnv;
138924
+ const merged = {
138925
+ ...process.env,
138926
+ ...invocationEnv
138927
+ };
138928
+ for (const layer of this._envLayers) Object.assign(merged, layer);
138929
+ return merged;
138930
+ }
138567
138931
  };
138568
138932
  function waitForSpawn(child) {
138569
138933
  return new Promise((resolve, reject) => {
@@ -138596,6 +138960,7 @@ var KimiCore = class {
138596
138960
  kaos;
138597
138961
  runtime;
138598
138962
  config;
138963
+ configWarnings = [];
138599
138964
  runtimeOverride;
138600
138965
  userHomeDir;
138601
138966
  kimiRequestHeaders;
@@ -138623,7 +138988,11 @@ var KimiCore = class {
138623
138988
  this.telemetry = options.telemetry ?? noopTelemetryClient;
138624
138989
  this.appVersion = options.appVersion;
138625
138990
  ensureKimiHome(this.homeDir);
138626
- this.config = loadRuntimeConfig(this.configPath);
138991
+ const loaded = loadRuntimeConfigSafe(this.configPath);
138992
+ if (loaded.fileError !== void 0) throw loaded.fileError;
138993
+ this.config = loaded.config;
138994
+ this.configWarnings = [...loaded.fileWarnings, ...loaded.envWarnings];
138995
+ if (this.configWarnings.length > 0) log.warn("config load degraded", { warnings: this.configWarnings });
138627
138996
  this.experimentalFlags = new FlagResolver(process.env, FLAG_DEFINITIONS, this.config.experimental);
138628
138997
  this.sessionStore = new SessionStore(this.homeDir);
138629
138998
  this.plugins = new PluginManager({ kimiHomeDir: this.homeDir });
@@ -138832,16 +139201,19 @@ var KimiCore = class {
138832
139201
  });
138833
139202
  }
138834
139203
  async getKimiConfig(input) {
138835
- if (input?.reload) this.setRuntimeConfig(loadRuntimeConfig(this.configPath));
139204
+ if (input?.reload) this.reloadRuntimeConfig();
138836
139205
  return this.config;
138837
139206
  }
139207
+ async getConfigDiagnostics(_input) {
139208
+ return { warnings: this.configWarnings };
139209
+ }
138838
139210
  async setKimiConfig(input) {
138839
- const config = mergeConfigPatch(readConfigFile(this.configPath), input);
139211
+ const config = mergeConfigPatch(this.readConfigForWrite(), input);
138840
139212
  await writeConfigFile(this.configPath, config);
138841
- return this.setRuntimeConfig(loadRuntimeConfig(this.configPath));
139213
+ return this.reloadRuntimeConfig();
138842
139214
  }
138843
139215
  async removeKimiProvider(input) {
138844
- const config = readConfigFile(this.configPath);
139216
+ const config = this.readConfigForWrite();
138845
139217
  delete config.providers[input.providerId];
138846
139218
  let removedDefault = false;
138847
139219
  const existingModels = config.models ?? {};
@@ -138853,7 +139225,7 @@ var KimiCore = class {
138853
139225
  if (removedDefault) config.defaultModel = void 0;
138854
139226
  if (config.defaultProvider === input.providerId) config.defaultProvider = void 0;
138855
139227
  await writeConfigFile(this.configPath, config);
138856
- return this.setRuntimeConfig(loadRuntimeConfig(this.configPath));
139228
+ return this.reloadRuntimeConfig();
138857
139229
  }
138858
139230
  prompt({ sessionId, ...payload }) {
138859
139231
  return this.sessionApi(sessionId).prompt(payload);
@@ -139113,7 +139485,24 @@ var KimiCore = class {
139113
139485
  return new SessionAPIImpl(session);
139114
139486
  }
139115
139487
  reloadProviderManager() {
139116
- return this.setRuntimeConfig(loadRuntimeConfig(this.configPath));
139488
+ return this.reloadRuntimeConfig();
139489
+ }
139490
+ readConfigForWrite() {
139491
+ return readConfigFileForUpdate(this.configPath);
139492
+ }
139493
+ reloadRuntimeConfig() {
139494
+ const loaded = loadRuntimeConfigSafe(this.configPath);
139495
+ if (loaded.fileWarnings.length > 0) {
139496
+ this.configWarnings = [
139497
+ ...loaded.fileWarnings,
139498
+ ...loaded.envWarnings,
139499
+ "config.toml has errors; keeping the previously loaded configuration."
139500
+ ];
139501
+ log.warn("config reload degraded; keeping previous config", { warnings: loaded.fileWarnings });
139502
+ return this.config;
139503
+ }
139504
+ this.configWarnings = loaded.envWarnings;
139505
+ return this.setRuntimeConfig(loaded.config);
139117
139506
  }
139118
139507
  setRuntimeConfig(config) {
139119
139508
  this.config = config;
@@ -139649,8 +140038,8 @@ var KimiHarness = class {
139649
140038
  get interactiveAgentId() {
139650
140039
  return this.rpc.interactiveAgentId;
139651
140040
  }
139652
- set interactiveAgentId(agentId) {
139653
- this.rpc.interactiveAgentId = agentId;
140041
+ withInteractiveAgent(agentId, fn) {
140042
+ return this.rpc.withInteractiveAgent(agentId, fn);
139654
140043
  }
139655
140044
  track(event, properties) {
139656
140045
  this.telemetry.track(event, properties);
@@ -139776,6 +140165,10 @@ var KimiHarness = class {
139776
140165
  async getConfig(options = {}) {
139777
140166
  return this.rpc.getConfig(options);
139778
140167
  }
140168
+ /** Warnings from the most recent config.toml load; empty when the config is fully valid. */
140169
+ async getConfigDiagnostics() {
140170
+ return this.rpc.getConfigDiagnostics();
140171
+ }
139779
140172
  async getExperimentalFeatures() {
139780
140173
  return this.rpc.getExperimentalFeatures();
139781
140174
  }
@@ -142102,7 +142495,19 @@ function managedModelKey(modelId) {
142102
142495
  }
142103
142496
  function capabilitiesForModel$1(model) {
142104
142497
  const caps = /* @__PURE__ */ new Set();
142105
- if (model.supportsReasoning) caps.add("thinking");
142498
+ switch (model.supportsThinkingType) {
142499
+ case "only":
142500
+ caps.add("thinking");
142501
+ caps.add("always_thinking");
142502
+ break;
142503
+ case "both":
142504
+ caps.add("thinking");
142505
+ break;
142506
+ case "no": break;
142507
+ case void 0:
142508
+ if (model.supportsReasoning) caps.add("thinking");
142509
+ break;
142510
+ }
142106
142511
  if (model.supportsImageIn) caps.add("image_in");
142107
142512
  if (model.supportsVideoIn) caps.add("video_in");
142108
142513
  if (model.supportsToolUse ?? true) caps.add("tool_use");
@@ -142245,9 +142650,13 @@ function toModelInfo$1(item) {
142245
142650
  supportsImageIn: Boolean(item["supports_image_in"]),
142246
142651
  supportsVideoIn: Boolean(item["supports_video_in"]),
142247
142652
  supportsToolUse,
142653
+ supportsThinkingType: parseSupportsThinkingType(item["supports_thinking_type"]),
142248
142654
  displayName: normalizedDisplayName
142249
142655
  };
142250
142656
  }
142657
+ function parseSupportsThinkingType(value) {
142658
+ return value === "only" || value === "no" || value === "both" ? value : void 0;
142659
+ }
142251
142660
  async function fetchManagedKimiCodeModels(options) {
142252
142661
  const fetchImpl = options.fetchImpl ?? fetch;
142253
142662
  const baseUrl = defaultBaseUrl(options.baseUrl);
@@ -142336,6 +142745,11 @@ function applyManagedKimiCodeLogoutConfig(config) {
142336
142745
  if (Object.keys(config.services).length === 0) config.services = void 0;
142337
142746
  }
142338
142747
  }
142748
+ function forcedThinking(model, fallback) {
142749
+ if (model?.supportsThinkingType === "only") return true;
142750
+ if (model?.supportsThinkingType === "no") return false;
142751
+ return fallback;
142752
+ }
142339
142753
  function selectDefaultModel(config, models, options) {
142340
142754
  const firstModel = models[0];
142341
142755
  if (firstModel === void 0) throw new Error("No models available for Kimi Code.");
@@ -142346,12 +142760,12 @@ function selectDefaultModel(config, models, options) {
142346
142760
  const preservedModel = managedModels.get(currentDefault);
142347
142761
  return {
142348
142762
  modelKey: currentDefault,
142349
- thinking: config.defaultThinking ?? preservedModel?.supportsReasoning ?? false
142763
+ thinking: forcedThinking(preservedModel, config.defaultThinking ?? preservedModel?.supportsReasoning ?? false)
142350
142764
  };
142351
142765
  }
142352
142766
  return {
142353
142767
  modelKey: managedModelKey(firstModel.id),
142354
- thinking: config.defaultThinking ?? firstModel.supportsReasoning
142768
+ thinking: forcedThinking(firstModel, config.defaultThinking ?? firstModel.supportsReasoning)
142355
142769
  };
142356
142770
  }
142357
142771
  function canPreserveDefaultModel(existingModels, defaultModel, managedModels) {
@@ -142463,12 +142877,25 @@ function toModelInfo(item) {
142463
142877
  supportsImageIn: Boolean(item["supports_image_in"]),
142464
142878
  supportsVideoIn: Boolean(item["supports_video_in"]),
142465
142879
  supportsToolUse,
142880
+ supportsThinkingType: parseSupportsThinkingType(item["supports_thinking_type"]),
142466
142881
  displayName: normalizedDisplayName
142467
142882
  };
142468
142883
  }
142469
142884
  function capabilitiesForModel(model) {
142470
142885
  const caps = /* @__PURE__ */ new Set();
142471
- if (model.supportsReasoning) caps.add("thinking");
142886
+ switch (model.supportsThinkingType) {
142887
+ case "only":
142888
+ caps.add("thinking");
142889
+ caps.add("always_thinking");
142890
+ break;
142891
+ case "both":
142892
+ caps.add("thinking");
142893
+ break;
142894
+ case "no": break;
142895
+ case void 0:
142896
+ if (model.supportsReasoning) caps.add("thinking");
142897
+ break;
142898
+ }
142472
142899
  if (model.supportsImageIn) caps.add("image_in");
142473
142900
  if (model.supportsVideoIn) caps.add("video_in");
142474
142901
  if (model.supportsToolUse ?? true) caps.add("tool_use");
@@ -142999,7 +143426,7 @@ var KimiAuthFacade = class {
142999
143426
  onRefresh: options.onRefresh,
143000
143427
  configAdapter: {
143001
143428
  configPath: options.configPath,
143002
- read: () => readConfigFile(options.configPath),
143429
+ read: () => readConfigFileForUpdate(options.configPath),
143003
143430
  write: async (config) => {
143004
143431
  await writeConfigFile(options.configPath, config);
143005
143432
  },
@@ -143074,7 +143501,7 @@ var KimiAuthFacade = class {
143074
143501
  };
143075
143502
  resolveManagedAuth(providerName) {
143076
143503
  const name = providerName ?? "managed:kimi-code";
143077
- const provider = readConfigFile(this.options.configPath).providers[name];
143504
+ const provider = loadRuntimeConfigSafe(this.options.configPath).config.providers[name];
143078
143505
  return {
143079
143506
  oauthRef: provider?.oauth,
143080
143507
  baseUrl: provider?.baseUrl
@@ -143100,10 +143527,16 @@ var KimiAuthFacade = class {
143100
143527
  //#region ../../packages/node-sdk/src/rpc.ts
143101
143528
  const MAIN_AGENT_ID$2 = "main";
143102
143529
  var SDKRpcClientBase = class {
143103
- interactiveAgentId = MAIN_AGENT_ID$2;
143530
+ interactiveAgentScope = new AsyncLocalStorage();
143104
143531
  eventListeners = /* @__PURE__ */ new Set();
143105
143532
  approvalHandlers = /* @__PURE__ */ new Map();
143106
143533
  questionHandlers = /* @__PURE__ */ new Map();
143534
+ get interactiveAgentId() {
143535
+ return this.interactiveAgentScope.getStore() ?? MAIN_AGENT_ID$2;
143536
+ }
143537
+ withInteractiveAgent(agentId, fn) {
143538
+ return this.interactiveAgentScope.run(agentId, fn);
143539
+ }
143107
143540
  async createSession(input) {
143108
143541
  const rpc = await this.getRpc();
143109
143542
  const { planMode, ...coreInput } = input;
@@ -143157,6 +143590,9 @@ var SDKRpcClientBase = class {
143157
143590
  async getConfig(input) {
143158
143591
  return (await this.getRpc()).getKimiConfig(input ?? {});
143159
143592
  }
143593
+ async getConfigDiagnostics() {
143594
+ return (await this.getRpc()).getConfigDiagnostics({});
143595
+ }
143160
143596
  async getExperimentalFeatures() {
143161
143597
  return (await this.getRpc()).getExperimentalFeatures({});
143162
143598
  }
@@ -144401,6 +144837,7 @@ const FEEDBACK_VERSION_PREFIX = "kimi-code-";
144401
144837
  const FEEDBACK_TELEMETRY_EVENT = "feedback_submitted";
144402
144838
  const KIMI_CODE_CDN_BASE = "https://code.kimi.com/kimi-code";
144403
144839
  const KIMI_CODE_CDN_LATEST_URL = `${KIMI_CODE_CDN_BASE}/latest`;
144840
+ const KIMI_CODE_TIPS_BANNER_URL = "https://cdn.kimi.com/kimi-code-tips/tips.json";
144404
144841
  const KIMI_CODE_PLUGIN_MARKETPLACE_URL = `${KIMI_CODE_CDN_BASE}/plugins/marketplace.json`;
144405
144842
  const KIMI_CODE_PLUGIN_MARKETPLACE_URL_ENV = "KIMI_CODE_PLUGIN_MARKETPLACE_URL";
144406
144843
  const KIMI_CODE_INSTALL_SH_URL = `${KIMI_CODE_CDN_BASE}/install.sh`;
@@ -150542,6 +150979,9 @@ var AcpKaos = class AcpKaos {
150542
150979
  withCwd(cwd) {
150543
150980
  return new AcpKaos(this.conn, this.sessionId, this.inner.withCwd(cwd));
150544
150981
  }
150982
+ withEnv(env) {
150983
+ return new AcpKaos(this.conn, this.sessionId, this.inner.withEnv(env));
150984
+ }
150545
150985
  stat(path, options) {
150546
150986
  return this.inner.stat(path, options);
150547
150987
  }
@@ -150562,13 +151002,14 @@ var AcpKaos = class AcpKaos {
150562
151002
  * should be routed through a non-ACP Kaos.
150563
151003
  */
150564
151004
  async readText(path, _options) {
151005
+ const rpcPath = this.toClientPath(path);
150565
151006
  try {
150566
151007
  return (await this.conn.readTextFile({
150567
151008
  sessionId: this.sessionId,
150568
- path
151009
+ path: rpcPath
150569
151010
  })).content;
150570
151011
  } catch (err) {
150571
- throw wrapKaosError(`acp: readTextFile failed for ${path}`, err);
151012
+ throw wrapKaosError(`acp: readTextFile failed for ${rpcPath}`, err);
150572
151013
  }
150573
151014
  }
150574
151015
  /**
@@ -150646,16 +151087,21 @@ var AcpKaos = class AcpKaos {
150646
151087
  return data.byteLength;
150647
151088
  }
150648
151089
  async acpWrite(path, content) {
151090
+ const rpcPath = this.toClientPath(path);
150649
151091
  try {
150650
151092
  await this.conn.writeTextFile({
150651
151093
  sessionId: this.sessionId,
150652
- path,
151094
+ path: rpcPath,
150653
151095
  content
150654
151096
  });
150655
151097
  } catch (err) {
150656
- throw wrapKaosError(`acp: writeTextFile failed for ${path}`, err);
151098
+ throw wrapKaosError(`acp: writeTextFile failed for ${rpcPath}`, err);
150657
151099
  }
150658
151100
  }
151101
+ toClientPath(path) {
151102
+ if (this.inner.pathClass() !== "win32") return path;
151103
+ return path.replaceAll("/", "\\");
151104
+ }
150659
151105
  exec(...args) {
150660
151106
  return this.inner.exec(...args);
150661
151107
  }
@@ -151702,13 +152148,24 @@ function acpModeToToggles(id) {
151702
152148
  */
151703
152149
  const TOGGLEABLE_THINKING_MODELS = new Set(["kimi-for-coding", "kimi-code"]);
151704
152150
  function deriveThinkingSupported(alias) {
151705
- if ((alias.capabilities ?? []).includes("thinking")) return true;
152151
+ const declared = alias.capabilities ?? [];
152152
+ if (declared.includes("thinking") || declared.includes("always_thinking")) return true;
151706
152153
  const lower = alias.model.toLowerCase();
151707
152154
  if (lower.includes("thinking") || lower.includes("reason")) return true;
151708
152155
  if (TOGGLEABLE_THINKING_MODELS.has(alias.model)) return true;
151709
152156
  return false;
151710
152157
  }
151711
152158
  /**
152159
+ * Whether the alias declares the 'always_thinking' capability — the model
152160
+ * cannot run with thinking disabled, so the ACP toggle must lock to on.
152161
+ * Deliberately capability-only: the name heuristics above keep feeding
152162
+ * `thinkingSupported`, but only an explicit (server-derived) declaration
152163
+ * may remove the off option from the client.
152164
+ */
152165
+ function deriveAlwaysThinking(alias) {
152166
+ return (alias.capabilities ?? []).includes("always_thinking");
152167
+ }
152168
+ /**
151712
152169
  * Project `harness.getConfig().models` into a flat catalog. Returns an
151713
152170
  * empty array when the harness has no models configured, when
151714
152171
  * `getConfig` is missing on the harness (partial test stubs), or when
@@ -151729,7 +152186,8 @@ async function listModelsFromHarness(harness) {
151729
152186
  for (const [id, alias] of Object.entries(models)) out.push({
151730
152187
  id,
151731
152188
  name: alias.displayName ?? alias.model ?? id,
151732
- thinkingSupported: deriveThinkingSupported(alias)
152189
+ thinkingSupported: deriveThinkingSupported(alias),
152190
+ alwaysThinking: deriveAlwaysThinking(alias)
151733
152191
  });
151734
152192
  return out;
151735
152193
  }
@@ -151788,8 +152246,26 @@ function buildModelOption(models, currentBaseModelId) {
151788
152246
  * currently-selected model has `thinkingSupported === false`, the
151789
152247
  * snapshot omits it entirely (dynamic visibility), so the client never
151790
152248
  * shows a toggle that wouldn't do anything.
152249
+ *
152250
+ * `alwaysThinking` models (declared `always_thinking` capability — the
152251
+ * runtime cannot disable thinking) collapse the select to a single
152252
+ * locked `on` entry: the state stays visible to the client, but there
152253
+ * is no off option to pick. ACP has no "disabled entry" concept, so
152254
+ * omitting `off` is the wire-level equivalent of the TUI's greyed-out
152255
+ * `Off (Unsupported)` segment.
151791
152256
  */
151792
- function buildThinkingOption(enabled) {
152257
+ function buildThinkingOption(enabled, alwaysThinking = false) {
152258
+ if (alwaysThinking) return {
152259
+ type: "select",
152260
+ id: "thinking",
152261
+ name: "Thinking",
152262
+ category: "thought_level",
152263
+ currentValue: "on",
152264
+ options: [{
152265
+ value: "on",
152266
+ name: "Thinking On"
152267
+ }]
152268
+ };
151793
152269
  return {
151794
152270
  type: "select",
151795
152271
  id: "thinking",
@@ -151856,9 +152332,11 @@ function buildModeOption(currentModeId) {
151856
152332
  */
151857
152333
  async function buildSessionConfigOptions(harness, currentBaseModelId, currentThinkingEnabled, currentModeId) {
151858
152334
  const models = await listModelsFromHarness(harness);
151859
- const showThinking = models.find((m) => m.id === currentBaseModelId)?.thinkingSupported === true;
152335
+ const currentModelEntry = models.find((m) => m.id === currentBaseModelId);
152336
+ const showThinking = currentModelEntry?.thinkingSupported === true;
152337
+ const alwaysThinking = currentModelEntry?.alwaysThinking === true;
151860
152338
  const out = [buildModelOption(models, currentBaseModelId)];
151861
- if (showThinking) out.push(buildThinkingOption(currentThinkingEnabled));
152339
+ if (showThinking) out.push(buildThinkingOption(alwaysThinking || currentThinkingEnabled, alwaysThinking));
151862
152340
  out.push(buildModeOption(currentModeId));
151863
152341
  return out;
151864
152342
  }
@@ -152204,11 +152682,25 @@ var AcpSession = class {
152204
152682
  * carries a fresh snapshot.
152205
152683
  */
152206
152684
  async setThinking(enabled) {
152685
+ if (!enabled && await this.currentModelAlwaysThinking()) {
152686
+ this.currentThinkingEnabledInternal = true;
152687
+ await this.emitConfigOptionUpdate();
152688
+ return;
152689
+ }
152207
152690
  if (typeof this.session.setThinking === "function") await this.session.setThinking(enabled ? THINKING_ON_LEVEL : THINKING_OFF_LEVEL);
152208
152691
  this.currentThinkingEnabledInternal = enabled;
152209
152692
  await this.emitConfigOptionUpdate();
152210
152693
  }
152211
152694
  /**
152695
+ * Whether the currently-selected model declares 'always_thinking'.
152696
+ * Harness-less adapter unit tests resolve to false — the agent-core
152697
+ * runtime clamp still protects the actual request in that case.
152698
+ */
152699
+ async currentModelAlwaysThinking() {
152700
+ if (!this.harness) return false;
152701
+ return (await listModelsFromHarness(this.harness)).find((m) => m.id === this.currentModelIdInternal)?.alwaysThinking === true;
152702
+ }
152703
+ /**
152212
152704
  * Forward an ACP `session/set_mode` request to the underlying SDK
152213
152705
  * session.
152214
152706
  *
@@ -153909,7 +154401,7 @@ function buildSkillSlashCommands(skills) {
153909
154401
  const commandMap = /* @__PURE__ */ new Map();
153910
154402
  return {
153911
154403
  commands: [...skills].toSorted(compareSkillSlashCommands).filter(isUserActivatableSkill).map((skill) => {
153912
- const commandName = skill.source === "builtin" ? skill.name : `skill:${skill.name}`;
154404
+ const commandName = skill.source === "builtin" || skill.isSubSkill === true ? skill.name : `skill:${skill.name}`;
153913
154405
  commandMap.set(commandName, skill.name);
153914
154406
  return {
153915
154407
  name: commandName,
@@ -154649,8 +155141,9 @@ function registerLoginCommand(parent) {
154649
155141
  *
154650
155142
  * `add` writes the same `source = { kind: 'apiJson', url, apiKey }` blob the
154651
155143
  * TUI does; the next launch's `refreshAllProviderModels`
154652
- * (apps/kimi-code/src/tui/utils/refresh-providers.ts) groups by `{url, apiKey}`
154653
- * and re-fetches the model list, so periodic refresh is automatic.
155144
+ * (apps/kimi-code/src/tui/utils/refresh-providers.ts) groups by URL, retries
155145
+ * available API-key candidates, and re-fetches the model list, so periodic
155146
+ * refresh is automatic.
154654
155147
  */
154655
155148
  async function handleProviderAdd$1(deps, url, opts) {
154656
155149
  const apiKey = resolveApiKey(opts.apiKey, deps.env);
@@ -154874,29 +155367,42 @@ async function loadCatalogOrExit(deps, url) {
154874
155367
  }
154875
155368
  function registerProviderCommand(parent, deps) {
154876
155369
  const provider = parent.command("provider").description("Manage LLM providers non-interactively.");
155370
+ const runAction = async (resolved, run) => {
155371
+ try {
155372
+ await run();
155373
+ } catch (error) {
155374
+ resolved.stderr.write(`${errorMessage(error)}\n`);
155375
+ resolved.exit(1);
155376
+ }
155377
+ };
154877
155378
  provider.command("add <url>").description("Import every provider listed in a custom registry (api.json).").option("--api-key <key>", "Registry API key. Falls back to KIMI_REGISTRY_API_KEY.").action(async (url, options) => {
154878
- await handleProviderAdd$1(resolveDeps(deps), url, { apiKey: options.apiKey });
155379
+ const resolved = resolveDeps(deps);
155380
+ await runAction(resolved, () => handleProviderAdd$1(resolved, url, { apiKey: options.apiKey }));
154879
155381
  });
154880
155382
  provider.command("remove <providerId>").description("Remove a provider and every model alias that referenced it.").action(async (providerId) => {
154881
- await handleProviderRemove(resolveDeps(deps), providerId);
155383
+ const resolved = resolveDeps(deps);
155384
+ await runAction(resolved, () => handleProviderRemove(resolved, providerId));
154882
155385
  });
154883
155386
  provider.command("list").description("Show configured providers and their model counts.").option("--json", "Emit the raw providers/models config as JSON.", false).action(async (options) => {
154884
- await handleProviderList(resolveDeps(deps), { json: options.json === true });
155387
+ const resolved = resolveDeps(deps);
155388
+ await runAction(resolved, () => handleProviderList(resolved, { json: options.json === true }));
154885
155389
  });
154886
155390
  const catalog = provider.command("catalog").description("Discover and import providers from the public models.dev catalog.");
154887
155391
  catalog.command("list [providerId]").description("List providers in the catalog, or models when a providerId is given.").option("--filter <substring>", "Case-insensitive id/name substring filter.").option("--url <url>", `Override catalog URL. Defaults to ${DEFAULT_CATALOG_URL}.`).option("--json", "Emit the matching catalog slice as JSON.", false).action(async (providerId, options) => {
154888
- await handleCatalogList(resolveDeps(deps), providerId, {
155392
+ const resolved = resolveDeps(deps);
155393
+ await runAction(resolved, () => handleCatalogList(resolved, providerId, {
154889
155394
  json: options.json === true,
154890
155395
  ...options.filter === void 0 ? {} : { filter: options.filter },
154891
155396
  ...options.url === void 0 ? {} : { url: options.url }
154892
- });
155397
+ }));
154893
155398
  });
154894
155399
  catalog.command("add <providerId>").description("Import a known provider from the catalog by id.").option("--api-key <key>", "API key for the provider. Falls back to KIMI_REGISTRY_API_KEY.").option("--default-model <modelId>", "Mark the imported model as default_model after import.").option("--url <url>", `Override catalog URL. Defaults to ${DEFAULT_CATALOG_URL}.`).action(async (providerId, options) => {
154895
- await handleCatalogAdd(resolveDeps(deps), providerId, {
155400
+ const resolved = resolveDeps(deps);
155401
+ await runAction(resolved, () => handleCatalogAdd(resolved, providerId, {
154896
155402
  ...options.apiKey === void 0 ? {} : { apiKey: options.apiKey },
154897
155403
  ...options.defaultModel === void 0 ? {} : { defaultModel: options.defaultModel },
154898
155404
  ...options.url === void 0 ? {} : { url: options.url }
154899
- });
155405
+ }));
154900
155406
  });
154901
155407
  }
154902
155408
  function resolveDeps(overrides = {}) {
@@ -154990,9 +155496,6 @@ function validateOptions(opts) {
154990
155496
  if (promptMode && opts.session === "") throw new OptionConflictError("Cannot use --session without an id in prompt mode.");
154991
155497
  if (opts.continue && opts.session !== void 0) throw new OptionConflictError("Cannot combine --continue, --session.");
154992
155498
  if (opts.yolo && opts.auto) throw new OptionConflictError("Cannot combine --yolo with --auto.");
154993
- if (!promptMode && (opts.continue || opts.session !== void 0) && opts.yolo) throw new OptionConflictError("Cannot combine --yolo with --continue or --session.");
154994
- if (!promptMode && (opts.continue || opts.session !== void 0) && opts.auto) throw new OptionConflictError("Cannot combine --auto with --continue or --session.");
154995
- if (!promptMode && (opts.continue || opts.session !== void 0) && opts.plan) throw new OptionConflictError("Cannot combine --plan with --continue or --session.");
154996
155499
  return {
154997
155500
  options: opts,
154998
155501
  uiMode: promptMode ? "print" : "shell"
@@ -155413,7 +155916,6 @@ const LLM_NOT_SET_MESSAGE = "LLM not set, send \"/login\" to login";
155413
155916
  const NO_ACTIVE_SESSION_MESSAGE = "No active session. Send /login to login.";
155414
155917
  const CTRL_D_HINT = "Press Ctrl+D again to exit";
155415
155918
  const CTRL_C_HINT = "Press Ctrl+C again to exit";
155416
- const MAIN_AGENT_ID = "main";
155417
155919
  const OAUTH_LOGIN_REQUIRED_STARTUP_NOTICE = "OAuth login expired. Send /login to login.";
155418
155920
  const EXIT_CONFIRM_WINDOW_MS = 1500;
155419
155921
  function isManagedUsageProvider(providerKey) {
@@ -155774,7 +156276,7 @@ var SearchableList = class {
155774
156276
  * addChild(picker), setFocus(picker); the picker invokes `onSelect` or
155775
156277
  * `onCancel`, and the host tears it down.
155776
156278
  */
155777
- function wrapDescription(text, width) {
156279
+ function wrapDescription$1(text, width) {
155778
156280
  const maxWidth = Math.max(1, width);
155779
156281
  const words = text.trim().split(/\s+/).filter((word) => word.length > 0);
155780
156282
  const lines = [];
@@ -155859,7 +156361,7 @@ var ChoicePickerComponent = class extends Container {
155859
156361
  lines.push(line);
155860
156362
  if (opt.description !== void 0 && opt.description.length > 0) {
155861
156363
  const descriptionWidth = Math.max(1, width - 4);
155862
- for (const descLine of wrapDescription(opt.description, descriptionWidth)) lines.push(currentTheme.fg("textMuted", ` ${descLine}`));
156364
+ for (const descLine of wrapDescription$1(opt.description, descriptionWidth)) lines.push(currentTheme.fg("textMuted", ` ${descLine}`));
155863
156365
  }
155864
156366
  }
155865
156367
  lines.push("");
@@ -156130,9 +156632,10 @@ var ModelSelectorComponent = class extends Container {
156130
156632
  }
156131
156633
  renderThinkingControl(choice) {
156132
156634
  const segment = (label, active) => active ? currentTheme.boldFg("primary", `[ ${label} ]`) : currentTheme.fg("text", ` ${label} `);
156635
+ const unavailable = (label) => currentTheme.fg("textMuted", ` ${label} (Unsupported) `);
156133
156636
  const availability = thinkingAvailability(choice.model);
156134
- if (availability === "always-on") return ` ${segment("Always on", true)}`;
156135
- if (availability === "unsupported") return ` ${segment("Off", true)} ${currentTheme.fg("textMuted", "unsupported")}`;
156637
+ if (availability === "always-on") return ` ${segment("On", true)} ${unavailable("Off")}`;
156638
+ if (availability === "unsupported") return ` ${unavailable("On")} ${segment("Off", true)}`;
156136
156639
  const draft = this.draftFor(choice);
156137
156640
  return ` ${segment("On", draft)} ${segment("Off", !draft)}`;
156138
156641
  }
@@ -156651,7 +157154,7 @@ var EditorSelectorComponent = class extends ChoicePickerComponent {
156651
157154
  };
156652
157155
  //#endregion
156653
157156
  //#region src/tui/components/dialogs/experiments-selector.ts
156654
- const ELLIPSIS$7 = "…";
157157
+ const ELLIPSIS$8 = "…";
156655
157158
  var ExperimentsSelectorComponent = class extends Container {
156656
157159
  focused = false;
156657
157160
  opts;
@@ -156710,7 +157213,7 @@ var ExperimentsSelectorComponent = class extends Container {
156710
157213
  else if (view.page.end < view.items.length) lines.push(currentTheme.fg("textMuted", ` ▼ ${String(view.items.length - view.page.end)} more`));
156711
157214
  lines.push(this.renderApplyButton());
156712
157215
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
156713
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$7));
157216
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$8));
156714
157217
  }
156715
157218
  toggleDraft(feature) {
156716
157219
  if (isLocked(feature)) return;
@@ -156783,7 +157286,7 @@ function wrapText(text, width) {
156783
157286
  continue;
156784
157287
  }
156785
157288
  if (current.length > 0) lines.push(current);
156786
- current = visibleWidth(word) <= maxWidth ? word : truncateToWidth(word, maxWidth, ELLIPSIS$7);
157289
+ current = visibleWidth(word) <= maxWidth ? word : truncateToWidth(word, maxWidth, ELLIPSIS$8);
156787
157290
  }
156788
157291
  if (current.length > 0) lines.push(current);
156789
157292
  return lines;
@@ -158154,7 +158657,7 @@ var GoalStartPermissionPromptComponent = class extends StartPermissionPromptComp
158154
158657
  //#region src/tui/components/dialogs/goal-queue-manager.ts
158155
158658
  const MAX_GOAL_OBJECTIVE_LENGTH$2 = 4e3;
158156
158659
  const MAX_EDIT_INPUT_LINES = 8;
158157
- const ELLIPSIS$6 = "…";
158660
+ const ELLIPSIS$7 = "…";
158158
158661
  const BRACKET_PASTE_START$1 = "\x1B[200~";
158159
158662
  const BRACKET_PASTE_END$1 = "\x1B[201~";
158160
158663
  const SHIFT_ENTER_LEGACY = "\x1B\r";
@@ -158244,7 +158747,7 @@ var GoalQueueManagerComponent = class extends Container {
158244
158747
  }
158245
158748
  lines.push("");
158246
158749
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
158247
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$6));
158750
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$7));
158248
158751
  }
158249
158752
  renderGoal(goal, index, selected, width) {
158250
158753
  const moving = goal.id === this.movingGoalId;
@@ -158255,7 +158758,7 @@ var GoalQueueManagerComponent = class extends Container {
158255
158758
  const labelWidth = visibleWidth(labelPrefix);
158256
158759
  const stateWidth = visibleWidth(stateLabel);
158257
158760
  const objectiveWidth = Math.max(1, width - 5 - labelWidth - stateWidth);
158258
- const objective = truncateToWidth(formatListObjective(goal.objective), objectiveWidth, ELLIPSIS$6);
158761
+ const objective = truncateToWidth(formatListObjective(goal.objective), objectiveWidth, ELLIPSIS$7);
158259
158762
  let line = prefix + (selected ? (text) => currentTheme.boldFg("primary", text) : (text) => currentTheme.fg("text", text))(labelPrefix + objective);
158260
158763
  if (moving) line += currentTheme.fg("success", stateLabel);
158261
158764
  return line;
@@ -158326,10 +158829,10 @@ var GoalQueueEditDialogComponent = class extends Container {
158326
158829
  const innerWidth = Math.max(10, safeWidth - 4);
158327
158830
  const pad = " ";
158328
158831
  const border = (s) => currentTheme.fg("primary", s);
158329
- const title = truncateToWidth(currentTheme.boldFg("textStrong", "Edit upcoming goal"), innerWidth, ELLIPSIS$6);
158330
- const subtitle = truncateToWidth(currentTheme.fg(this.error === void 0 ? "textDim" : "warning", this.error ?? "Update the queued objective."), innerWidth, ELLIPSIS$6);
158832
+ const title = truncateToWidth(currentTheme.boldFg("textStrong", "Edit upcoming goal"), innerWidth, ELLIPSIS$7);
158833
+ const subtitle = truncateToWidth(currentTheme.fg(this.error === void 0 ? "textDim" : "warning", this.error ?? "Update the queued objective."), innerWidth, ELLIPSIS$7);
158331
158834
  const inputLines = this.input.render(innerWidth);
158332
- const footer = truncateToWidth(currentTheme.fg("textDim", "Enter submit · Shift-Enter/Ctrl-J newline · Esc cancel"), innerWidth, ELLIPSIS$6);
158835
+ const footer = truncateToWidth(currentTheme.fg("textDim", "Enter submit · Shift-Enter/Ctrl-J newline · Esc cancel"), innerWidth, ELLIPSIS$7);
158333
158836
  const contentLines = [
158334
158837
  title,
158335
158838
  "",
@@ -158435,14 +158938,14 @@ var MultilineGoalInput = class {
158435
158938
  const cursor = this.cursorLocation();
158436
158939
  const range = visibleLineRange(logicalLines.length, cursor.line);
158437
158940
  const rendered = [];
158438
- if (range.start > 0) rendered.push(padInputLine(` ${ELLIPSIS$6} ${String(range.start)} previous`, safeWidth));
158941
+ if (range.start > 0) rendered.push(padInputLine(` ${ELLIPSIS$7} ${String(range.start)} previous`, safeWidth));
158439
158942
  for (let lineIndex = range.start; lineIndex < range.end; lineIndex++) {
158440
158943
  const line = logicalLines[lineIndex] ?? "";
158441
158944
  const prefix = lineIndex === 0 ? "> " : " ";
158442
158945
  rendered.push(lineIndex === cursor.line ? renderCursorLine(line, cursor.column, prefix, safeWidth, this.focused) : renderTextLine(line, prefix, safeWidth));
158443
158946
  }
158444
158947
  const remaining = logicalLines.length - range.end;
158445
- if (remaining > 0) rendered.push(padInputLine(` ${ELLIPSIS$6} ${String(remaining)} more`, safeWidth));
158948
+ if (remaining > 0) rendered.push(padInputLine(` ${ELLIPSIS$7} ${String(remaining)} more`, safeWidth));
158446
158949
  return rendered;
158447
158950
  }
158448
158951
  insert(text) {
@@ -158582,7 +159085,7 @@ function visibleLineRange(totalLines, cursorLine) {
158582
159085
  }
158583
159086
  function renderTextLine(line, prefix, width) {
158584
159087
  const prefixWidth = visibleWidth(prefix);
158585
- return padInputLine(prefix + truncateToWidth(line, Math.max(1, width - prefixWidth), ELLIPSIS$6), width);
159088
+ return padInputLine(prefix + truncateToWidth(line, Math.max(1, width - prefixWidth), ELLIPSIS$7), width);
158586
159089
  }
158587
159090
  function renderCursorLine(line, column, prefix, width, focused) {
158588
159091
  const prefixWidth = visibleWidth(prefix);
@@ -159737,10 +160240,14 @@ function buildProviderManagerOptions(host) {
159737
160240
  providers: host.state.appState.availableProviders,
159738
160241
  activeProviderId,
159739
160242
  onAdd: () => {
159740
- handleProviderAdd(host);
160243
+ handleProviderAdd(host).catch((error) => {
160244
+ host.showError(`Add provider failed: ${formatErrorMessage$2(error)}`);
160245
+ });
159741
160246
  },
159742
160247
  onDeleteSource: (providerIds) => {
159743
- handleProviderManagerDeleteSource(host, providerIds);
160248
+ handleProviderManagerDeleteSource(host, providerIds).catch((error) => {
160249
+ host.showError(`Remove provider failed: ${formatErrorMessage$2(error)}`);
160250
+ });
159744
160251
  },
159745
160252
  onClose: () => {
159746
160253
  host.restoreEditor();
@@ -159890,7 +160397,9 @@ async function handleCatalogProviderAdd(host) {
159890
160397
  initialTabId: providerId,
159891
160398
  onSelect: ({ alias, thinking }) => {
159892
160399
  host.restoreEditor();
159893
- setDefaultModel(host, alias, thinking);
160400
+ setDefaultModel(host, alias, thinking).catch((error) => {
160401
+ host.showError(`Set default model failed: ${formatErrorMessage$2(error)}`);
160402
+ });
159894
160403
  },
159895
160404
  onCancel: () => {
159896
160405
  host.restoreEditor();
@@ -159918,8 +160427,8 @@ async function handleCustomRegistryAddViaDialog(host) {
159918
160427
  let entries;
159919
160428
  try {
159920
160429
  entries = await fetchCustomRegistry(source);
159921
- } catch (err) {
159922
- host.showError(`Failed to import registry: ${formatErrorMessage$2(err)}`);
160430
+ } catch (error) {
160431
+ host.showError(`Failed to import registry: ${formatErrorMessage$2(error)}`);
159923
160432
  return false;
159924
160433
  }
159925
160434
  const addedProviderIds = Object.values(entries).map((entry) => entry.id);
@@ -159931,8 +160440,8 @@ async function handleCustomRegistryAddViaDialog(host) {
159931
160440
  models: config.models
159932
160441
  });
159933
160442
  await host.authFlow.refreshConfigAfterLogin();
159934
- } catch (err) {
159935
- host.showError(`Failed to apply registry: ${formatErrorMessage$2(err)}`);
160443
+ } catch (error) {
160444
+ host.showError(`Failed to apply registry: ${formatErrorMessage$2(error)}`);
159936
160445
  return false;
159937
160446
  }
159938
160447
  const count = addedProviderIds.length;
@@ -159952,7 +160461,9 @@ async function handleCustomRegistryAddViaDialog(host) {
159952
160461
  initialTabId: firstNewProvider,
159953
160462
  onSelect: ({ alias, thinking }) => {
159954
160463
  host.restoreEditor();
159955
- setDefaultModel(host, alias, thinking);
160464
+ setDefaultModel(host, alias, thinking).catch((error) => {
160465
+ host.showError(`Set default model failed: ${formatErrorMessage$2(error)}`);
160466
+ });
159956
160467
  },
159957
160468
  onCancel: () => {
159958
160469
  host.restoreEditor();
@@ -160150,7 +160661,7 @@ const OVERVIEW_PLUGIN_PREFIX = "plugin:";
160150
160661
  const MCP_SERVER_PREFIX = "mcp:";
160151
160662
  const REMOVE_CONFIRM_CANCEL = "cancel";
160152
160663
  const REMOVE_CONFIRM_REMOVE = "remove";
160153
- const ELLIPSIS$5 = "…";
160664
+ const ELLIPSIS$6 = "…";
160154
160665
  var PluginsOverviewSelectorComponent = class extends Container {
160155
160666
  focused = false;
160156
160667
  opts;
@@ -160243,7 +160754,7 @@ var PluginsOverviewSelectorComponent = class extends Container {
160243
160754
  for (let i = 0; i < actionItems.length; i++) lines.push(...this.renderItem(actionItems[i], pluginItems.length + i, width));
160244
160755
  lines.push("");
160245
160756
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
160246
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$5));
160757
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$6));
160247
160758
  }
160248
160759
  renderItem(item, index, width) {
160249
160760
  const selected = index === this.selectedIndex;
@@ -160315,7 +160826,7 @@ var PluginMarketplaceSelectorComponent = class extends Container {
160315
160826
  for (let i = 0; i < actions.length; i++) lines.push(...this.renderItem(actions[i], entries.length + i, width));
160316
160827
  lines.push("");
160317
160828
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
160318
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$5));
160829
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$6));
160319
160830
  }
160320
160831
  renderItem(item, index, width) {
160321
160832
  const selected = index === this.selectedIndex;
@@ -160394,7 +160905,7 @@ var PluginMcpSelectorComponent = class extends Container {
160394
160905
  for (let i = 0; i < actionItems.length; i++) lines.push(...this.renderItem(actionItems[i], serverItems.length + i, width));
160395
160906
  lines.push("");
160396
160907
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
160397
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$5));
160908
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$6));
160398
160909
  }
160399
160910
  renderItem(item, index, width) {
160400
160911
  const selected = index === this.selectedIndex;
@@ -160574,7 +161085,7 @@ function wrapOverviewDescription(text, width) {
160574
161085
  continue;
160575
161086
  }
160576
161087
  if (current.length > 0) lines.push(current);
160577
- current = visibleWidth(word) <= maxWidth ? word : truncateToWidth(word, maxWidth, ELLIPSIS$5);
161088
+ current = visibleWidth(word) <= maxWidth ? word : truncateToWidth(word, maxWidth, ELLIPSIS$6);
160578
161089
  }
160579
161090
  if (current.length > 0) lines.push(current);
160580
161091
  return lines;
@@ -161065,15 +161576,23 @@ function applyRuntimeConfig(host, config) {
161065
161576
  }
161066
161577
  //#endregion
161067
161578
  //#region src/tui/components/dialogs/swarm-start-permission-prompt.ts
161068
- const OPTIONS = [{
161069
- value: "auto",
161070
- label: "Switch to Auto and start",
161071
- description: "Best for swarm tasks. Tools are approved automatically, and questions are skipped."
161072
- }, {
161073
- value: "manual",
161074
- label: "Start in Manual",
161075
- description: "Keep approvals on. Kimi Code may stop and wait for you during the swarm task."
161076
- }];
161579
+ const OPTIONS = [
161580
+ {
161581
+ value: "auto",
161582
+ label: "Switch to Auto and start",
161583
+ description: "Best for swarm tasks. Tools are approved automatically, and questions are skipped."
161584
+ },
161585
+ {
161586
+ value: "yolo",
161587
+ label: "Switch to YOLO and start",
161588
+ description: "Tools and plan changes are approved automatically. Kimi Code may still ask you questions."
161589
+ },
161590
+ {
161591
+ value: "manual",
161592
+ label: "Start in Manual",
161593
+ description: "Keep approvals on. Kimi Code may stop and wait for you during the swarm task."
161594
+ }
161595
+ ];
161077
161596
  const NOTICE_LINES = [
161078
161597
  "Manual mode asks you before Kimi Code runs commands, edits files, or takes other risky actions.",
161079
161598
  "Manual mode can block swarm work while agents are running.",
@@ -161151,7 +161670,7 @@ function showSwarmStartPermissionPrompt(host, commandText, cancelStatus, onSelec
161151
161670
  }));
161152
161671
  }
161153
161672
  async function startSwarmWithPermission(host, prompt, choice) {
161154
- if (choice === "auto") {
161673
+ if (choice === "auto" || choice === "yolo") {
161155
161674
  if (!await setPermissionForSwarm(host, choice)) return;
161156
161675
  }
161157
161676
  await startSwarmTask(host, prompt);
@@ -161182,7 +161701,7 @@ async function applySwarmMode(host, enabled, commandText) {
161182
161701
  }
161183
161702
  if (enabled && host.state.appState.permissionMode === "manual") {
161184
161703
  showSwarmStartPermissionPrompt(host, commandText, "Swarm mode not enabled.", async (choice) => {
161185
- if (choice === "auto" && !await setPermissionForSwarm(host, choice)) return;
161704
+ if ((choice === "auto" || choice === "yolo") && !await setPermissionForSwarm(host, choice)) return;
161186
161705
  if (!await setSwarmMode(host, true, "manual")) return;
161187
161706
  renderSwarmModeMarker(host, "active");
161188
161707
  });
@@ -164236,11 +164755,13 @@ var TruncatedOutputComponent = class {
164236
164755
  maxLines;
164237
164756
  indent;
164238
164757
  expandHint;
164758
+ tail;
164239
164759
  constructor(output, options) {
164240
164760
  this.expanded = options.expanded;
164241
164761
  this.maxLines = options.maxLines ?? PREVIEW_LINES;
164242
164762
  this.indent = options.indent ?? DEFAULT_INDENT;
164243
164763
  this.expandHint = options.expandHint ?? true;
164764
+ this.tail = options.tail ?? false;
164244
164765
  const cleaned = trimTrailingEmptyLines(output.split("\n")).join("\n");
164245
164766
  this.textComponent = new Text(options.isError ? currentTheme.fg("error", cleaned) : currentTheme.dim(cleaned), this.indent, 0);
164246
164767
  }
@@ -164250,8 +164771,12 @@ var TruncatedOutputComponent = class {
164250
164771
  render(width) {
164251
164772
  const contentLines = this.textComponent.render(width);
164252
164773
  if (this.expanded || contentLines.length <= this.maxLines) return contentLines;
164253
- const shown = contentLines.slice(0, this.maxLines);
164254
164774
  const remaining = contentLines.length - this.maxLines;
164775
+ if (this.tail) {
164776
+ const shown = contentLines.slice(contentLines.length - this.maxLines);
164777
+ return [" ".repeat(this.indent) + currentTheme.dim(`... (${String(remaining)} earlier lines)`), ...shown];
164778
+ }
164779
+ const shown = contentLines.slice(0, this.maxLines);
164255
164780
  const hint = this.expandHint ? `... (${String(remaining)} more lines, ctrl+o to expand)` : `... (${String(remaining)} more lines)`;
164256
164781
  return [...shown, " ".repeat(this.indent) + currentTheme.dim(hint)];
164257
164782
  }
@@ -164269,7 +164794,7 @@ var ShellExecutionComponent = class extends Container {
164269
164794
  constructor(options) {
164270
164795
  super();
164271
164796
  if (options.showCommand === true) this.addCommandPreview(options.command ?? "", options.commandPreviewLines);
164272
- if (options.result !== void 0) this.addResultPreview(options.result, options.expanded ?? false, options.resultPreviewLines ?? PREVIEW_LINES);
164797
+ if (options.result !== void 0) this.addResultPreview(options.result, options.expanded ?? false, options.resultPreviewLines ?? PREVIEW_LINES, options.tailOutput ?? false, options.expandHint ?? true);
164273
164798
  }
164274
164799
  addCommandPreview(command, previewLines) {
164275
164800
  if (command.length === 0) return;
@@ -164280,12 +164805,14 @@ var ShellExecutionComponent = class extends Container {
164280
164805
  this.addChild(new Text(currentTheme.dim(prefix + line), 2, 0));
164281
164806
  }
164282
164807
  }
164283
- addResultPreview(result, expanded, previewLines) {
164808
+ addResultPreview(result, expanded, previewLines, tailOutput, expandHint) {
164284
164809
  if (!result.output) return;
164285
164810
  this.addChild(new TruncatedOutputComponent(result.output, {
164286
164811
  expanded,
164287
164812
  isError: result.is_error ?? false,
164288
- maxLines: previewLines
164813
+ maxLines: previewLines,
164814
+ tail: tailOutput,
164815
+ expandHint
164289
164816
  }));
164290
164817
  }
164291
164818
  };
@@ -164735,6 +165262,7 @@ const STREAMING_PROGRESS_INTERVAL_MS = 1e3;
164735
165262
  const SUBAGENT_ELAPSED_INTERVAL_MS = 1e3;
164736
165263
  const PROGRESS_URL_RE = /https?:\/\/\S+/g;
164737
165264
  const ABORTED_MARK = "⊘";
165265
+ const MAX_LIVE_OUTPUT_CHARS = 5e4;
164738
165266
  function backgroundFailureMessage(status) {
164739
165267
  switch (status) {
164740
165268
  case "lost": return "Background agent lost (session restarted before completion)";
@@ -165050,6 +165578,7 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165050
165578
  subagentEndedAtMs;
165051
165579
  progressLines = [];
165052
165580
  static MAX_PROGRESS_LINES = 24;
165581
+ liveOutput = "";
165053
165582
  /**
165054
165583
  * Registered by a group container (`AgentGroupComponent` or
165055
165584
  * `ReadGroupComponent`) when this component is borrowed as a hidden state
@@ -165072,6 +165601,7 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165072
165601
  this.buildCallPreview();
165073
165602
  this.callPreviewEndIndex = this.children.length;
165074
165603
  this.buildProgressBlock();
165604
+ this.buildLiveOutputBlock();
165075
165605
  this.buildContent();
165076
165606
  this.buildSubagentBlock();
165077
165607
  this.syncStreamingProgressTimer();
@@ -165090,6 +165620,7 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165090
165620
  setResult(result) {
165091
165621
  this.result = result;
165092
165622
  this.progressLines = [];
165623
+ this.liveOutput = "";
165093
165624
  this.finalizeSubagentElapsedIfNeeded();
165094
165625
  this.syncStreamingProgressTimer();
165095
165626
  this.syncSubagentElapsedTimer();
@@ -165120,6 +165651,14 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165120
165651
  this.notifySnapshotChange();
165121
165652
  this.ui?.requestRender();
165122
165653
  }
165654
+ appendLiveOutput(text) {
165655
+ if (this.result !== void 0 || text.length === 0) return;
165656
+ this.liveOutput += text;
165657
+ if (this.liveOutput.length > MAX_LIVE_OUTPUT_CHARS) this.liveOutput = `[...truncated]\n${this.liveOutput.slice(this.liveOutput.length - MAX_LIVE_OUTPUT_CHARS)}`;
165658
+ this.rebuildContent();
165659
+ this.notifySnapshotChange();
165660
+ this.ui?.requestRender();
165661
+ }
165123
165662
  dispose() {
165124
165663
  this.stopStreamingProgressTimer();
165125
165664
  this.stopSubagentElapsedTimer();
@@ -165490,6 +166029,20 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165490
166029
  this.notifySnapshotChange();
165491
166030
  this.ui?.requestRender();
165492
166031
  }
166032
+ appendSubToolLiveOutput(id, text) {
166033
+ if (text.length === 0) return;
166034
+ const activity = this.subToolActivities.get(id);
166035
+ const ongoing = this.ongoingSubCalls.get(id);
166036
+ if (activity === void 0 && ongoing === void 0) return;
166037
+ const name = activity?.name ?? ongoing?.name ?? "Tool";
166038
+ const args = activity?.args ?? ongoing?.args ?? {};
166039
+ let output = (activity?.output ?? "") + text;
166040
+ if (output.length > MAX_LIVE_OUTPUT_CHARS) output = `[...truncated]\n${output.slice(output.length - MAX_LIVE_OUTPUT_CHARS)}`;
166041
+ this.upsertSubToolActivity(id, name, args, activity?.phase ?? "ongoing", output);
166042
+ this.rebuildContent();
166043
+ this.notifySnapshotChange();
166044
+ this.ui?.requestRender();
166045
+ }
165493
166046
  finishSubToolCall(result) {
165494
166047
  const ongoing = this.ongoingSubCalls.get(result.tool_call_id);
165495
166048
  if (ongoing === void 0) return;
@@ -165564,6 +166117,7 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165564
166117
  rebuildContent() {
165565
166118
  while (this.children.length > this.callPreviewEndIndex) this.children.pop();
165566
166119
  this.buildProgressBlock();
166120
+ this.buildLiveOutputBlock();
165567
166121
  this.buildContent();
165568
166122
  this.buildSubagentBlock();
165569
166123
  }
@@ -165572,6 +166126,7 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165572
166126
  this.buildCallPreview();
165573
166127
  this.callPreviewEndIndex = this.children.length;
165574
166128
  this.buildProgressBlock();
166129
+ this.buildLiveOutputBlock();
165575
166130
  this.buildContent();
165576
166131
  this.buildSubagentBlock();
165577
166132
  }
@@ -165601,6 +166156,21 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165601
166156
  this.addChild(new Text(styled, 2, 0));
165602
166157
  }
165603
166158
  }
166159
+ buildLiveOutputBlock() {
166160
+ if (this.result !== void 0) return;
166161
+ if (this.liveOutput.length === 0) return;
166162
+ this.addChild(new ShellExecutionComponent({
166163
+ result: {
166164
+ tool_call_id: this.toolCall.id,
166165
+ output: this.liveOutput,
166166
+ is_error: false
166167
+ },
166168
+ expanded: this.expanded,
166169
+ resultPreviewLines: 3,
166170
+ tailOutput: true,
166171
+ expandHint: false
166172
+ }));
166173
+ }
165604
166174
  buildSubagentBlock() {
165605
166175
  if (this.subagentAgentId === void 0 && this.ongoingSubCalls.size === 0 && this.finishedSubCalls.length === 0 && this.subagentText.length === 0 && this.subagentPhase === void 0 && this.backgroundTaskTerminalPhase === void 0) return;
165606
166176
  if (this.isSingleSubagentView()) {
@@ -165750,7 +166320,6 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165750
166320
  if (outputLine !== void 0) this.addChild(new PrefixedWrappedLine(` ${currentTheme.fg("text", "└")} `, " ", currentTheme.fg("text", outputLine)));
165751
166321
  }
165752
166322
  addSubToolOutputPreview(activity) {
165753
- if (activity.phase === "ongoing") return;
165754
166323
  const output = activity.output;
165755
166324
  if (output === void 0 || output.trim().length === 0) return;
165756
166325
  if (activity.name !== "Bash" && !isGenericToolResult(activity.name)) return;
@@ -165759,7 +166328,8 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165759
166328
  expandHint: false,
165760
166329
  isError: activity.phase === "failed",
165761
166330
  maxLines: 3,
165762
- indent: SUBAGENT_SUBTOOL_OUTPUT_INDENT
166331
+ indent: SUBAGENT_SUBTOOL_OUTPUT_INDENT,
166332
+ tail: activity.phase === "ongoing"
165763
166333
  }));
165764
166334
  }
165765
166335
  getRecentSubToolActivities() {
@@ -166665,6 +167235,7 @@ async function runPrompt(opts, version, io = {}) {
166665
167235
  try {
166666
167236
  await harness.ensureConfigFile();
166667
167237
  const config = await harness.getConfig();
167238
+ for (const warning of (await harness.getConfigDiagnostics()).warnings) stderr.write(`Warning: ${warning}\n`);
166668
167239
  const { session, resumed, restorePermission, telemetryModel, goalModel } = await resolvePromptSession(harness, opts, workDir, config.defaultModel, stderr, (restorePermission) => {
166669
167240
  restorePromptSessionPermission = restorePermission;
166670
167241
  });
@@ -166809,15 +167380,20 @@ function installPromptTerminationCleanup(promptProcess, cleanup) {
166809
167380
  };
166810
167381
  const onSigint = () => exitAfterCleanup("SIGINT");
166811
167382
  const onSigterm = () => exitAfterCleanup("SIGTERM");
167383
+ const onSighup = () => exitAfterCleanup("SIGHUP");
166812
167384
  promptProcess.once("SIGINT", onSigint);
166813
167385
  promptProcess.once("SIGTERM", onSigterm);
167386
+ promptProcess.once("SIGHUP", onSighup);
166814
167387
  return () => {
166815
167388
  promptProcess.off("SIGINT", onSigint);
166816
167389
  promptProcess.off("SIGTERM", onSigterm);
167390
+ promptProcess.off("SIGHUP", onSighup);
166817
167391
  };
166818
167392
  }
166819
167393
  function signalExitCode(signal) {
166820
- return signal === "SIGINT" ? 130 : 143;
167394
+ if (signal === "SIGINT") return 130;
167395
+ if (signal === "SIGHUP") return 129;
167396
+ return 143;
166821
167397
  }
166822
167398
  function runPromptTurn(session, prompt, outputFormat, stdout, stderr) {
166823
167399
  let activeTurnId;
@@ -167373,6 +167949,144 @@ function findBinaryRecursively(rootDir, binaryName) {
167373
167949
  return null;
167374
167950
  }
167375
167951
  //#endregion
167952
+ //#region src/tui/banner/banner-provider.ts
167953
+ function normalizeTag(value) {
167954
+ if (typeof value !== "string") return null;
167955
+ const trimmed = value.trim();
167956
+ return trimmed.length > 0 ? trimmed : null;
167957
+ }
167958
+ function normalizeText(value) {
167959
+ if (typeof value !== "string") return null;
167960
+ const trimmed = value.trim();
167961
+ return trimmed.length > 0 ? trimmed : null;
167962
+ }
167963
+ function normalizeUtcDate(value) {
167964
+ if (value.endsWith("Z")) return value;
167965
+ if (/[+-]\d{2}:\d{2}$/.test(value)) return value;
167966
+ return `${value}Z`;
167967
+ }
167968
+ function parseDate(value) {
167969
+ if (typeof value !== "string" || value.length === 0) return null;
167970
+ const normalized = normalizeUtcDate(value);
167971
+ const date = new Date(normalized);
167972
+ return Number.isNaN(date.getTime()) ? null : date;
167973
+ }
167974
+ function isWithinWindow(start, end, now) {
167975
+ if (start !== null && now < start) return false;
167976
+ if (end !== null && now > end) return false;
167977
+ return true;
167978
+ }
167979
+ function meetsMinVersion(minVersion, clientVersion) {
167980
+ if (minVersion === void 0 || minVersion === null) return true;
167981
+ if (typeof minVersion !== "string" || minVersion.length === 0) return true;
167982
+ const min = valid(minVersion);
167983
+ const current = valid(clientVersion);
167984
+ if (min === null || current === null) return false;
167985
+ return gte(current, min);
167986
+ }
167987
+ function pickActiveBanner(json, clientVersion, now) {
167988
+ if (json.banner_enabled !== true) return null;
167989
+ if (!meetsMinVersion(json.banner_min_version, clientVersion)) return null;
167990
+ if (!isWithinWindow(parseDate(json.banner_start_time), parseDate(json.banner_end_time), now)) return null;
167991
+ const mainText = normalizeText(json.banner_maintext);
167992
+ if (mainText === null) return null;
167993
+ return {
167994
+ tag: normalizeTag(json.banner_title),
167995
+ mainText,
167996
+ subText: normalizeText(json.banner_subtext)
167997
+ };
167998
+ }
167999
+ function pickFallbackBanner(json, clientVersion, now, random) {
168000
+ if (json.banner_fallback_enabled !== true) return null;
168001
+ const list = Array.isArray(json.banner_fallback_list) ? json.banner_fallback_list : [];
168002
+ const candidates = [];
168003
+ for (const raw of list) {
168004
+ if (typeof raw !== "object" || raw === null) continue;
168005
+ const item = raw;
168006
+ if (item.enabled !== true) continue;
168007
+ if (!meetsMinVersion(item.banner_min_version, clientVersion)) continue;
168008
+ const mainText = normalizeText(item.banner_maintext);
168009
+ if (mainText === null) continue;
168010
+ candidates.push({
168011
+ tag: normalizeTag(item.banner_title),
168012
+ mainText,
168013
+ subText: normalizeText(item.banner_subtext)
168014
+ });
168015
+ }
168016
+ if (candidates.length === 0) return null;
168017
+ return candidates[Math.floor(random() * candidates.length)];
168018
+ }
168019
+ function selectBannerState(json, clientVersion, now, random) {
168020
+ const typed = typeof json === "object" && json !== null ? json : {};
168021
+ return pickActiveBanner(typed, clientVersion, now) ?? pickFallbackBanner(typed, clientVersion, now, random);
168022
+ }
168023
+ var BannerProvider = class {
168024
+ clientVersion;
168025
+ url;
168026
+ constructor(clientVersion, url = KIMI_CODE_TIPS_BANNER_URL) {
168027
+ this.clientVersion = clientVersion;
168028
+ this.url = url;
168029
+ }
168030
+ async load(fetchImpl = fetch) {
168031
+ try {
168032
+ const controller = new AbortController();
168033
+ const timeout = setTimeout(() => {
168034
+ controller.abort();
168035
+ }, 3e3);
168036
+ const response = await fetchImpl(this.url, { signal: controller.signal });
168037
+ clearTimeout(timeout);
168038
+ if (!response.ok) return null;
168039
+ return selectBannerState(await response.json(), this.clientVersion, /* @__PURE__ */ new Date(), Math.random);
168040
+ } catch {
168041
+ return null;
168042
+ }
168043
+ }
168044
+ };
168045
+ //#endregion
168046
+ //#region src/tui/components/chrome/banner.ts
168047
+ const PREFIX_STAR = "✦";
168048
+ const PADDING = " ";
168049
+ var BannerComponent = class {
168050
+ state;
168051
+ constructor(state) {
168052
+ this.state = state;
168053
+ }
168054
+ invalidate() {}
168055
+ render(width) {
168056
+ const main = (s) => currentTheme.boldFg("textStrong", s);
168057
+ const dim = (s) => currentTheme.fg("textDim", s);
168058
+ if (width < 1) return [""];
168059
+ const tagText = this.state.tag ?? "";
168060
+ const tagLabel = tagText.length > 0 ? `${PREFIX_STAR} ${tagText}` : "";
168061
+ const tagStyled = tagLabel.length > 0 ? currentTheme.boldFg("primary", tagLabel) : "";
168062
+ const tagDisplay = tagStyled.length > 0 ? tagStyled + PADDING : "";
168063
+ const tagWidth = visibleWidth(tagDisplay);
168064
+ const showTag = tagWidth > 0 && tagWidth < width;
168065
+ const bodyIndent = showTag ? " ".repeat(tagWidth) : "";
168066
+ const descIndent = showTag ? " ".repeat(visibleWidth(PREFIX_STAR + PADDING)) : "";
168067
+ const bodyContentWidth = width - (showTag ? tagWidth : 0);
168068
+ const descContentWidth = width - (showTag ? visibleWidth(PREFIX_STAR + PADDING) : 0);
168069
+ if (bodyContentWidth <= 0) return [""];
168070
+ const mainSegments = this.state.mainText.split("\n");
168071
+ const subSegments = this.state.subText ? this.state.subText.split("\n") : [];
168072
+ const result = [];
168073
+ for (let i = 0; i < mainSegments.length; i++) {
168074
+ const wrapped = wrapTextWithAnsi(mainSegments[i], bodyContentWidth);
168075
+ for (let j = 0; j < wrapped.length; j++) {
168076
+ const boldLine = main(wrapped[j]);
168077
+ if (i === 0 && j === 0 && showTag) result.push(tagDisplay + boldLine);
168078
+ else result.push(bodyIndent + boldLine);
168079
+ }
168080
+ }
168081
+ for (const sub of subSegments) {
168082
+ const wrapped = wrapTextWithAnsi(sub, descContentWidth <= 0 ? bodyContentWidth : descContentWidth);
168083
+ for (const line of wrapped) result.push(descIndent + dim(line));
168084
+ }
168085
+ result.push("");
168086
+ return result;
168087
+ }
168088
+ };
168089
+ //#endregion
167376
168090
  //#region src/tui/components/chrome/device-code-box.ts
167377
168091
  var DeviceCodeBoxComponent = class {
167378
168092
  params;
@@ -167783,16 +168497,16 @@ function buildNumericHint(count) {
167783
168497
  * emulator's "snap to bottom on stdout" reflex, causing flicker and an
167784
168498
  * unscrollable history pane.
167785
168499
  */
167786
- const ELLIPSIS$4 = "…";
168500
+ const ELLIPSIS$5 = "…";
167787
168501
  function padToWidth$2(line, width) {
167788
168502
  const w = visibleWidth(line);
167789
168503
  if (w === width) return line;
167790
- if (w > width) return truncateToWidth(line, width, ELLIPSIS$4);
168504
+ if (w > width) return truncateToWidth(line, width, ELLIPSIS$5);
167791
168505
  return line + " ".repeat(width - w);
167792
168506
  }
167793
168507
  function fitExactly$2(line, width) {
167794
168508
  let s = line;
167795
- if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$4);
168509
+ if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$5);
167796
168510
  return padToWidth$2(s, width);
167797
168511
  }
167798
168512
  var ApprovalPreviewViewer = class extends Container {
@@ -168649,7 +169363,7 @@ var QuestionDialogComponent = class extends Container {
168649
169363
  /**
168650
169364
  * SessionPicker — pi-tui version of the session selection dialog.
168651
169365
  */
168652
- const ELLIPSIS$3 = "…";
169366
+ const ELLIPSIS$4 = "…";
168653
169367
  function formatRelativeTime$1(ts) {
168654
169368
  if (!Number.isFinite(ts) || ts <= 0) return "";
168655
169369
  const diffSec = Math.floor(Math.max(0, Date.now() - ts) / 1e3);
@@ -168669,7 +169383,7 @@ function homeAlias(path) {
168669
169383
  function truncatePathLeft(path, maxWidth) {
168670
169384
  if (maxWidth <= 0) return "";
168671
169385
  if (visibleWidth(path) <= maxWidth) return path;
168672
- if (maxWidth === 1) return ELLIPSIS$3;
169386
+ if (maxWidth === 1) return ELLIPSIS$4;
168673
169387
  const segments = [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(path)].map((s) => s.segment);
168674
169388
  let used = 0;
168675
169389
  const budget = maxWidth - 1;
@@ -168682,7 +169396,7 @@ function truncatePathLeft(path, maxWidth) {
168682
169396
  used += w;
168683
169397
  i--;
168684
169398
  }
168685
- return ELLIPSIS$3 + segments.slice(i + 1).join("");
169399
+ return ELLIPSIS$4 + segments.slice(i + 1).join("");
168686
169400
  }
168687
169401
  function singleLine$1(text) {
168688
169402
  return text.replaceAll(/\s+/g, " ").trim();
@@ -168734,26 +169448,26 @@ var SessionPickerComponent = class extends Container {
168734
169448
  if (matchesKey(data, Key.down)) this.selectedIndex = Math.min(this.sessions.length - 1, this.selectedIndex + 1);
168735
169449
  }
168736
169450
  render(width) {
168737
- return this.renderLines(width).map((line) => truncateToWidth(line, width, ELLIPSIS$3));
169451
+ return this.renderLines(width).map((line) => truncateToWidth(line, width, ELLIPSIS$4));
168738
169452
  }
168739
169453
  renderLines(width) {
168740
169454
  const lines = [currentTheme.fg("primary", "─".repeat(width))];
168741
169455
  if (this.loading) {
168742
- lines.push(currentTheme.boldFg("primary", truncateToWidth("Sessions", width, ELLIPSIS$3)));
168743
- lines.push(currentTheme.fg("textMuted", truncateToWidth("Loading sessions...", width, ELLIPSIS$3)));
169456
+ lines.push(currentTheme.boldFg("primary", truncateToWidth("Sessions", width, ELLIPSIS$4)));
169457
+ lines.push(currentTheme.fg("textMuted", truncateToWidth("Loading sessions...", width, ELLIPSIS$4)));
168744
169458
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
168745
169459
  return lines;
168746
169460
  }
168747
169461
  if (this.sessions.length === 0) {
168748
- lines.push(currentTheme.boldFg("primary", truncateToWidth("Sessions", width, ELLIPSIS$3)));
168749
- lines.push(currentTheme.fg("textMuted", truncateToWidth("No sessions found. Press Escape to close.", width, ELLIPSIS$3)));
169462
+ lines.push(currentTheme.boldFg("primary", truncateToWidth("Sessions", width, ELLIPSIS$4)));
169463
+ lines.push(currentTheme.fg("textMuted", truncateToWidth("No sessions found. Press Escape to close.", width, ELLIPSIS$4)));
168750
169464
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
168751
169465
  return lines;
168752
169466
  }
168753
169467
  const headerLabel = "Sessions ";
168754
169468
  const headerHint = "↑↓ navigate · Enter select · Esc cancel";
168755
169469
  const labelWidth = visibleWidth(headerLabel);
168756
- const shownHint = truncateToWidth(headerHint, Math.max(0, width - labelWidth), ELLIPSIS$3);
169470
+ const shownHint = truncateToWidth(headerHint, Math.max(0, width - labelWidth), ELLIPSIS$4);
168757
169471
  lines.push(currentTheme.boldFg("primary", headerLabel) + currentTheme.fg("textMuted", shownHint));
168758
169472
  lines.push("");
168759
169473
  const visibleStart = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisibleSessions / 2), Math.max(0, this.sessions.length - this.maxVisibleSessions)));
@@ -168768,7 +169482,7 @@ var SessionPickerComponent = class extends Container {
168768
169482
  if (this.sessions.length > visibleSessions.length) {
168769
169483
  lines.push("");
168770
169484
  const footer = `Showing ${String(visibleStart + 1)}-${String(visibleStart + visibleSessions.length)} of ${String(this.sessions.length)} sessions`;
168771
- lines.push(currentTheme.fg("textMuted", truncateToWidth(footer, width, ELLIPSIS$3)));
169485
+ lines.push(currentTheme.fg("textMuted", truncateToWidth(footer, width, ELLIPSIS$4)));
168772
169486
  }
168773
169487
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
168774
169488
  return lines;
@@ -168789,7 +169503,7 @@ var SessionPickerComponent = class extends Container {
168789
169503
  const trailingWidth = visibleWidth(trailingParts.length > 0 ? " " + trailingParts.join(" ") : "");
168790
169504
  const headerPrefixWidth = visibleWidth(pointer) + 1;
168791
169505
  const titleBudget = Math.max(8, width - headerPrefixWidth - trailingWidth);
168792
- const shownTitle = truncateToWidth(singleLine$1(titleSource), titleBudget, ELLIPSIS$3);
169506
+ const shownTitle = truncateToWidth(singleLine$1(titleSource), titleBudget, ELLIPSIS$4);
168793
169507
  let header = currentTheme.fg(isSelected ? "primary" : "textDim", pointer + " ");
168794
169508
  header += titleStyle(shownTitle);
168795
169509
  if (time.length > 0) header += " " + currentTheme.fg("textDim", time);
@@ -168804,7 +169518,7 @@ var SessionPickerComponent = class extends Container {
168804
169518
  const dirWidth = visibleWidth(aliasedDir);
168805
169519
  if (idLineWidth + metaGapWidth + dirWidth <= width) card.push(indent + currentTheme.fg("textMuted", fullId) + currentTheme.fg("textDim", metaGap) + currentTheme.fg("textMuted", aliasedDir));
168806
169520
  else {
168807
- card.push(indent + currentTheme.fg("textMuted", truncateToWidth(fullId, Math.max(idWidth, width - indentWidth), ELLIPSIS$3)));
169521
+ card.push(indent + currentTheme.fg("textMuted", truncateToWidth(fullId, Math.max(idWidth, width - indentWidth), ELLIPSIS$4)));
168808
169522
  const dir = truncatePathLeft(aliasedDir, Math.max(8, width - indentWidth));
168809
169523
  card.push(indent + currentTheme.fg("textMuted", dir));
168810
169524
  }
@@ -168813,7 +169527,7 @@ var SessionPickerComponent = class extends Container {
168813
169527
  const promptMarker = "› ";
168814
169528
  const promptMarkerWidth = visibleWidth(promptMarker);
168815
169529
  const promptBudget = Math.max(8, width - indentWidth - promptMarkerWidth);
168816
- const promptText = truncateToWidth(singleLine$1(rawPrompt), promptBudget, ELLIPSIS$3);
169530
+ const promptText = truncateToWidth(singleLine$1(rawPrompt), promptBudget, ELLIPSIS$4);
168817
169531
  const promptLine = indent + currentTheme.fg("textDim", promptMarker + promptText);
168818
169532
  card.push(promptLine);
168819
169533
  }
@@ -168821,6 +169535,447 @@ var SessionPickerComponent = class extends Container {
168821
169535
  }
168822
169536
  };
168823
169537
  //#endregion
169538
+ //#region src/tui/components/editor/file-mention-provider.ts
169539
+ const PATH_DELIMITERS = new Set([
169540
+ " ",
169541
+ " ",
169542
+ "\"",
169543
+ "'",
169544
+ "="
169545
+ ]);
169546
+ const MAX_FALLBACK_SCAN = 2e3;
169547
+ const MAX_FALLBACK_SUGGESTIONS = 50;
169548
+ /**
169549
+ * Kimi wrapper around pi-tui's combined autocomplete provider.
169550
+ *
169551
+ * File / folder mention behavior uses pi-tui's fd-backed provider when fd is
169552
+ * available. While managed fd is downloading (or when it is unavailable), a
169553
+ * small filesystem fallback keeps basic `@` file and folder completion usable.
169554
+ * Ordinary path completion is still handled by pi-tui's readdir-backed path
169555
+ * completer. This wrapper also keeps Kimi-specific slash-command guards.
169556
+ */
169557
+ var FileMentionProvider = class {
169558
+ slashCommands;
169559
+ workDir;
169560
+ fdPath;
169561
+ inner;
169562
+ constructor(slashCommands, workDir, fdPath) {
169563
+ this.slashCommands = slashCommands;
169564
+ this.workDir = workDir;
169565
+ this.fdPath = fdPath;
169566
+ const expanded = [];
169567
+ for (const cmd of slashCommands) {
169568
+ expanded.push(cmd);
169569
+ for (const alias of cmd.aliases ?? []) expanded.push({
169570
+ ...cmd,
169571
+ name: alias
169572
+ });
169573
+ }
169574
+ this.inner = new CombinedAutocompleteProvider(expanded, workDir, fdPath);
169575
+ }
169576
+ async getSuggestions(lines, cursorLine, cursorCol, options) {
169577
+ const currentLine = lines[cursorLine] ?? "";
169578
+ const textBeforeCursor = currentLine.slice(0, cursorCol);
169579
+ if (shouldSuppressLeadingWhitespaceSlashPath(textBeforeCursor, options.force)) return null;
169580
+ if (shouldSuppressSlashArgumentCompletion(textBeforeCursor, currentLine.slice(cursorCol), options.force)) return null;
169581
+ const atPrefix = extractAtPrefix(textBeforeCursor);
169582
+ if (atPrefix !== null) {
169583
+ if (this.fdPath === null) return getFsMentionSuggestions(this.workDir, atPrefix, options.signal);
169584
+ try {
169585
+ return await this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
169586
+ } catch {
169587
+ return getFsMentionSuggestions(this.workDir, atPrefix, options.signal);
169588
+ }
169589
+ }
169590
+ if (!options.force && textBeforeCursor.startsWith("/")) {
169591
+ if (textBeforeCursor.indexOf(" ") === -1) {
169592
+ const tokens = textBeforeCursor.slice(1).trim().split(/\s+/).filter((t) => t.length > 0);
169593
+ const matches = [];
169594
+ for (const cmd of this.slashCommands) {
169595
+ const nameScore = scoreTokens(tokens, cmd.name);
169596
+ if (nameScore !== null) {
169597
+ matches.push({
169598
+ cmd,
169599
+ score: nameScore,
169600
+ viaAlias: false,
169601
+ label: cmd.name
169602
+ });
169603
+ continue;
169604
+ }
169605
+ const aliases = cmd.aliases ?? [];
169606
+ let bestAliasScore = null;
169607
+ for (const alias of aliases) {
169608
+ const aliasScore = scoreTokens(tokens, alias);
169609
+ if (aliasScore !== null && (bestAliasScore === null || aliasScore < bestAliasScore)) bestAliasScore = aliasScore;
169610
+ }
169611
+ if (bestAliasScore !== null) matches.push({
169612
+ cmd,
169613
+ score: bestAliasScore,
169614
+ viaAlias: true,
169615
+ label: `${cmd.name} (${aliases.join(", ")})`
169616
+ });
169617
+ }
169618
+ matches.sort((a, b) => a.score - b.score || Number(a.viaAlias) - Number(b.viaAlias));
169619
+ if (matches.length === 0) return null;
169620
+ return {
169621
+ items: matches.map((m) => ({
169622
+ value: m.cmd.name,
169623
+ label: m.label,
169624
+ description: formatSlashCommandDescription(m.cmd)
169625
+ })),
169626
+ prefix: textBeforeCursor
169627
+ };
169628
+ }
169629
+ }
169630
+ try {
169631
+ return await this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
169632
+ } catch {
169633
+ return null;
169634
+ }
169635
+ }
169636
+ applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
169637
+ return this.inner.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
169638
+ }
169639
+ };
169640
+ function extractAtPrefix(text) {
169641
+ let tokenStart = 0;
169642
+ for (let i = text.length - 1; i >= 0; i -= 1) if (PATH_DELIMITERS.has(text[i] ?? "")) {
169643
+ tokenStart = i + 1;
169644
+ break;
169645
+ }
169646
+ if (text[tokenStart] !== "@") return null;
169647
+ return text.slice(tokenStart);
169648
+ }
169649
+ function getFsMentionSuggestions(workDir, atPrefix, signal) {
169650
+ if (signal.aborted) return null;
169651
+ const query = atPrefix.slice(1);
169652
+ const candidates = collectFsMentionCandidates(workDir, signal);
169653
+ if (candidates.length === 0 || signal.aborted) return null;
169654
+ const ranked = rankFsMentionCandidates(candidates, query).slice(0, MAX_FALLBACK_SUGGESTIONS);
169655
+ if (ranked.length === 0) return null;
169656
+ return {
169657
+ prefix: atPrefix,
169658
+ items: ranked.map(toMentionItem)
169659
+ };
169660
+ }
169661
+ function collectFsMentionCandidates(workDir, signal) {
169662
+ const result = [];
169663
+ const stack = [""];
169664
+ while (stack.length > 0 && result.length < MAX_FALLBACK_SCAN) {
169665
+ if (signal.aborted) break;
169666
+ const relativeDir = stack.pop() ?? "";
169667
+ const absoluteDir = relativeDir.length === 0 ? workDir : join$1(workDir, relativeDir);
169668
+ let entries;
169669
+ try {
169670
+ entries = readdirSync(absoluteDir, { withFileTypes: true });
169671
+ } catch {
169672
+ continue;
169673
+ }
169674
+ for (const entry of entries) {
169675
+ if (signal.aborted || result.length >= MAX_FALLBACK_SCAN) break;
169676
+ if (entry.name === ".git") continue;
169677
+ const relativePath = normalizePath(relativeDir.length === 0 ? entry.name : join$1(relativeDir, entry.name));
169678
+ const isSymlink = entry.isSymbolicLink();
169679
+ let isDirectory = entry.isDirectory();
169680
+ if (!isDirectory && isSymlink) try {
169681
+ isDirectory = statSync(join$1(workDir, relativePath)).isDirectory();
169682
+ } catch {}
169683
+ result.push({
169684
+ path: relativePath,
169685
+ isDirectory
169686
+ });
169687
+ if (isDirectory && !isSymlink) stack.push(relativePath);
169688
+ }
169689
+ }
169690
+ return result;
169691
+ }
169692
+ function rankFsMentionCandidates(candidates, query) {
169693
+ const lowerQuery = query.toLowerCase();
169694
+ const scored = [];
169695
+ for (const candidate of candidates) {
169696
+ const score = scoreCandidate(candidate, lowerQuery);
169697
+ if (score > 0) scored.push({
169698
+ candidate,
169699
+ score
169700
+ });
169701
+ }
169702
+ scored.sort((a, b) => {
169703
+ if (a.score !== b.score) return b.score - a.score;
169704
+ if (a.candidate.isDirectory !== b.candidate.isDirectory) return a.candidate.isDirectory ? -1 : 1;
169705
+ return a.candidate.path.localeCompare(b.candidate.path);
169706
+ });
169707
+ return scored.map((entry) => entry.candidate);
169708
+ }
169709
+ function scoreCandidate(candidate, lowerQuery) {
169710
+ if (lowerQuery.length === 0) {
169711
+ const depthPenalty = candidate.path.split("/").length - 1;
169712
+ return (candidate.isDirectory ? 120 : 100) - depthPenalty;
169713
+ }
169714
+ const lowerPath = candidate.path.toLowerCase();
169715
+ const lowerBase = basename$1(candidate.path).toLowerCase();
169716
+ let score = 0;
169717
+ if (lowerBase === lowerQuery) score = 100;
169718
+ else if (lowerBase.startsWith(lowerQuery)) score = 80;
169719
+ else if (lowerBase.includes(lowerQuery)) score = 50;
169720
+ else if (lowerPath.includes(lowerQuery)) score = 30;
169721
+ if (candidate.isDirectory && score > 0) score += 10;
169722
+ return score;
169723
+ }
169724
+ function toMentionItem(candidate) {
169725
+ const valuePath = candidate.isDirectory ? `${candidate.path}/` : candidate.path;
169726
+ return {
169727
+ value: valuePath.includes(" ") ? `@"${valuePath}"` : `@${valuePath}`,
169728
+ label: `${basename$1(candidate.path)}${candidate.isDirectory ? "/" : ""}`,
169729
+ description: valuePath
169730
+ };
169731
+ }
169732
+ function normalizePath(path) {
169733
+ return path.replaceAll("\\", "/");
169734
+ }
169735
+ function shouldSuppressLeadingWhitespaceSlashPath(textBeforeCursor, force) {
169736
+ if (force === true) return false;
169737
+ if (textBeforeCursor.startsWith("/")) return false;
169738
+ return textBeforeCursor.trimStart().startsWith("/");
169739
+ }
169740
+ function shouldSuppressSlashArgumentCompletion(textBeforeCursor, textAfterCursor, force) {
169741
+ if (force === true) return false;
169742
+ if (!textBeforeCursor.startsWith("/")) return false;
169743
+ if (!textBeforeCursor.includes(" ")) return false;
169744
+ return textAfterCursor.trimStart().length > 0;
169745
+ }
169746
+ /**
169747
+ * All tokens must fuzzy-match `text`; returns the summed score, or null when
169748
+ * any token misses. An empty token list matches everything with score 0.
169749
+ * Mirrors pi-tui fuzzyFilter's token semantics — keep in sync if it changes.
169750
+ */
169751
+ function scoreTokens(tokens, text) {
169752
+ let score = 0;
169753
+ for (const token of tokens) {
169754
+ const m = fuzzyMatch(token, text);
169755
+ if (!m.matches) return null;
169756
+ score += m.score;
169757
+ }
169758
+ return score;
169759
+ }
169760
+ /**
169761
+ * Mirrors CombinedAutocompleteProvider's description rendering so the
169762
+ * intercepted name completion keeps showing the argument hint.
169763
+ */
169764
+ function formatSlashCommandDescription(cmd) {
169765
+ const desc = cmd.description ?? "";
169766
+ return (cmd.argumentHint ? desc ? `${cmd.argumentHint} — ${desc}` : cmd.argumentHint : desc) || void 0;
169767
+ }
169768
+ //#endregion
169769
+ //#region src/tui/components/messages/goal-markers.ts
169770
+ /**
169771
+ * Low-profile transcript markers for the autonomous goal loop.
169772
+ *
169773
+ * Lifecycle changes (paused / resumed / cancelled) and `no_progress` verdicts
169774
+ * render as a single dim line — `◦ Goal paused` — that expands (ctrl+o, shared
169775
+ * with tool output) to show the reason when there is one. Terminal outcomes use
169776
+ * the richer completion card (the `/goal` box), not this marker.
169777
+ */
169778
+ const HEAD_INDENT = " ";
169779
+ const DETAIL_INDENT = " ";
169780
+ var GoalMarkerComponent = class {
169781
+ headline;
169782
+ detail;
169783
+ accentToken;
169784
+ expanded = false;
169785
+ marker;
169786
+ textToken;
169787
+ expandable;
169788
+ indent;
169789
+ leadingBlank;
169790
+ constructor(headline, detail, accentToken, options = {}) {
169791
+ this.headline = headline;
169792
+ this.detail = detail;
169793
+ this.accentToken = accentToken;
169794
+ this.marker = options.marker ?? "◦";
169795
+ this.textToken = options.textToken ?? "textDim";
169796
+ this.expandable = options.expandable ?? true;
169797
+ this.indent = options.indent ?? HEAD_INDENT;
169798
+ this.leadingBlank = options.leadingBlank ?? false;
169799
+ }
169800
+ invalidate() {}
169801
+ setExpanded(expanded) {
169802
+ this.expanded = expanded;
169803
+ }
169804
+ render(width) {
169805
+ const dot = currentTheme.fg(this.accentToken, this.marker);
169806
+ const head = currentTheme.fg(this.textToken, this.headline);
169807
+ if (!(this.detail !== void 0 && this.detail.length > 0)) return this.clampToWidth([`${this.indent}${dot} ${head}`], width);
169808
+ if (!this.expandable) return this.clampToWidth([`${this.indent}${dot} ${head}`], width);
169809
+ if (!this.expanded) return this.clampToWidth([`${this.indent}${dot} ${head} ${currentTheme.fg("textMuted", "(ctrl+o)")}`], width);
169810
+ const out = [`${this.indent}${dot} ${head}`];
169811
+ const wrapWidth = Math.max(20, width - 4);
169812
+ for (const line of wrap(this.detail, wrapWidth)) out.push(DETAIL_INDENT + currentTheme.fg("textDim", line));
169813
+ return this.clampToWidth(out, width);
169814
+ }
169815
+ clampToWidth(lines, width) {
169816
+ const withBlank = this.withLeadingBlank(lines);
169817
+ if (width <= 0) return withBlank.map(() => "");
169818
+ return withBlank.map((line) => truncateToWidth(line, width));
169819
+ }
169820
+ withLeadingBlank(lines) {
169821
+ return this.leadingBlank ? ["", ...lines] : lines;
169822
+ }
169823
+ };
169824
+ /**
169825
+ * Builds a marker for a lifecycle change (paused / resumed / blocked), or `null`
169826
+ * when the change should be silent (a `completion` change posts its own message,
169827
+ * not a marker). `expanded` seeds the initial ctrl+o state.
169828
+ */
169829
+ function buildGoalMarker(change, expanded, actor) {
169830
+ const spec = markerSpec(change, actor);
169831
+ if (spec === null) return null;
169832
+ const marker = new GoalMarkerComponent(spec.headline, spec.detail ?? change.reason, spec.accentToken, spec.options);
169833
+ marker.setExpanded(expanded);
169834
+ return marker;
169835
+ }
169836
+ function markerSpec(change, actor) {
169837
+ if (change.kind === "lifecycle") switch (change.status) {
169838
+ case "paused": return prominentMarker(pausedHeadline(change.reason, actor), "warning");
169839
+ case "active": return prominentMarker(resumedHeadline(actor), "primary");
169840
+ case "blocked": return {
169841
+ headline: "Goal blocked",
169842
+ accentToken: "warning"
169843
+ };
169844
+ default: return null;
169845
+ }
169846
+ return null;
169847
+ }
169848
+ function prominentMarker(headline, accentToken) {
169849
+ return {
169850
+ headline,
169851
+ accentToken,
169852
+ detail: void 0,
169853
+ options: {
169854
+ marker: STATUS_BULLET.trimEnd(),
169855
+ textToken: accentToken,
169856
+ expandable: false,
169857
+ indent: "",
169858
+ leadingBlank: true
169859
+ }
169860
+ };
169861
+ }
169862
+ function pausedHeadline(reason, actor) {
169863
+ if (reason === "Paused after interruption") return "Goal paused due to user's interruption";
169864
+ if (actor === "user") return "Goal paused by the user.";
169865
+ if (reason?.startsWith("Paused ") === true) return `Goal ${lowercaseFirst(reason)}`;
169866
+ if (reason !== void 0 && reason.length > 0) return `Goal paused: ${reason}`;
169867
+ if (actor === "model") return "Goal paused by the agent.";
169868
+ return "Goal paused";
169869
+ }
169870
+ function resumedHeadline(actor) {
169871
+ if (actor === "user") return "Goal resumed by the user.";
169872
+ if (actor === "model") return "Goal resumed by the agent.";
169873
+ return "Goal resumed";
169874
+ }
169875
+ function lowercaseFirst(text) {
169876
+ return text.length === 0 ? text : `${text[0].toLowerCase()}${text.slice(1)}`;
169877
+ }
169878
+ function wrap(text, width) {
169879
+ const words = text.replaceAll(/\s+/g, " ").trim().split(" ");
169880
+ const lines = [];
169881
+ let current = "";
169882
+ for (const word of words) {
169883
+ const candidate = current.length === 0 ? word : `${current} ${word}`;
169884
+ if (candidate.length > width && current.length > 0) {
169885
+ lines.push(current);
169886
+ current = word;
169887
+ } else current = candidate;
169888
+ }
169889
+ if (current.length > 0) lines.push(current);
169890
+ return lines.length > 0 ? lines : [""];
169891
+ }
169892
+ //#endregion
169893
+ //#region src/tui/components/messages/status-message.ts
169894
+ var StatusMessageComponent = class extends Container {
169895
+ textComponent;
169896
+ content;
169897
+ color;
169898
+ constructor(content, color) {
169899
+ super();
169900
+ this.content = content;
169901
+ this.color = color;
169902
+ const text = color === void 0 ? currentTheme.fg("textDim", content) : currentTheme.fg(color, content);
169903
+ this.textComponent = new Text(` ${text}`, 0, 0);
169904
+ this.addChild(this.textComponent);
169905
+ }
169906
+ invalidate() {
169907
+ const text = this.color === void 0 ? currentTheme.fg("textDim", this.content) : currentTheme.fg(this.color, this.content);
169908
+ this.textComponent.setText(` ${text}`);
169909
+ super.invalidate();
169910
+ }
169911
+ };
169912
+ var NoticeMessageComponent = class extends Container {
169913
+ titleText;
169914
+ detailText;
169915
+ title;
169916
+ detail;
169917
+ constructor(title, detail) {
169918
+ super();
169919
+ this.title = title;
169920
+ this.detail = detail;
169921
+ this.addChild(new Spacer(1));
169922
+ this.titleText = new Text(` ${currentTheme.fg("textStrong", title)}`, 0, 0);
169923
+ this.addChild(this.titleText);
169924
+ if (detail !== void 0 && detail.length > 0) {
169925
+ this.detailText = new Text(` ${currentTheme.fg("textDim", detail)}`, 0, 0);
169926
+ this.addChild(this.detailText);
169927
+ }
169928
+ }
169929
+ invalidate() {
169930
+ this.titleText.setText(` ${currentTheme.fg("textStrong", this.title)}`);
169931
+ if (this.detailText !== void 0 && this.detail !== void 0) this.detailText.setText(` ${currentTheme.fg("textDim", this.detail)}`);
169932
+ super.invalidate();
169933
+ }
169934
+ };
169935
+ //#endregion
169936
+ //#region src/tui/components/panes/activity-pane.ts
169937
+ var ActivityPaneComponent = class extends Container {
169938
+ constructor(options) {
169939
+ super();
169940
+ if (options.mode === "waiting" || options.mode === "tool") {
169941
+ if (options.spinner !== void 0) {
169942
+ this.addChild(new Spacer(1));
169943
+ this.addChild(options.spinner);
169944
+ }
169945
+ return;
169946
+ }
169947
+ if (options.mode === "composing" && options.spinner !== void 0) {
169948
+ this.addChild(new Spacer(1));
169949
+ this.addChild(options.spinner);
169950
+ }
169951
+ }
169952
+ };
169953
+ //#endregion
169954
+ //#region src/tui/components/panes/queue-pane.ts
169955
+ const ELLIPSIS$3 = "…";
169956
+ var QueuePaneComponent = class extends Container {
169957
+ messages;
169958
+ hint;
169959
+ constructor(options) {
169960
+ super();
169961
+ this.messages = options.messages;
169962
+ if (options.messages.length > 0) this.hint = options.isCompacting && !options.isStreaming ? " ↑ to edit · will send after compaction" : !options.canSteerImmediately ? " ↑ to edit · will send after current task" : " ↑ to edit · ctrl-s to steer immediately";
169963
+ }
169964
+ render(width) {
169965
+ const accent = (text) => currentTheme.fg("accent", text);
169966
+ const dim = (text) => currentTheme.fg("textDim", text);
169967
+ const lines = [];
169968
+ for (const item of this.messages) {
169969
+ const singleLine = item.text.replaceAll(/\s+/g, " ").trim();
169970
+ const prefix = ` ❯ `;
169971
+ const truncated = truncateToWidth(singleLine, Math.max(1, width - visibleWidth(prefix)), ELLIPSIS$3);
169972
+ lines.push(accent(prefix + truncated));
169973
+ }
169974
+ if (this.hint !== void 0) lines.push(dim(truncateToWidth(this.hint, width, ELLIPSIS$3)));
169975
+ return lines;
169976
+ }
169977
+ };
169978
+ //#endregion
168824
169979
  //#region src/tui/utils/refresh-providers.ts
168825
169980
  function readCustomRegistrySource(provider) {
168826
169981
  const source = provider.source;
@@ -168837,6 +169992,26 @@ function readCustomRegistrySource(provider) {
168837
169992
  apiKey
168838
169993
  };
168839
169994
  }
169995
+ function customRegistrySourceKey(source) {
169996
+ return JSON.stringify([source.url]);
169997
+ }
169998
+ function customRegistrySourceCredentialKey(source) {
169999
+ return JSON.stringify([source.url, source.apiKey]);
170000
+ }
170001
+ async function fetchCustomRegistryFromSources(sources) {
170002
+ let lastError;
170003
+ for (const source of sources) try {
170004
+ return {
170005
+ entries: await fetchCustomRegistry(source),
170006
+ source
170007
+ };
170008
+ } catch (error) {
170009
+ lastError = error;
170010
+ }
170011
+ if (lastError instanceof Error) throw lastError;
170012
+ if (typeof lastError === "string") throw new Error(lastError);
170013
+ throw new Error("No custom registry sources configured.");
170014
+ }
168840
170015
  function asManaged(config) {
168841
170016
  return config;
168842
170017
  }
@@ -168887,6 +170062,12 @@ function providerModelSnapshot(config, providerId, aliasKeys) {
168887
170062
  function providerModelsEqual(config, nextConfig, providerId, aliasKeys) {
168888
170063
  return providerModelSnapshot(config, providerId, aliasKeys) === providerModelSnapshot(nextConfig, providerId, aliasKeys);
168889
170064
  }
170065
+ function providerConfigSnapshot(config, providerId) {
170066
+ return JSON.stringify(config.providers[providerId] ?? null);
170067
+ }
170068
+ function providerConfigEqual(config, nextConfig, providerId) {
170069
+ return providerConfigSnapshot(config, providerId) === providerConfigSnapshot(nextConfig, providerId);
170070
+ }
168890
170071
  function providerRefreshAliasKeys(config, nextConfig, providerId, aliasPrefix) {
168891
170072
  const keys = generatedProviderAliasKeys(config, providerId, aliasPrefix);
168892
170073
  for (const key of providerAliasKeys(nextConfig, providerId)) keys.add(key);
@@ -168910,7 +170091,7 @@ function restoreProviderAliases(config, aliases) {
168910
170091
  function restoreDefaultSelection(config, defaultModel, defaultThinking) {
168911
170092
  if (defaultModel === void 0 || config.models?.[defaultModel] === void 0) return;
168912
170093
  config.defaultModel = defaultModel;
168913
- config.defaultThinking = defaultThinking;
170094
+ config.defaultThinking = (config.models[defaultModel]?.capabilities ?? []).includes("always_thinking") ? true : defaultThinking;
168914
170095
  }
168915
170096
  function clampDanglingDefault(config) {
168916
170097
  if (config.defaultModel !== void 0 && config.models?.[config.defaultModel] === void 0) {
@@ -168918,6 +170099,9 @@ function clampDanglingDefault(config) {
168918
170099
  config.defaultThinking = void 0;
168919
170100
  }
168920
170101
  }
170102
+ function clearDefaultThinkingWhenDefaultRemoved(config, previousDefaultModel) {
170103
+ if (previousDefaultModel !== void 0 && config.defaultModel === void 0) config.defaultThinking = void 0;
170104
+ }
168921
170105
  function pickDefaultModel(config, providerId, models) {
168922
170106
  const firstModel = models[0];
168923
170107
  if (firstModel === void 0) return "";
@@ -168959,6 +170143,7 @@ async function refreshAllProviderModels(host) {
168959
170143
  restoreProviderAliases(next, preserveUserProviderAliases(config, KIMI_CODE_PROVIDER_NAME, refreshedAliasKeys));
168960
170144
  restoreDefaultSelection(next, config.defaultModel, config.defaultThinking);
168961
170145
  clampDanglingDefault(next);
170146
+ clearDefaultThinkingWhenDefaultRemoved(next, config.defaultModel);
168962
170147
  if (providerModelsEqual(config, next, "managed:kimi-code", refreshedAliasKeys)) unchanged.push(KIMI_CODE_PROVIDER_NAME);
168963
170148
  else {
168964
170149
  const { added, removed } = computeChanges(collectModelIdsForAliases(config, refreshedAliasKeys), collectModelIdsForAliases(next, refreshedAliasKeys));
@@ -169010,6 +170195,7 @@ async function refreshAllProviderModels(host) {
169010
170195
  restoreProviderAliases(next, preserveUserProviderAliases(config, providerId, refreshedAliasKeys));
169011
170196
  restoreDefaultSelection(next, config.defaultModel, config.defaultThinking);
169012
170197
  clampDanglingDefault(next);
170198
+ clearDefaultThinkingWhenDefaultRemoved(next, config.defaultModel);
169013
170199
  if (providerModelsEqual(config, next, providerId, refreshedAliasKeys)) unchanged.push(providerId);
169014
170200
  else {
169015
170201
  const { added, removed } = computeChanges(collectModelIdsForAliases(config, refreshedAliasKeys), collectModelIdsForAliases(next, refreshedAliasKeys));
@@ -169040,26 +170226,55 @@ async function refreshAllProviderModels(host) {
169040
170226
  if (isOpenPlatformId(providerId)) continue;
169041
170227
  const source = readCustomRegistrySource(providerConfig);
169042
170228
  if (source === void 0) continue;
169043
- const key = `${source.url}${source.apiKey}`;
170229
+ const key = customRegistrySourceKey(source);
170230
+ const sourceKey = customRegistrySourceCredentialKey(source);
169044
170231
  const entry = customSources.get(key);
169045
- if (entry !== void 0) entry.providerIds.push(providerId);
169046
- else customSources.set(key, {
169047
- source,
170232
+ if (entry !== void 0) {
170233
+ if (!entry.sourceKeys.has(sourceKey)) {
170234
+ entry.sources.push(source);
170235
+ entry.sourceKeys.add(sourceKey);
170236
+ }
170237
+ entry.providerIds.push(providerId);
170238
+ } else customSources.set(key, {
170239
+ sources: [source],
170240
+ sourceKeys: new Set([sourceKey]),
169048
170241
  providerIds: [providerId]
169049
170242
  });
169050
170243
  }
169051
- for (const { source, providerIds } of customSources.values()) try {
169052
- const entries = await fetchCustomRegistry(source);
170244
+ for (const { sources, providerIds } of customSources.values()) try {
170245
+ const { entries, source } = await fetchCustomRegistryFromSources(sources);
169053
170246
  const next = structuredClone(config);
169054
170247
  const changedProviders = [];
169055
- for (const providerId of providerIds) {
169056
- const entry = entries[providerId];
169057
- if (entry === void 0) continue;
170248
+ const providersToRemoveBeforeSet = /* @__PURE__ */ new Set();
170249
+ let hasUnreportedConfigChange = false;
170250
+ const remoteEntries = Object.values(entries);
170251
+ const remoteEntriesByProviderId = new Map(remoteEntries.map((entry) => [entry.id, entry]));
170252
+ const providerIdsToSync = new Set(providerIds);
170253
+ for (const entry of remoteEntries) providerIdsToSync.add(entry.id);
170254
+ for (const providerId of providerIdsToSync) {
170255
+ const entry = remoteEntriesByProviderId.get(providerId);
170256
+ if (entry === void 0) {
170257
+ const oldIds = collectModelIdsForAliases(config, providerAliasKeys(config, providerId));
170258
+ removeCustomRegistryProvider(asManaged(next), providerId);
170259
+ changedProviders.push({
170260
+ providerId,
170261
+ providerName: providerId,
170262
+ added: 0,
170263
+ removed: oldIds.size
170264
+ });
170265
+ providersToRemoveBeforeSet.add(providerId);
170266
+ continue;
170267
+ }
170268
+ const existed = config.providers[providerId] !== void 0;
169058
170269
  applyCustomRegistryProvider(asManaged(next), entry, source);
169059
170270
  const refreshedAliasKeys = providerRefreshAliasKeys(config, next, providerId, `${providerId}/`);
169060
- restoreProviderAliases(next, preserveUserProviderAliases(config, providerId, refreshedAliasKeys));
169061
- if (providerModelsEqual(config, next, providerId, refreshedAliasKeys)) unchanged.push(providerId);
169062
- else {
170271
+ if (existed) restoreProviderAliases(next, preserveUserProviderAliases(config, providerId, refreshedAliasKeys));
170272
+ if (existed && providerModelsEqual(config, next, providerId, refreshedAliasKeys) && providerConfigEqual(config, next, providerId)) unchanged.push(providerId);
170273
+ else if (existed && providerModelsEqual(config, next, providerId, refreshedAliasKeys)) {
170274
+ unchanged.push(providerId);
170275
+ providersToRemoveBeforeSet.add(providerId);
170276
+ hasUnreportedConfigChange = true;
170277
+ } else {
169063
170278
  const { added, removed } = computeChanges(collectModelIdsForAliases(config, refreshedAliasKeys), collectModelIdsForAliases(next, refreshedAliasKeys));
169064
170279
  changedProviders.push({
169065
170280
  providerId,
@@ -169067,19 +170282,26 @@ async function refreshAllProviderModels(host) {
169067
170282
  added,
169068
170283
  removed
169069
170284
  });
170285
+ if (existed) providersToRemoveBeforeSet.add(providerId);
169070
170286
  }
169071
170287
  }
169072
- if (changedProviders.length > 0) {
170288
+ if (changedProviders.length > 0 || hasUnreportedConfigChange) {
169073
170289
  restoreDefaultSelection(next, config.defaultModel, config.defaultThinking);
169074
170290
  clampDanglingDefault(next);
169075
- for (const { providerId } of changedProviders) await host.removeProvider(providerId);
170291
+ clearDefaultThinkingWhenDefaultRemoved(next, config.defaultModel);
170292
+ for (const providerId of providersToRemoveBeforeSet) await host.removeProvider(providerId);
169076
170293
  config = await host.setConfig({
169077
170294
  providers: next.providers,
169078
170295
  models: next.models,
169079
170296
  defaultModel: next.defaultModel,
169080
170297
  defaultThinking: next.defaultThinking
169081
170298
  });
169082
- for (const change of changedProviders) changed.push(change);
170299
+ for (const change of changedProviders) changed.push({
170300
+ providerId: change.providerId,
170301
+ providerName: change.providerName,
170302
+ added: change.added,
170303
+ removed: change.removed
170304
+ });
169083
170305
  }
169084
170306
  } catch (error) {
169085
170307
  for (const providerId of providerIds) failed.push({
@@ -169539,13 +170761,7 @@ var BtwPanelController = class {
169539
170761
  return panel.isRunning() || panel.isEmpty();
169540
170762
  }
169541
170763
  withInteractiveAgent(agentId, fn) {
169542
- const previousAgentId = this.host.harness.interactiveAgentId;
169543
- this.host.harness.interactiveAgentId = agentId;
169544
- try {
169545
- return fn();
169546
- } finally {
169547
- this.host.harness.interactiveAgentId = previousAgentId;
169548
- }
170764
+ return this.host.harness.withInteractiveAgent(agentId, fn);
169549
170765
  }
169550
170766
  };
169551
170767
  function formatBtwTurnEnd(event) {
@@ -170682,173 +171898,6 @@ var EditorKeyboardController = class {
170682
171898
  }
170683
171899
  };
170684
171900
  //#endregion
170685
- //#region src/tui/components/messages/goal-markers.ts
170686
- /**
170687
- * Low-profile transcript markers for the autonomous goal loop.
170688
- *
170689
- * Lifecycle changes (paused / resumed / cancelled) and `no_progress` verdicts
170690
- * render as a single dim line — `◦ Goal paused` — that expands (ctrl+o, shared
170691
- * with tool output) to show the reason when there is one. Terminal outcomes use
170692
- * the richer completion card (the `/goal` box), not this marker.
170693
- */
170694
- const HEAD_INDENT = " ";
170695
- const DETAIL_INDENT = " ";
170696
- var GoalMarkerComponent = class {
170697
- headline;
170698
- detail;
170699
- accentToken;
170700
- expanded = false;
170701
- marker;
170702
- textToken;
170703
- expandable;
170704
- indent;
170705
- leadingBlank;
170706
- constructor(headline, detail, accentToken, options = {}) {
170707
- this.headline = headline;
170708
- this.detail = detail;
170709
- this.accentToken = accentToken;
170710
- this.marker = options.marker ?? "◦";
170711
- this.textToken = options.textToken ?? "textDim";
170712
- this.expandable = options.expandable ?? true;
170713
- this.indent = options.indent ?? HEAD_INDENT;
170714
- this.leadingBlank = options.leadingBlank ?? false;
170715
- }
170716
- invalidate() {}
170717
- setExpanded(expanded) {
170718
- this.expanded = expanded;
170719
- }
170720
- render(width) {
170721
- const dot = currentTheme.fg(this.accentToken, this.marker);
170722
- const head = currentTheme.fg(this.textToken, this.headline);
170723
- if (!(this.detail !== void 0 && this.detail.length > 0)) return this.clampToWidth([`${this.indent}${dot} ${head}`], width);
170724
- if (!this.expandable) return this.clampToWidth([`${this.indent}${dot} ${head}`], width);
170725
- if (!this.expanded) return this.clampToWidth([`${this.indent}${dot} ${head} ${currentTheme.fg("textMuted", "(ctrl+o)")}`], width);
170726
- const out = [`${this.indent}${dot} ${head}`];
170727
- const wrapWidth = Math.max(20, width - 4);
170728
- for (const line of wrap(this.detail, wrapWidth)) out.push(DETAIL_INDENT + currentTheme.fg("textDim", line));
170729
- return this.clampToWidth(out, width);
170730
- }
170731
- clampToWidth(lines, width) {
170732
- const withBlank = this.withLeadingBlank(lines);
170733
- if (width <= 0) return withBlank.map(() => "");
170734
- return withBlank.map((line) => truncateToWidth(line, width));
170735
- }
170736
- withLeadingBlank(lines) {
170737
- return this.leadingBlank ? ["", ...lines] : lines;
170738
- }
170739
- };
170740
- /**
170741
- * Builds a marker for a lifecycle change (paused / resumed / blocked), or `null`
170742
- * when the change should be silent (a `completion` change posts its own message,
170743
- * not a marker). `expanded` seeds the initial ctrl+o state.
170744
- */
170745
- function buildGoalMarker(change, expanded, actor) {
170746
- const spec = markerSpec(change, actor);
170747
- if (spec === null) return null;
170748
- const marker = new GoalMarkerComponent(spec.headline, spec.detail ?? change.reason, spec.accentToken, spec.options);
170749
- marker.setExpanded(expanded);
170750
- return marker;
170751
- }
170752
- function markerSpec(change, actor) {
170753
- if (change.kind === "lifecycle") switch (change.status) {
170754
- case "paused": return prominentMarker(pausedHeadline(change.reason, actor), "warning");
170755
- case "active": return prominentMarker(resumedHeadline(actor), "primary");
170756
- case "blocked": return {
170757
- headline: "Goal blocked",
170758
- accentToken: "warning"
170759
- };
170760
- default: return null;
170761
- }
170762
- return null;
170763
- }
170764
- function prominentMarker(headline, accentToken) {
170765
- return {
170766
- headline,
170767
- accentToken,
170768
- detail: void 0,
170769
- options: {
170770
- marker: STATUS_BULLET.trimEnd(),
170771
- textToken: accentToken,
170772
- expandable: false,
170773
- indent: "",
170774
- leadingBlank: true
170775
- }
170776
- };
170777
- }
170778
- function pausedHeadline(reason, actor) {
170779
- if (reason === "Paused after interruption") return "Goal paused due to user's interruption";
170780
- if (actor === "user") return "Goal paused by the user.";
170781
- if (reason?.startsWith("Paused ") === true) return `Goal ${lowercaseFirst(reason)}`;
170782
- if (reason !== void 0 && reason.length > 0) return `Goal paused: ${reason}`;
170783
- if (actor === "model") return "Goal paused by the agent.";
170784
- return "Goal paused";
170785
- }
170786
- function resumedHeadline(actor) {
170787
- if (actor === "user") return "Goal resumed by the user.";
170788
- if (actor === "model") return "Goal resumed by the agent.";
170789
- return "Goal resumed";
170790
- }
170791
- function lowercaseFirst(text) {
170792
- return text.length === 0 ? text : `${text[0].toLowerCase()}${text.slice(1)}`;
170793
- }
170794
- function wrap(text, width) {
170795
- const words = text.replaceAll(/\s+/g, " ").trim().split(" ");
170796
- const lines = [];
170797
- let current = "";
170798
- for (const word of words) {
170799
- const candidate = current.length === 0 ? word : `${current} ${word}`;
170800
- if (candidate.length > width && current.length > 0) {
170801
- lines.push(current);
170802
- current = word;
170803
- } else current = candidate;
170804
- }
170805
- if (current.length > 0) lines.push(current);
170806
- return lines.length > 0 ? lines : [""];
170807
- }
170808
- //#endregion
170809
- //#region src/tui/components/messages/status-message.ts
170810
- var StatusMessageComponent = class extends Container {
170811
- textComponent;
170812
- content;
170813
- color;
170814
- constructor(content, color) {
170815
- super();
170816
- this.content = content;
170817
- this.color = color;
170818
- const text = color === void 0 ? currentTheme.fg("textDim", content) : currentTheme.fg(color, content);
170819
- this.textComponent = new Text(` ${text}`, 0, 0);
170820
- this.addChild(this.textComponent);
170821
- }
170822
- invalidate() {
170823
- const text = this.color === void 0 ? currentTheme.fg("textDim", this.content) : currentTheme.fg(this.color, this.content);
170824
- this.textComponent.setText(` ${text}`);
170825
- super.invalidate();
170826
- }
170827
- };
170828
- var NoticeMessageComponent = class extends Container {
170829
- titleText;
170830
- detailText;
170831
- title;
170832
- detail;
170833
- constructor(title, detail) {
170834
- super();
170835
- this.title = title;
170836
- this.detail = detail;
170837
- this.addChild(new Spacer(1));
170838
- this.titleText = new Text(` ${currentTheme.fg("textStrong", title)}`, 0, 0);
170839
- this.addChild(this.titleText);
170840
- if (detail !== void 0 && detail.length > 0) {
170841
- this.detailText = new Text(` ${currentTheme.fg("textDim", detail)}`, 0, 0);
170842
- this.addChild(this.detailText);
170843
- }
170844
- }
170845
- invalidate() {
170846
- this.titleText.setText(` ${currentTheme.fg("textStrong", this.title)}`);
170847
- if (this.detailText !== void 0 && this.detail !== void 0) this.detailText.setText(` ${currentTheme.fg("textDim", this.detail)}`);
170848
- super.invalidate();
170849
- }
170850
- };
170851
- //#endregion
170852
171901
  //#region src/tui/utils/goal-completion.ts
170853
171902
  /**
170854
171903
  * Deterministic goal-completion text rendered by the TUI when the model marks a
@@ -171128,6 +172177,7 @@ var SubAgentEventHandler = class {
171128
172177
  name: event.name,
171129
172178
  argumentsPart: event.argumentsPart ?? null
171130
172179
  });
172180
+ else if (event.type === "tool.progress" && (event.update.kind === "stdout" || event.update.kind === "stderr") && event.update.text !== void 0) toolCall.appendSubToolLiveOutput(`${childAgentId}:${event.toolCallId}`, event.update.text);
171131
172181
  else if (event.type === "tool.result") toolCall.finishSubToolCall({
171132
172182
  tool_call_id: `${childAgentId}:${event.toolCallId}`,
171133
172183
  output: serializeToolResultOutput(event.output),
@@ -171865,12 +172915,15 @@ var SessionEventHandler = class {
171865
172915
  streamingUI.scheduleFlush();
171866
172916
  }
171867
172917
  handleToolProgress(event) {
171868
- if (event.update.kind !== "status") return;
171869
172918
  const text = event.update.text;
171870
172919
  if (text === void 0 || text.length === 0) return;
171871
172920
  const tc = this.host.streamingUI.getToolComponent(event.toolCallId);
171872
172921
  if (tc === void 0) return;
171873
- tc.appendProgress(text);
172922
+ if (event.update.kind === "status") {
172923
+ tc.appendProgress(text);
172924
+ return;
172925
+ }
172926
+ if (event.update.kind === "stdout" || event.update.kind === "stderr") tc.appendLiveOutput(text);
171874
172927
  }
171875
172928
  handleToolResult(event) {
171876
172929
  const { streamingUI } = this.host;
@@ -172579,6 +173632,9 @@ var SessionReplayRenderer = class {
172579
173632
  case "message":
172580
173633
  this.renderMessage(context, record.message);
172581
173634
  return;
173635
+ case "compaction":
173636
+ this.renderCompaction(context, record);
173637
+ return;
172582
173638
  case "goal_updated":
172583
173639
  this.renderGoalReplayRecord(context, record);
172584
173640
  return;
@@ -172741,6 +173797,28 @@ var SessionReplayRenderer = class {
172741
173797
  skillTrigger: skill.trigger
172742
173798
  });
172743
173799
  }
173800
+ renderCompaction(context, record) {
173801
+ this.flushAssistant(context);
173802
+ if (record.result === void 0) return;
173803
+ if (record.result === "cancelled") {
173804
+ this.host.appendTranscriptEntry({
173805
+ ...replayEntry(context, "status", "Compaction cancelled", "plain"),
173806
+ compactionData: {
173807
+ result: "cancelled",
173808
+ instruction: record.instruction
173809
+ }
173810
+ });
173811
+ return;
173812
+ }
173813
+ this.host.appendTranscriptEntry({
173814
+ ...replayEntry(context, "status", "Compaction complete", "plain"),
173815
+ compactionData: {
173816
+ tokensBefore: record.result.tokensBefore,
173817
+ tokensAfter: record.result.tokensAfter,
173818
+ instruction: record.instruction
173819
+ }
173820
+ });
173821
+ }
172744
173822
  renderGoalReplayRecord(context, record) {
172745
173823
  this.flushAssistant(context);
172746
173824
  const { change } = record;
@@ -173011,6 +174089,23 @@ function supportsOsc9Notification(env = process.env) {
173011
174089
  if (term === "xterm-kitty" || term === "xterm-ghostty") return true;
173012
174090
  return false;
173013
174091
  }
174092
+ /**
174093
+ * Best-effort detection of ConEmu-style OSC 9;4 progress support, driven
174094
+ * off well-known environment variables like `supportsOsc9Notification`.
174095
+ * The two allow-lists must stay separate: iTerm2 posts a desktop
174096
+ * notification for ANY `OSC 9;<payload>` it receives, so sending the 9;4
174097
+ * progress sequence there pops a "4;3" notification every keepalive tick.
174098
+ * Terminals outside this list simply get no progress reporting, which is
174099
+ * always safe.
174100
+ */
174101
+ function supportsTerminalProgress(env = process.env) {
174102
+ if ((env["WT_SESSION"] ?? "").length > 0) return true;
174103
+ if (env["ConEmuANSI"] === "ON") return true;
174104
+ const termProgram = env["TERM_PROGRAM"] ?? "";
174105
+ if (termProgram === "ghostty" || termProgram === "WezTerm") return true;
174106
+ if ((env["TERM"] ?? "") === "xterm-ghostty") return true;
174107
+ return false;
174108
+ }
173014
174109
  function isInsideTmux(env = process.env) {
173015
174110
  return (env["TMUX"] ?? "").length > 0;
173016
174111
  }
@@ -174536,217 +175631,6 @@ var TasksBrowserController = class {
174536
175631
  }
174537
175632
  };
174538
175633
  //#endregion
174539
- //#region src/tui/components/editor/file-mention-provider.ts
174540
- const PATH_DELIMITERS = new Set([
174541
- " ",
174542
- " ",
174543
- "\"",
174544
- "'",
174545
- "="
174546
- ]);
174547
- const MAX_FALLBACK_SCAN = 2e3;
174548
- const MAX_FALLBACK_SUGGESTIONS = 50;
174549
- /**
174550
- * Kimi wrapper around pi-tui's combined autocomplete provider.
174551
- *
174552
- * File / folder mention behavior uses pi-tui's fd-backed provider when fd is
174553
- * available. While managed fd is downloading (or when it is unavailable), a
174554
- * small filesystem fallback keeps basic `@` file and folder completion usable.
174555
- * Ordinary path completion is still handled by pi-tui's readdir-backed path
174556
- * completer. This wrapper also keeps Kimi-specific slash-command guards.
174557
- */
174558
- var FileMentionProvider = class {
174559
- workDir;
174560
- fdPath;
174561
- inner;
174562
- constructor(slashCommands, workDir, fdPath) {
174563
- this.workDir = workDir;
174564
- this.fdPath = fdPath;
174565
- this.inner = new CombinedAutocompleteProvider(slashCommands, workDir, fdPath);
174566
- }
174567
- async getSuggestions(lines, cursorLine, cursorCol, options) {
174568
- const currentLine = lines[cursorLine] ?? "";
174569
- const textBeforeCursor = currentLine.slice(0, cursorCol);
174570
- if (shouldSuppressLeadingWhitespaceSlashPath(textBeforeCursor, options.force)) return null;
174571
- if (shouldSuppressSlashArgumentCompletion(textBeforeCursor, currentLine.slice(cursorCol), options.force)) return null;
174572
- const atPrefix = extractAtPrefix(textBeforeCursor);
174573
- if (atPrefix !== null) {
174574
- if (this.fdPath === null) return getFsMentionSuggestions(this.workDir, atPrefix, options.signal);
174575
- try {
174576
- return await this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
174577
- } catch {
174578
- return getFsMentionSuggestions(this.workDir, atPrefix, options.signal);
174579
- }
174580
- }
174581
- try {
174582
- return await this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
174583
- } catch {
174584
- return null;
174585
- }
174586
- }
174587
- applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
174588
- return this.inner.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
174589
- }
174590
- };
174591
- function extractAtPrefix(text) {
174592
- let tokenStart = 0;
174593
- for (let i = text.length - 1; i >= 0; i -= 1) if (PATH_DELIMITERS.has(text[i] ?? "")) {
174594
- tokenStart = i + 1;
174595
- break;
174596
- }
174597
- if (text[tokenStart] !== "@") return null;
174598
- return text.slice(tokenStart);
174599
- }
174600
- function getFsMentionSuggestions(workDir, atPrefix, signal) {
174601
- if (signal.aborted) return null;
174602
- const query = atPrefix.slice(1);
174603
- const candidates = collectFsMentionCandidates(workDir, signal);
174604
- if (candidates.length === 0 || signal.aborted) return null;
174605
- const ranked = rankFsMentionCandidates(candidates, query).slice(0, MAX_FALLBACK_SUGGESTIONS);
174606
- if (ranked.length === 0) return null;
174607
- return {
174608
- prefix: atPrefix,
174609
- items: ranked.map(toMentionItem)
174610
- };
174611
- }
174612
- function collectFsMentionCandidates(workDir, signal) {
174613
- const result = [];
174614
- const stack = [""];
174615
- while (stack.length > 0 && result.length < MAX_FALLBACK_SCAN) {
174616
- if (signal.aborted) break;
174617
- const relativeDir = stack.pop() ?? "";
174618
- const absoluteDir = relativeDir.length === 0 ? workDir : join$1(workDir, relativeDir);
174619
- let entries;
174620
- try {
174621
- entries = readdirSync(absoluteDir, { withFileTypes: true });
174622
- } catch {
174623
- continue;
174624
- }
174625
- for (const entry of entries) {
174626
- if (signal.aborted || result.length >= MAX_FALLBACK_SCAN) break;
174627
- if (entry.name === ".git") continue;
174628
- const relativePath = normalizePath(relativeDir.length === 0 ? entry.name : join$1(relativeDir, entry.name));
174629
- const isSymlink = entry.isSymbolicLink();
174630
- let isDirectory = entry.isDirectory();
174631
- if (!isDirectory && isSymlink) try {
174632
- isDirectory = statSync(join$1(workDir, relativePath)).isDirectory();
174633
- } catch {}
174634
- result.push({
174635
- path: relativePath,
174636
- isDirectory
174637
- });
174638
- if (isDirectory && !isSymlink) stack.push(relativePath);
174639
- }
174640
- }
174641
- return result;
174642
- }
174643
- function rankFsMentionCandidates(candidates, query) {
174644
- const lowerQuery = query.toLowerCase();
174645
- const scored = [];
174646
- for (const candidate of candidates) {
174647
- const score = scoreCandidate(candidate, lowerQuery);
174648
- if (score > 0) scored.push({
174649
- candidate,
174650
- score
174651
- });
174652
- }
174653
- scored.sort((a, b) => {
174654
- if (a.score !== b.score) return b.score - a.score;
174655
- if (a.candidate.isDirectory !== b.candidate.isDirectory) return a.candidate.isDirectory ? -1 : 1;
174656
- return a.candidate.path.localeCompare(b.candidate.path);
174657
- });
174658
- return scored.map((entry) => entry.candidate);
174659
- }
174660
- function scoreCandidate(candidate, lowerQuery) {
174661
- if (lowerQuery.length === 0) {
174662
- const depthPenalty = candidate.path.split("/").length - 1;
174663
- return (candidate.isDirectory ? 120 : 100) - depthPenalty;
174664
- }
174665
- const lowerPath = candidate.path.toLowerCase();
174666
- const lowerBase = basename$1(candidate.path).toLowerCase();
174667
- let score = 0;
174668
- if (lowerBase === lowerQuery) score = 100;
174669
- else if (lowerBase.startsWith(lowerQuery)) score = 80;
174670
- else if (lowerBase.includes(lowerQuery)) score = 50;
174671
- else if (lowerPath.includes(lowerQuery)) score = 30;
174672
- if (candidate.isDirectory && score > 0) score += 10;
174673
- return score;
174674
- }
174675
- function toMentionItem(candidate) {
174676
- const valuePath = candidate.isDirectory ? `${candidate.path}/` : candidate.path;
174677
- return {
174678
- value: valuePath.includes(" ") ? `@"${valuePath}"` : `@${valuePath}`,
174679
- label: `${basename$1(candidate.path)}${candidate.isDirectory ? "/" : ""}`,
174680
- description: valuePath
174681
- };
174682
- }
174683
- function normalizePath(path) {
174684
- return path.replaceAll("\\", "/");
174685
- }
174686
- function shouldSuppressLeadingWhitespaceSlashPath(textBeforeCursor, force) {
174687
- if (force === true) return false;
174688
- if (textBeforeCursor.startsWith("/")) return false;
174689
- return textBeforeCursor.trimStart().startsWith("/");
174690
- }
174691
- function shouldSuppressSlashArgumentCompletion(textBeforeCursor, textAfterCursor, force) {
174692
- if (force === true) return false;
174693
- if (!textBeforeCursor.startsWith("/")) return false;
174694
- if (!textBeforeCursor.includes(" ")) return false;
174695
- return textAfterCursor.trimStart().length > 0;
174696
- }
174697
- //#endregion
174698
- //#region src/tui/components/panes/activity-pane.ts
174699
- var ActivityPaneComponent = class extends Container {
174700
- constructor(options) {
174701
- super();
174702
- if (options.mode === "waiting" || options.mode === "tool") {
174703
- if (options.spinner !== void 0) {
174704
- this.addChild(new Spacer(1));
174705
- this.addChild(options.spinner);
174706
- }
174707
- return;
174708
- }
174709
- if (options.mode === "composing" && options.spinner !== void 0) {
174710
- this.addChild(new Spacer(1));
174711
- this.addChild(options.spinner);
174712
- }
174713
- }
174714
- };
174715
- //#endregion
174716
- //#region src/tui/components/panes/queue-pane.ts
174717
- const ELLIPSIS = "…";
174718
- var QueuePaneComponent = class extends Container {
174719
- messages;
174720
- hint;
174721
- constructor(options) {
174722
- super();
174723
- this.messages = options.messages;
174724
- if (options.messages.length > 0) this.hint = options.isCompacting && !options.isStreaming ? " ↑ to edit · will send after compaction" : !options.canSteerImmediately ? " ↑ to edit · will send after current task" : " ↑ to edit · ctrl-s to steer immediately";
174725
- }
174726
- render(width) {
174727
- const accent = (text) => currentTheme.fg("accent", text);
174728
- const dim = (text) => currentTheme.fg("textDim", text);
174729
- const lines = [];
174730
- for (const item of this.messages) {
174731
- const singleLine = item.text.replaceAll(/\s+/g, " ").trim();
174732
- const prefix = ` ❯ `;
174733
- const truncated = truncateToWidth(singleLine, Math.max(1, width - visibleWidth(prefix)), ELLIPSIS);
174734
- lines.push(accent(prefix + truncated));
174735
- }
174736
- if (this.hint !== void 0) lines.push(dim(truncateToWidth(this.hint, width, ELLIPSIS)));
174737
- return lines;
174738
- }
174739
- };
174740
- //#endregion
174741
- //#region src/tui/utils/startup.ts
174742
- function combineStartupNotice(existing, next) {
174743
- if (existing !== void 0 && next !== void 0) return `${existing}\n${next}`;
174744
- return existing ?? next;
174745
- }
174746
- function isOAuthLoginRequiredError(error) {
174747
- return error.code === OAUTH_LOGIN_REQUIRED_CODE;
174748
- }
174749
- //#endregion
174750
175634
  //#region src/tui/reverse-rpc/approval/adapter.ts
174751
175635
  const DEFAULT_APPROVAL_CHOICES = [
174752
175636
  {
@@ -175305,13 +176189,6 @@ function adaptQuestionAnswers(event, response) {
175305
176189
  } : null;
175306
176190
  }
175307
176191
  //#endregion
175308
- //#region src/tui/types.ts
175309
- const INITIAL_LIVE_PANE = {
175310
- mode: "idle",
175311
- pendingApproval: null,
175312
- pendingQuestion: null
175313
- };
175314
- //#endregion
175315
176192
  //#region src/utils/git/git-status.ts
175316
176193
  /**
175317
176194
  * Cached git branch + working-tree status for the footer/statusline.
@@ -176028,6 +176905,107 @@ function styleTitle(title, status, colors) {
176028
176905
  }
176029
176906
  }
176030
176907
  //#endregion
176908
+ //#region src/tui/components/editor/wrapping-select-list.ts
176909
+ const DEFAULT_PRIMARY_COLUMN_WIDTH = 32;
176910
+ const PRIMARY_COLUMN_GAP = 2;
176911
+ const MIN_DESCRIPTION_WIDTH = 10;
176912
+ const DESCRIPTION_MAX_LINES = 2;
176913
+ const ELLIPSIS = "…";
176914
+ const ELLIPSIS_WIDTH = visibleWidth(ELLIPSIS);
176915
+ const TRAILING_ANSI_RESET = /(?:\u001B\[0m)+$/;
176916
+ function truncatePlainToWidth(text, maxWidth) {
176917
+ return truncateToWidth(text, maxWidth, "").replace(TRAILING_ANSI_RESET, "");
176918
+ }
176919
+ /**
176920
+ * SelectList that wraps item descriptions onto up to two lines instead of
176921
+ * truncating them to one. Long command / skill descriptions stay readable;
176922
+ * anything past the second line is ellipsized.
176923
+ *
176924
+ * Only `render` is replaced — selection, filtering, and key handling stay in
176925
+ * pi-tui. pi-tui keeps the row state private, so the renderer reads it
176926
+ * through a cast, the same idiom CustomEditor uses for autocomplete
176927
+ * internals.
176928
+ */
176929
+ var WrappingSelectList = class extends SelectList {
176930
+ render(width) {
176931
+ const { filteredItems, selectedIndex, maxVisible, theme } = this.internals();
176932
+ if (filteredItems.length === 0) return [theme.noMatch(" No matching commands")];
176933
+ const primaryColumnWidth = this.primaryColumnWidth();
176934
+ const startIndex = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), filteredItems.length - maxVisible));
176935
+ const endIndex = Math.min(startIndex + maxVisible, filteredItems.length);
176936
+ const lines = [];
176937
+ for (let i = startIndex; i < endIndex; i++) {
176938
+ const item = filteredItems[i];
176939
+ if (!item) continue;
176940
+ lines.push(...this.renderItemLines(item, i === selectedIndex, width, primaryColumnWidth));
176941
+ }
176942
+ if (startIndex > 0 || endIndex < filteredItems.length) {
176943
+ const scrollText = ` (${selectedIndex + 1}/${filteredItems.length})`;
176944
+ lines.push(theme.scrollInfo(truncatePlainToWidth(scrollText, width - 2)));
176945
+ }
176946
+ return lines;
176947
+ }
176948
+ renderItemLines(item, isSelected, width, primaryColumnWidth) {
176949
+ const { theme } = this.internals();
176950
+ const prefix = isSelected ? "→ " : " ";
176951
+ const prefixWidth = visibleWidth(prefix);
176952
+ const description = item.description ? item.description.replaceAll(/[\r\n]+/g, " ").trim() : void 0;
176953
+ if (description && width > 40) {
176954
+ const effectivePrimaryColumnWidth = Math.max(1, Math.min(primaryColumnWidth, width - prefixWidth - 4));
176955
+ const maxPrimaryWidth = Math.max(1, effectivePrimaryColumnWidth - PRIMARY_COLUMN_GAP);
176956
+ const truncatedValue = this.truncatePrimaryValue(item, isSelected, maxPrimaryWidth, effectivePrimaryColumnWidth);
176957
+ const truncatedValueWidth = visibleWidth(truncatedValue);
176958
+ const spacing = " ".repeat(Math.max(1, effectivePrimaryColumnWidth - truncatedValueWidth));
176959
+ const descriptionStart = prefixWidth + truncatedValueWidth + spacing.length;
176960
+ const remainingWidth = width - descriptionStart - 2;
176961
+ if (remainingWidth > MIN_DESCRIPTION_WIDTH) {
176962
+ const descriptionLines = wrapDescription(description, remainingWidth);
176963
+ const indent = " ".repeat(descriptionStart);
176964
+ if (isSelected) return descriptionLines.map((line, index) => theme.selectedText(index === 0 ? `${prefix}${truncatedValue}${spacing}${line}` : indent + line));
176965
+ return descriptionLines.map((line, index) => index === 0 ? prefix + truncatedValue + theme.description(spacing + line) : theme.description(indent + line));
176966
+ }
176967
+ }
176968
+ const maxWidth = width - prefixWidth - 2;
176969
+ const truncatedValue = this.truncatePrimaryValue(item, isSelected, maxWidth, maxWidth);
176970
+ return [isSelected ? theme.selectedText(`${prefix}${truncatedValue}`) : prefix + truncatedValue];
176971
+ }
176972
+ truncatePrimaryValue(item, isSelected, maxWidth, columnWidth) {
176973
+ const { layout } = this.internals();
176974
+ const displayValue = item.label || item.value;
176975
+ return truncatePlainToWidth(layout.truncatePrimary ? layout.truncatePrimary({
176976
+ text: displayValue,
176977
+ maxWidth,
176978
+ columnWidth,
176979
+ item,
176980
+ isSelected
176981
+ }) : displayValue, maxWidth);
176982
+ }
176983
+ primaryColumnWidth() {
176984
+ const { filteredItems, layout } = this.internals();
176985
+ const rawMin = layout.minPrimaryColumnWidth ?? layout.maxPrimaryColumnWidth ?? DEFAULT_PRIMARY_COLUMN_WIDTH;
176986
+ const rawMax = layout.maxPrimaryColumnWidth ?? layout.minPrimaryColumnWidth ?? DEFAULT_PRIMARY_COLUMN_WIDTH;
176987
+ const min = Math.max(1, Math.min(rawMin, rawMax));
176988
+ const max = Math.max(1, Math.max(rawMin, rawMax));
176989
+ const widest = filteredItems.reduce((acc, item) => Math.max(acc, visibleWidth(item.label || item.value) + PRIMARY_COLUMN_GAP), 0);
176990
+ return Math.max(min, Math.min(widest, max));
176991
+ }
176992
+ internals() {
176993
+ return this;
176994
+ }
176995
+ };
176996
+ /**
176997
+ * Wrap `text` to at most DESCRIPTION_MAX_LINES lines of `width` columns.
176998
+ * When the text needs more lines, the last visible line is rebuilt from the
176999
+ * remaining text and ellipsized.
177000
+ */
177001
+ function wrapDescription(text, width) {
177002
+ const wrapped = wrapTextWithAnsi(text, width);
177003
+ if (wrapped.length <= DESCRIPTION_MAX_LINES) return wrapped;
177004
+ const kept = wrapped.slice(0, DESCRIPTION_MAX_LINES - 1);
177005
+ const clipped = truncatePlainToWidth(wrapped.slice(DESCRIPTION_MAX_LINES - 1).join(" "), width - ELLIPSIS_WIDTH).trimEnd();
177006
+ return [...kept, `${clipped}${ELLIPSIS}`];
177007
+ }
177008
+ //#endregion
176031
177009
  //#region src/tui/components/editor/custom-editor.ts
176032
177010
  /**
176033
177011
  * Custom editor extending pi-tui Editor with app-level keybindings.
@@ -176040,6 +177018,10 @@ const KITTY_CSI_U = /^\u001B\[(\d+);(\d+)((?::\d+)*)u$/;
176040
177018
  const CAPS_LOCK_BIT = 64;
176041
177019
  const CTRL_BIT = 4;
176042
177020
  const SHIFT_BIT = 1;
177021
+ const SLASH_COMMAND_SELECT_LIST_LAYOUT = {
177022
+ minPrimaryColumnWidth: 12,
177023
+ maxPrimaryColumnWidth: 32
177024
+ };
176043
177025
  /**
176044
177026
  * Workaround for a pi-tui bug that surfaces when Kitty keyboard protocol
176045
177027
  * is active AND caps_lock is on. In that state terminals emit, e.g.,
@@ -176125,7 +177107,12 @@ var CustomEditor = class extends Editor {
176125
177107
  consumingPaste = false;
176126
177108
  consumeBuffer = "";
176127
177109
  constructor(tui) {
176128
- super(tui, createEditorTheme(), { paddingX: 4 });
177110
+ const theme = createEditorTheme();
177111
+ super(tui, theme, { paddingX: 4 });
177112
+ this.createAutocompleteList = (prefix, items) => {
177113
+ if (prefix.startsWith("/")) return new WrappingSelectList(items, this.getAutocompleteMaxVisible(), theme.selectList, SLASH_COMMAND_SELECT_LIST_LAYOUT);
177114
+ return new SelectList(items, this.getAutocompleteMaxVisible(), theme.selectList);
177115
+ };
176129
177116
  }
176130
177117
  expandPasteMarkerAtCursor() {
176131
177118
  const { line, col } = this.getCursor();
@@ -176368,11 +177355,19 @@ function createTerminalState() {
176368
177355
  notificationKeys: /* @__PURE__ */ new Set(),
176369
177356
  focused: true,
176370
177357
  supportsOsc9: supportsOsc9Notification(),
177358
+ supportsProgress: supportsTerminalProgress(),
176371
177359
  insideTmux: isInsideTmux(),
176372
177360
  progressActive: false
176373
177361
  };
176374
177362
  }
176375
177363
  //#endregion
177364
+ //#region src/tui/types.ts
177365
+ const INITIAL_LIVE_PANE = {
177366
+ mode: "idle",
177367
+ pendingApproval: null,
177368
+ pendingQuestion: null
177369
+ };
177370
+ //#endregion
176376
177371
  //#region src/tui/tui-state.ts
176377
177372
  function createTUIState(options) {
176378
177373
  const initialAppState = options.initialAppState;
@@ -176586,6 +177581,15 @@ function sessionRowsForPicker(sessions, currentSessionId, currentSessionHasConte
176586
177581
  }));
176587
177582
  }
176588
177583
  //#endregion
177584
+ //#region src/tui/utils/startup.ts
177585
+ function combineStartupNotice(existing, next) {
177586
+ if (existing !== void 0 && next !== void 0) return `${existing}\n${next}`;
177587
+ return existing ?? next;
177588
+ }
177589
+ function isOAuthLoginRequiredError(error) {
177590
+ return error.code === OAUTH_LOGIN_REQUIRED_CODE;
177591
+ }
177592
+ //#endregion
176589
177593
  //#region src/tui/utils/terminal-focus.ts
176590
177594
  function installTerminalFocusTracking(state) {
176591
177595
  state.terminalState.focused = true;
@@ -176756,7 +177760,8 @@ function createInitialAppState(input) {
176756
177760
  availableProviders: {},
176757
177761
  sessionTitle: null,
176758
177762
  goal: null,
176759
- mcpServersSummary: null
177763
+ mcpServersSummary: null,
177764
+ banner: void 0
176760
177765
  };
176761
177766
  }
176762
177767
  var KimiTUI = class {
@@ -176853,6 +177858,7 @@ var KimiTUI = class {
176853
177858
  const completer = cmd.completeArgs;
176854
177859
  return {
176855
177860
  name: cmd.name,
177861
+ aliases: cmd.aliases,
176856
177862
  description: cmd.description,
176857
177863
  ...cmd.argumentHint !== void 0 ? { argumentHint: cmd.argumentHint } : {},
176858
177864
  ...completer !== void 0 ? { getArgumentCompletions: (prefix) => completer(prefix) } : {}
@@ -176921,10 +177927,28 @@ var KimiTUI = class {
176921
177927
  throw error;
176922
177928
  }
176923
177929
  }
177930
+ async loadBanner() {
177931
+ const provider = new BannerProvider(this.state.appState.version);
177932
+ this.state.appState.banner = await provider.load();
177933
+ if (this.state.appState.banner !== null) {
177934
+ this.renderBanner();
177935
+ this.state.ui.requestRender();
177936
+ }
177937
+ }
177938
+ renderBanner() {
177939
+ if (this.state.appState.banner === null || this.state.appState.banner === void 0) return;
177940
+ if (this.state.transcriptContainer.children.some((child) => child instanceof BannerComponent)) return;
177941
+ const welcomeIndex = this.state.transcriptContainer.children.findIndex((child) => child instanceof WelcomeComponent);
177942
+ const banner = new BannerComponent(this.state.appState.banner);
177943
+ if (welcomeIndex >= 0) this.state.transcriptContainer.children.splice(welcomeIndex + 1, 0, banner);
177944
+ else this.state.transcriptContainer.children.unshift(banner);
177945
+ this.state.transcriptContainer.invalidate();
177946
+ }
176924
177947
  async initMainTui() {
176925
177948
  const shouldReplayHistory = await this.init();
176926
177949
  this.mountFooter();
176927
177950
  this.renderWelcome();
177951
+ this.loadBanner();
176928
177952
  this.setupAutocomplete();
176929
177953
  this.loadPersistedInputHistory();
176930
177954
  this.state.editorContainer.clear();
@@ -176950,10 +177974,8 @@ var KimiTUI = class {
176950
177974
  try {
176951
177975
  const result = await this.authFlow.refreshProviderModels();
176952
177976
  for (const c of result.changed) {
176953
- const parts = [c.providerName];
176954
- if (c.added > 0) parts.push(`+${String(c.added)} model${c.added > 1 ? "s" : ""}`);
176955
- if (c.removed > 0) parts.push(`-${String(c.removed)} model${c.removed > 1 ? "s" : ""}`);
176956
- this.showStatus(parts.join(" · ") + ".");
177977
+ if (c.added <= 0) continue;
177978
+ this.showStatus(`${c.providerName} · +${String(c.added)} model${c.added > 1 ? "s" : ""}.`);
176957
177979
  }
176958
177980
  for (const f of result.failed) this.showStatus(`Skipped refreshing ${f.provider}: ${f.reason}`, "warning");
176959
177981
  } catch {}
@@ -176968,7 +177990,10 @@ var KimiTUI = class {
176968
177990
  this.bootstrapFromPicker();
176969
177991
  return;
176970
177992
  }
176971
- if (shouldReplayHistory) await this.sessionReplay.hydrateFromReplay(this.requireSession());
177993
+ if (shouldReplayHistory) {
177994
+ await this.sessionReplay.hydrateFromReplay(this.requireSession());
177995
+ this.applyStartupPermissionAndPlanToAppState();
177996
+ }
176972
177997
  const resumeState = this.session?.getResumeState();
176973
177998
  if (resumeState?.warning !== void 0) this.showStatus(`Warning: ${resumeState.warning}`, "warning");
176974
177999
  if (this.session !== void 0) this.sessionEventHandler.startSubscription();
@@ -177026,7 +178051,10 @@ var KimiTUI = class {
177026
178051
  }
177027
178052
  }
177028
178053
  } else session = await this.harness.createSession(createSessionOptions);
177029
- if (session !== void 0 && startup.model !== void 0 && isResumeStartup) await session.setModel(startup.model);
178054
+ if (session !== void 0 && shouldReplayHistory) {
178055
+ await this.applyStartupModesToResumedSession(session);
178056
+ if (startup.model !== void 0) await session.setModel(startup.model);
178057
+ }
177030
178058
  } catch (error) {
177031
178059
  if (!isOAuthLoginRequiredError(error)) throw error;
177032
178060
  this.authFlow.enterLoginRequiredStartupState();
@@ -177035,6 +178063,7 @@ var KimiTUI = class {
177035
178063
  if (session === void 0) throw new Error("Startup session was not initialized.");
177036
178064
  await this.setSession(session);
177037
178065
  await this.syncRuntimeState(session);
178066
+ this.applyStartupPermissionAndPlanToAppState();
177038
178067
  this.state.startupState = "ready";
177039
178068
  return shouldReplayHistory;
177040
178069
  }
@@ -177224,10 +178253,11 @@ var KimiTUI = class {
177224
178253
  this.showError(message);
177225
178254
  }
177226
178255
  sendQueuedMessage(session, item) {
177227
- this.harness.interactiveAgentId = item.agentId ?? "main";
177228
- this.sendMessageInternal(session, item.text, {
177229
- parts: item.parts,
177230
- imageAttachmentIds: item.imageAttachmentIds
178256
+ this.harness.withInteractiveAgent(item.agentId ?? "main", () => {
178257
+ this.sendMessageInternal(session, item.text, {
178258
+ parts: item.parts,
178259
+ imageAttachmentIds: item.imageAttachmentIds
178260
+ });
177231
178261
  });
177232
178262
  }
177233
178263
  requestQueuedGoalPromotion() {
@@ -177388,6 +178418,20 @@ var KimiTUI = class {
177388
178418
  goal: goalResult.goal
177389
178419
  });
177390
178420
  }
178421
+ async applyStartupModesToResumedSession(session) {
178422
+ const { startup } = this.options;
178423
+ if (startup.auto) await session.setPermission("auto");
178424
+ else if (startup.yolo) await session.setPermission("yolo");
178425
+ if (startup.plan) {
178426
+ if (!(await session.getStatus()).planMode) await session.setPlanMode(true);
178427
+ }
178428
+ }
178429
+ applyStartupPermissionAndPlanToAppState() {
178430
+ const { startup } = this.options;
178431
+ if (startup.auto) this.setAppState({ permissionMode: "auto" });
178432
+ else if (startup.yolo) this.setAppState({ permissionMode: "yolo" });
178433
+ if (startup.plan) this.setAppState({ planMode: true });
178434
+ }
177391
178435
  async activateRuntime() {
177392
178436
  const session = this.requireSession();
177393
178437
  await session.setPermission(this.state.appState.permissionMode);
@@ -177439,7 +178483,6 @@ var KimiTUI = class {
177439
178483
  this.streamingUI.discardPending();
177440
178484
  this.state.queuedMessages = [];
177441
178485
  this.state.swarmModeEntry = void 0;
177442
- this.harness.interactiveAgentId = MAIN_AGENT_ID;
177443
178486
  this.streamingUI.resetToolCallState();
177444
178487
  this.streamingUI.resetToolUi();
177445
178488
  this.sessionEventHandler.resetRuntimeState();
@@ -177554,12 +178597,21 @@ var KimiTUI = class {
177554
178597
  this.sessionEventHandler.startSubscription();
177555
178598
  this.clearTranscriptAndRedraw();
177556
178599
  this.showStatus(`Started a new session (${session.id}).`);
178600
+ this.showConfigWarningsIfAny();
178601
+ }
178602
+ /** Surface config.toml load warnings (degraded or kept-previous config) in the status bar. */
178603
+ async showConfigWarningsIfAny() {
178604
+ try {
178605
+ const { warnings } = await this.harness.getConfigDiagnostics();
178606
+ for (const warning of warnings) this.showStatus(warning, "warning");
178607
+ } catch {}
177557
178608
  }
177558
178609
  createTranscriptComponent(entry) {
177559
178610
  if (entry.compactionData !== void 0) {
177560
178611
  const data = entry.compactionData;
177561
178612
  const block = new CompactionComponent(this.state.ui, data.instruction);
177562
- block.markDone(data.tokensBefore, data.tokensAfter);
178613
+ if (data.result === "cancelled") block.markCanceled();
178614
+ else block.markDone(data.tokensBefore, data.tokensAfter);
177563
178615
  return block;
177564
178616
  }
177565
178617
  switch (entry.kind) {
@@ -177826,6 +178878,7 @@ var KimiTUI = class {
177826
178878
  this.sessionEventHandler.syncAgentSwarmActivitySpinner(spinner);
177827
178879
  }
177828
178880
  syncTerminalProgress(active) {
178881
+ if (!this.state.terminalState.supportsProgress) return;
177829
178882
  if (this.state.terminalState.progressActive === active) return;
177830
178883
  this.state.terminal.setProgress(active);
177831
178884
  this.state.terminalState.progressActive = active;
@@ -177905,16 +178958,18 @@ var KimiTUI = class {
177905
178958
  }
177906
178959
  async showSessionPicker() {
177907
178960
  await this.fetchSessions();
177908
- this.mountSessionPicker(() => {
178961
+ this.mountSessionPicker({ onCancel: () => {
177909
178962
  this.hideSessionPicker();
177910
- });
178963
+ } });
177911
178964
  }
177912
178965
  async bootstrapFromPicker() {
177913
178966
  await this.fetchSessions();
177914
- this.mountSessionPicker(() => {
177915
- this.hideSessionPicker();
177916
- this.stop();
177917
- }, {
178967
+ this.mountSessionPicker({
178968
+ applyStartupModes: true,
178969
+ onCancel: () => {
178970
+ this.hideSessionPicker();
178971
+ this.stop();
178972
+ },
177918
178973
  onCtrlC: () => {
177919
178974
  this.state.editor.onCtrlC?.();
177920
178975
  },
@@ -177928,20 +178983,27 @@ var KimiTUI = class {
177928
178983
  this.state.activeDialog = null;
177929
178984
  this.restoreEditor();
177930
178985
  }
177931
- mountSessionPicker(onCancel, shortcuts = {}) {
178986
+ mountSessionPicker(options) {
177932
178987
  this.state.activeDialog = "session-picker";
177933
178988
  this.mountEditorReplacement(new SessionPickerComponent({
177934
178989
  sessions: this.state.sessions,
177935
178990
  loading: this.state.loadingSessions,
177936
178991
  currentSessionId: this.state.appState.sessionId,
177937
178992
  onSelect: (sessionId) => {
177938
- this.resumeSession(sessionId).then((switched) => {
177939
- if (switched) this.hideSessionPicker();
178993
+ this.resumeSession(sessionId).then(async (switched) => {
178994
+ if (!switched) return;
178995
+ if (options.applyStartupModes === true) {
178996
+ await this.applyStartupModesToResumedSession(this.requireSession());
178997
+ this.applyStartupPermissionAndPlanToAppState();
178998
+ }
178999
+ this.hideSessionPicker();
179000
+ }).catch((error) => {
179001
+ this.showError(`Failed to apply startup flags: ${formatErrorMessage$2(error)}`);
177940
179002
  });
177941
179003
  },
177942
- onCancel,
177943
- onCtrlC: shortcuts.onCtrlC,
177944
- onCtrlD: shortcuts.onCtrlD
179004
+ onCancel: options.onCancel,
179005
+ onCtrlC: options.onCtrlC,
179006
+ onCtrlD: options.onCtrlD
177945
179007
  }));
177946
179008
  }
177947
179009
  showApprovalPanel(payload) {
@@ -178069,6 +179131,7 @@ async function runShell(opts, version, runOptions = {}) {
178069
179131
  return;
178070
179132
  }
178071
179133
  const config = await harness.getConfig();
179134
+ for (const warning of (await harness.getConfigDiagnostics()).warnings) configWarning = combineStartupNotice(configWarning, warning);
178072
179135
  const configMs = Date.now() - configStartedAt;
178073
179136
  const tui = new KimiTUI(harness, {
178074
179137
  cliOptions: opts,