@moonshot-ai/kimi-code 0.14.0 → 0.14.1

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";
@@ -40,14 +41,13 @@ import { 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
  }
@@ -56376,8 +56446,12 @@ var ConfigState = class {
56376
56446
  return this._modelAlias;
56377
56447
  }
56378
56448
  get thinkingLevel() {
56449
+ if (this._thinkingLevel === "off" && this.alwaysThinkingModel) return resolveThinkingEffort("on", this.agent.kimiConfig?.thinking);
56379
56450
  return this._thinkingLevel;
56380
56451
  }
56452
+ get alwaysThinkingModel() {
56453
+ return this.tryResolvedProviderConfig()?.alwaysThinking === true;
56454
+ }
56381
56455
  get profileName() {
56382
56456
  return this._profileName;
56383
56457
  }
@@ -58024,6 +58098,31 @@ var InjectionManager = class {
58024
58098
  }
58025
58099
  };
58026
58100
  //#endregion
58101
+ //#region ../../packages/agent-core/src/agent/permission/policies/agent-swarm-exclusive-deny.ts
58102
+ var AgentSwarmExclusiveDenyPermissionPolicy = class {
58103
+ name = "agent-swarm-exclusive-deny";
58104
+ evaluate(context) {
58105
+ const toolCalls = context.toolCalls;
58106
+ const agentSwarmCount = toolCalls.filter((toolCall) => toolCall.name === "AgentSwarm").length;
58107
+ if (agentSwarmCount === 0) return;
58108
+ if (agentSwarmCount === 1 && toolCalls.length === 1) return;
58109
+ return {
58110
+ kind: "deny",
58111
+ message: agentSwarmCount > 1 ? multipleAgentSwarmDeniedMessage(toolCalls.length > agentSwarmCount) : mixedAgentSwarmDeniedMessage(),
58112
+ reason: {
58113
+ agent_swarm_tool_calls: agentSwarmCount,
58114
+ tool_calls: toolCalls.length
58115
+ }
58116
+ };
58117
+ }
58118
+ };
58119
+ function multipleAgentSwarmDeniedMessage(hasOtherToolCalls) {
58120
+ 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." : ""}`;
58121
+ }
58122
+ function mixedAgentSwarmDeniedMessage() {
58123
+ 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.";
58124
+ }
58125
+ //#endregion
58027
58126
  //#region ../../packages/agent-core/src/agent/permission/policies/auto-mode-approve.ts
58028
58127
  var AutoModeApprovePermissionPolicy = class {
58029
58128
  agent;
@@ -58466,11 +58565,7 @@ function parseGitDir(content, markerParent) {
58466
58565
  //#endregion
58467
58566
  //#region ../../packages/agent-core/src/agent/permission/policies/file-access-ask.ts
58468
58567
  var SensitiveFileAccessAskPermissionPolicy = class {
58469
- agent;
58470
58568
  name = "sensitive-file-access-ask";
58471
- constructor(agent) {
58472
- this.agent = agent;
58473
- }
58474
58569
  evaluate(context) {
58475
58570
  const access = fileAccesses(context).find((fileAccess) => isSensitiveFile(fileAccess.path));
58476
58571
  if (access === void 0) return;
@@ -60720,6 +60815,7 @@ var YoloModeApprovePermissionPolicy = class {
60720
60815
  function createPermissionDecisionPolicies(agent) {
60721
60816
  return [
60722
60817
  new PreToolCallHookPermissionPolicy(agent),
60818
+ new AgentSwarmExclusiveDenyPermissionPolicy(),
60723
60819
  new AutoModeAskUserQuestionDenyPermissionPolicy(agent),
60724
60820
  new PlanModeGuardDenyPermissionPolicy(agent),
60725
60821
  new UserConfiguredDenyPermissionPolicy(agent),
@@ -60729,7 +60825,7 @@ function createPermissionDecisionPolicies(agent) {
60729
60825
  new UserConfiguredAllowPermissionPolicy(agent),
60730
60826
  new ExitPlanModeReviewAskPermissionPolicy(agent),
60731
60827
  new PlanModeToolApprovePermissionPolicy(agent),
60732
- new SensitiveFileAccessAskPermissionPolicy(agent),
60828
+ new SensitiveFileAccessAskPermissionPolicy(),
60733
60829
  new GitControlPathAccessAskPermissionPolicy(agent),
60734
60830
  new YoloModeApprovePermissionPolicy(agent),
60735
60831
  new SwarmModeAgentSwarmApprovePermissionPolicy(agent),
@@ -62006,12 +62102,16 @@ var AgentRecords = class {
62006
62102
  //#region ../../packages/agent-core/src/agent/replay/index.ts
62007
62103
  var ReplayBuilder = class {
62008
62104
  agent;
62105
+ captureLiveRecords = false;
62009
62106
  records = [];
62010
62107
  constructor(agent) {
62011
62108
  this.agent = agent;
62012
62109
  }
62013
62110
  push(record) {
62014
- if (this.agent.records.restoring) this.records.push(record);
62111
+ if (this.captureLiveRecords || this.agent.records.restoring) this.records.push({
62112
+ ...record,
62113
+ time: this.agent.records.restoring?.time ?? Date.now()
62114
+ });
62015
62115
  }
62016
62116
  removeLastMessages(removedMessages) {
62017
62117
  if (removedMessages.size === 0) return;
@@ -65487,7 +65587,7 @@ function isUserActivatableSkillType(type) {
65487
65587
  return isInlineSkillType(type) || type === "flow";
65488
65588
  }
65489
65589
  function isSupportedSkillType(type) {
65490
- return isUserActivatableSkillType(type);
65590
+ return isUserActivatableSkillType(type) || type === "reference";
65491
65591
  }
65492
65592
  function summarizeSkill(skill) {
65493
65593
  return {
@@ -65698,7 +65798,7 @@ function isRecord$11(value) {
65698
65798
  }
65699
65799
  //#endregion
65700
65800
  //#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";
65801
+ 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
65802
  //#endregion
65703
65803
  //#region ../../packages/agent-core/src/skill/builtin/custom-theme.ts
65704
65804
  const PSEUDO_PATH$3 = "builtin://custom-theme";
@@ -65742,7 +65842,7 @@ const IMPORT_FROM_CC_CODEX_SKILL = {
65742
65842
  };
65743
65843
  //#endregion
65744
65844
  //#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";
65845
+ 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
65846
  //#endregion
65747
65847
  //#region ../../packages/agent-core/src/skill/builtin/mcp-config.ts
65748
65848
  const PSEUDO_PATH$1 = "builtin://mcp-config";
@@ -65799,7 +65899,7 @@ const SUB_SKILL_REVIEW = makeBuiltin(SKILL_default$1, "sub-skill.review", "built
65799
65899
  const SUB_SKILL_CONSOLIDATE = makeBuiltin(SKILL_default$2, "sub-skill.consolidate", "builtin://sub-skill/consolidate", { disableModelInvocation: true });
65800
65900
  //#endregion
65801
65901
  //#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";
65902
+ 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
65903
  //#endregion
65804
65904
  //#region ../../packages/agent-core/src/skill/builtin/update-config.ts
65805
65905
  const PSEUDO_PATH = "builtin://update-config";
@@ -73815,6 +73915,10 @@ function abortedToolOutput(toolName, signal) {
73815
73915
  }
73816
73916
  async function runToolCallBatch(step, response) {
73817
73917
  if (response.toolCalls.length === 0) return { stopTurn: false };
73918
+ const batchStep = {
73919
+ ...step,
73920
+ toolCalls: response.toolCalls
73921
+ };
73818
73922
  const calls = response.toolCalls.map((toolCall) => preflightToolCall(step.tools, toolCall));
73819
73923
  const scheduler = new ToolScheduler();
73820
73924
  const pendingResults = [];
@@ -73822,19 +73926,19 @@ async function runToolCallBatch(step, response) {
73822
73926
  try {
73823
73927
  for (let index = 0; index < calls.length; index += 1) {
73824
73928
  const call = calls[index];
73825
- const prepared = await prepareToolCall(step, call);
73929
+ const prepared = await prepareToolCall(batchStep, call);
73826
73930
  pendingResults.push(scheduler.add(prepared.task));
73827
73931
  if (prepared.stopBatchAfterThis === true) {
73828
73932
  stopTurn = true;
73829
73933
  for (const skippedCall of calls.slice(index + 1)) {
73830
- const skippedTask = await prepareSkippedToolCall(step, skippedCall);
73934
+ const skippedTask = await prepareSkippedToolCall(batchStep, skippedCall);
73831
73935
  pendingResults.push(scheduler.add(skippedTask));
73832
73936
  }
73833
73937
  break;
73834
73938
  }
73835
73939
  }
73836
73940
  for (const pendingResult of pendingResults) {
73837
- const result = await finalizePendingToolResult(step, await pendingResult);
73941
+ const result = await finalizePendingToolResult(batchStep, await pendingResult);
73838
73942
  if (result.stopTurn === true) stopTurn = true;
73839
73943
  await step.dispatchEvent({
73840
73944
  type: "tool.result",
@@ -73989,6 +74093,7 @@ async function runPrepareToolExecutionHook(step, call) {
73989
74093
  try {
73990
74094
  hookResult = await hooks.prepareToolExecution({
73991
74095
  toolCall,
74096
+ toolCalls: step.toolCalls,
73992
74097
  tool: call.tool,
73993
74098
  args,
73994
74099
  turnId,
@@ -74031,6 +74136,7 @@ async function runAuthorizeToolExecutionHook(step, call, args, execution) {
74031
74136
  try {
74032
74137
  return await hooks.authorizeToolExecution({
74033
74138
  toolCall: call.toolCall,
74139
+ toolCalls: step.toolCalls,
74034
74140
  tool: call.tool,
74035
74141
  args,
74036
74142
  execution,
@@ -74086,6 +74192,7 @@ async function finalizePendingToolResult(step, pendingResult) {
74086
74192
  try {
74087
74193
  const effectiveResult = coerceToolResult(await hooks.finalizeToolResult({
74088
74194
  toolCall: pendingResult.toolCall,
74195
+ toolCalls: step.toolCalls,
74089
74196
  args: pendingResult.args,
74090
74197
  result: pendingResult.result,
74091
74198
  turnId,
@@ -81732,7 +81839,7 @@ function buildSubagentDescriptions(subagents) {
81732
81839
  }
81733
81840
  //#endregion
81734
81841
  //#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";
81842
+ 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
81843
  //#endregion
81737
81844
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-swarm.ts
81738
81845
  const DEFAULT_SUBAGENT_TYPE = "coder";
@@ -90238,7 +90345,8 @@ var BashTool = class {
90238
90345
  }, timeoutMs);
90239
90346
  try {
90240
90347
  const builder = new ToolResultBuilder();
90241
- const [, exitCode] = await Promise.all([Promise.all([readStreamIntoBuilder(proc.stdout, builder), readStreamIntoBuilder(proc.stderr, builder)]), proc.wait()]);
90348
+ const isTerminating = () => timedOut || aborted || killed;
90349
+ const [, exitCode] = await Promise.all([Promise.all([readStreamIntoBuilder(proc.stdout, builder, isTerminating), readStreamIntoBuilder(proc.stderr, builder, isTerminating)]), proc.wait()]);
90242
90350
  if (timedOut) {
90243
90351
  const timeoutLabel = timeoutMs % 1e3 === 0 ? `${String(timeoutMs / 1e3)}s` : `${String(timeoutMs)}ms`;
90244
90352
  return builder.error(`Command killed by timeout (${timeoutLabel})`, { brief: `Killed by timeout (${timeoutLabel})` });
@@ -90301,7 +90409,7 @@ var BashTool = class {
90301
90409
  const info = backgroundManager.getTask(taskId);
90302
90410
  if (info && info.status === "running") backgroundManager.stop(taskId, "Timed out");
90303
90411
  })();
90304
- }, timeoutMs);
90412
+ }, timeoutMs).unref?.();
90305
90413
  const status = backgroundManager.getTask(taskId).status;
90306
90414
  const builder = new ToolResultBuilder();
90307
90415
  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,14 +90419,21 @@ human_shell_hint: Tell the human to run /tasks to open the interactive backgroun
90311
90419
  return builder.ok("Background task started", { brief: `Started ${taskId}` });
90312
90420
  }
90313
90421
  };
90314
- async function readStreamIntoBuilder(stream, builder) {
90422
+ async function readStreamIntoBuilder(stream, builder, suppressPrematureClose) {
90315
90423
  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));
90424
+ try {
90425
+ for await (const chunk of stream) {
90426
+ const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
90427
+ builder.write(decoder.write(buf));
90428
+ }
90429
+ } catch (error) {
90430
+ if (!isPrematureCloseError(error) || suppressPrematureClose?.() !== true) throw error;
90319
90431
  }
90320
90432
  builder.write(decoder.end());
90321
90433
  }
90434
+ function isPrematureCloseError(error) {
90435
+ return error instanceof Error && error.code === "ERR_STREAM_PREMATURE_CLOSE";
90436
+ }
90322
90437
  function shellQuote(s) {
90323
90438
  return `'${s.replaceAll("'", "'\\''")}'`;
90324
90439
  }
@@ -121101,6 +121216,32 @@ var McpConnectionManager = class {
121101
121216
  this.initialLoad = initialLoad;
121102
121217
  return initialLoad;
121103
121218
  }
121219
+ async connect(name, config) {
121220
+ const previous = this.entries.get(name);
121221
+ if (previous !== void 0) await this.closeClient(previous);
121222
+ const disabled = config.enabled === false;
121223
+ const entry = {
121224
+ name,
121225
+ config,
121226
+ attemptId: 0,
121227
+ status: disabled ? "disabled" : "pending"
121228
+ };
121229
+ this.entries.set(name, entry);
121230
+ this.emit(entry);
121231
+ if (!disabled) await this.connectOne(entry, this.beginConnectAttempt(entry));
121232
+ }
121233
+ async remove(name) {
121234
+ const entry = this.entries.get(name);
121235
+ if (entry === void 0) return false;
121236
+ await this.closeClient(entry);
121237
+ entry.status = "disabled";
121238
+ entry.tools = void 0;
121239
+ entry.enabledNames = void 0;
121240
+ entry.error = void 0;
121241
+ this.emit(entry);
121242
+ this.entries.delete(name);
121243
+ return true;
121244
+ }
121104
121245
  waitForInitialLoad(signal) {
121105
121246
  signal?.throwIfAborted();
121106
121247
  if (signal === void 0) return this.initialLoad;
@@ -121450,6 +121591,20 @@ function mergeCallerMcpServers(base, callerServers) {
121450
121591
  //#endregion
121451
121592
  //#region ../../packages/agent-core/src/session/index.ts
121452
121593
  const BACKGROUND_KEEP_ALIVE_ON_EXIT_ENV = "KIMI_CODE_BACKGROUND_KEEP_ALIVE_ON_EXIT";
121594
+ const ACTIVE_TURN_CLOSE_TIMEOUT_MS = 8e3;
121595
+ async function waitForSettlementOrTimeout(promise, timeoutMs) {
121596
+ let timeout;
121597
+ try {
121598
+ return await Promise.race([promise.then(() => true, () => true), new Promise((resolve) => {
121599
+ timeout = setTimeout(() => {
121600
+ resolve(false);
121601
+ }, timeoutMs);
121602
+ timeout.unref?.();
121603
+ })]);
121604
+ } finally {
121605
+ if (timeout !== void 0) clearTimeout(timeout);
121606
+ }
121607
+ }
121453
121608
  var Session$1 = class {
121454
121609
  options;
121455
121610
  rpc;
@@ -121541,6 +121696,7 @@ var Session$1 = class {
121541
121696
  async close() {
121542
121697
  try {
121543
121698
  await Promise.allSettled(Array.from(this.readyAgents(), async (agent) => agent.cron?.stop()));
121699
+ await this.cancelActiveTurnsOnClose();
121544
121700
  await this.stopBackgroundTasksOnExit();
121545
121701
  await this.flushMetadata();
121546
121702
  await this.triggerSessionEnd("exit");
@@ -121564,12 +121720,46 @@ var Session$1 = class {
121564
121720
  }
121565
121721
  }
121566
121722
  }
121723
+ async cancelActiveTurnsOnClose() {
121724
+ const backgroundAgentIds = this.activeBackgroundAgentIds();
121725
+ const cancellations = [];
121726
+ for (const [agentId, entry] of this.agents) {
121727
+ if (!(entry instanceof Agent$1) || backgroundAgentIds.has(agentId)) continue;
121728
+ cancellations.push(this.cancelAgentTurnOnClose(entry));
121729
+ }
121730
+ await Promise.allSettled(cancellations);
121731
+ }
121732
+ activeBackgroundAgentIds() {
121733
+ const agentIds = /* @__PURE__ */ new Set();
121734
+ 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);
121735
+ return agentIds;
121736
+ }
121737
+ async cancelAgentTurnOnClose(agent) {
121738
+ if (!agent.turn.hasActiveTurn) return;
121739
+ let waitForTurn;
121740
+ try {
121741
+ waitForTurn = agent.turn.waitForCurrentTurn();
121742
+ } catch (error) {
121743
+ this.log.debug("active turn wait unavailable during session close", {
121744
+ agentType: agent.type,
121745
+ agentHomedir: agent.homedir,
121746
+ error
121747
+ });
121748
+ return;
121749
+ }
121750
+ agent.turn.cancel(void 0, abortError$1("Session closed"));
121751
+ if (!await waitForSettlementOrTimeout(waitForTurn, ACTIVE_TURN_CLOSE_TIMEOUT_MS)) this.log.warn("timed out waiting for active turn to cancel during session close", {
121752
+ agentType: agent.type,
121753
+ agentHomedir: agent.homedir,
121754
+ timeoutMs: ACTIVE_TURN_CLOSE_TIMEOUT_MS
121755
+ });
121756
+ }
121567
121757
  async stopBackgroundTasksOnExit() {
121568
121758
  if (resolveConfigValue({
121569
121759
  env: process.env,
121570
121760
  envKey: BACKGROUND_KEEP_ALIVE_ON_EXIT_ENV,
121571
121761
  configValue: this.options.background?.keepAliveOnExit,
121572
- defaultValue: true,
121762
+ defaultValue: false,
121573
121763
  parseEnv: parseBooleanEnv
121574
121764
  })) return;
121575
121765
  await Promise.all(Array.from(this.readyAgents(), (agent) => agent.background.stopAll("Session closed")));
@@ -136951,6 +137141,7 @@ var ProviderManager = class {
136951
137141
  providerName,
136952
137142
  provider,
136953
137143
  modelCapabilities: resolveModelCapabilities(alias, provider),
137144
+ alwaysThinking: (alias.capabilities ?? []).some((c) => c.trim().toLowerCase() === "always_thinking"),
136954
137145
  maxOutputSize: alias.maxOutputSize
136955
137146
  };
136956
137147
  }
@@ -138335,9 +138526,11 @@ var LocalKaos = class LocalKaos {
138335
138526
  name = "local";
138336
138527
  osEnv;
138337
138528
  _cwd;
138338
- constructor(osEnv, cwd) {
138529
+ _envLayers;
138530
+ constructor(osEnv, cwd, envLayers = []) {
138339
138531
  this._cwd = normalize(cwd ?? process.cwd());
138340
138532
  this.osEnv = osEnv;
138533
+ this._envLayers = envLayers;
138341
138534
  }
138342
138535
  /**
138343
138536
  * Construct a fresh `LocalKaos` after probing the host environment.
@@ -138350,7 +138543,10 @@ var LocalKaos = class LocalKaos {
138350
138543
  return new LocalKaos(await detectEnvironmentFromNode());
138351
138544
  }
138352
138545
  withCwd(cwd) {
138353
- return new LocalKaos(this.osEnv, cwd);
138546
+ return new LocalKaos(this.osEnv, cwd, this._envLayers);
138547
+ }
138548
+ withEnv(env) {
138549
+ return new LocalKaos(this.osEnv, this._cwd, [...this._envLayers, env]);
138354
138550
  }
138355
138551
  _resolvePath(path) {
138356
138552
  if (isAbsolute(path)) return normalize(path);
@@ -138543,7 +138739,8 @@ var LocalKaos = class LocalKaos {
138543
138739
  "pipe",
138544
138740
  "pipe"
138545
138741
  ],
138546
- detached: !isWindows
138742
+ detached: !isWindows,
138743
+ env: this._buildExecEnv()
138547
138744
  });
138548
138745
  await waitForSpawn(child);
138549
138746
  return new LocalProcess(child);
@@ -138559,11 +138756,20 @@ var LocalKaos = class LocalKaos {
138559
138756
  "pipe"
138560
138757
  ],
138561
138758
  detached: !isWindows,
138562
- env
138759
+ env: this._buildExecEnv(env)
138563
138760
  });
138564
138761
  await waitForSpawn(child);
138565
138762
  return new LocalProcess(child);
138566
138763
  }
138764
+ _buildExecEnv(invocationEnv) {
138765
+ if (this._envLayers.length === 0) return invocationEnv;
138766
+ const merged = {
138767
+ ...process.env,
138768
+ ...invocationEnv
138769
+ };
138770
+ for (const layer of this._envLayers) Object.assign(merged, layer);
138771
+ return merged;
138772
+ }
138567
138773
  };
138568
138774
  function waitForSpawn(child) {
138569
138775
  return new Promise((resolve, reject) => {
@@ -139649,8 +139855,8 @@ var KimiHarness = class {
139649
139855
  get interactiveAgentId() {
139650
139856
  return this.rpc.interactiveAgentId;
139651
139857
  }
139652
- set interactiveAgentId(agentId) {
139653
- this.rpc.interactiveAgentId = agentId;
139858
+ withInteractiveAgent(agentId, fn) {
139859
+ return this.rpc.withInteractiveAgent(agentId, fn);
139654
139860
  }
139655
139861
  track(event, properties) {
139656
139862
  this.telemetry.track(event, properties);
@@ -142102,7 +142308,19 @@ function managedModelKey(modelId) {
142102
142308
  }
142103
142309
  function capabilitiesForModel$1(model) {
142104
142310
  const caps = /* @__PURE__ */ new Set();
142105
- if (model.supportsReasoning) caps.add("thinking");
142311
+ switch (model.supportsThinkingType) {
142312
+ case "only":
142313
+ caps.add("thinking");
142314
+ caps.add("always_thinking");
142315
+ break;
142316
+ case "both":
142317
+ caps.add("thinking");
142318
+ break;
142319
+ case "no": break;
142320
+ case void 0:
142321
+ if (model.supportsReasoning) caps.add("thinking");
142322
+ break;
142323
+ }
142106
142324
  if (model.supportsImageIn) caps.add("image_in");
142107
142325
  if (model.supportsVideoIn) caps.add("video_in");
142108
142326
  if (model.supportsToolUse ?? true) caps.add("tool_use");
@@ -142245,9 +142463,13 @@ function toModelInfo$1(item) {
142245
142463
  supportsImageIn: Boolean(item["supports_image_in"]),
142246
142464
  supportsVideoIn: Boolean(item["supports_video_in"]),
142247
142465
  supportsToolUse,
142466
+ supportsThinkingType: parseSupportsThinkingType(item["supports_thinking_type"]),
142248
142467
  displayName: normalizedDisplayName
142249
142468
  };
142250
142469
  }
142470
+ function parseSupportsThinkingType(value) {
142471
+ return value === "only" || value === "no" || value === "both" ? value : void 0;
142472
+ }
142251
142473
  async function fetchManagedKimiCodeModels(options) {
142252
142474
  const fetchImpl = options.fetchImpl ?? fetch;
142253
142475
  const baseUrl = defaultBaseUrl(options.baseUrl);
@@ -142336,6 +142558,11 @@ function applyManagedKimiCodeLogoutConfig(config) {
142336
142558
  if (Object.keys(config.services).length === 0) config.services = void 0;
142337
142559
  }
142338
142560
  }
142561
+ function forcedThinking(model, fallback) {
142562
+ if (model?.supportsThinkingType === "only") return true;
142563
+ if (model?.supportsThinkingType === "no") return false;
142564
+ return fallback;
142565
+ }
142339
142566
  function selectDefaultModel(config, models, options) {
142340
142567
  const firstModel = models[0];
142341
142568
  if (firstModel === void 0) throw new Error("No models available for Kimi Code.");
@@ -142346,12 +142573,12 @@ function selectDefaultModel(config, models, options) {
142346
142573
  const preservedModel = managedModels.get(currentDefault);
142347
142574
  return {
142348
142575
  modelKey: currentDefault,
142349
- thinking: config.defaultThinking ?? preservedModel?.supportsReasoning ?? false
142576
+ thinking: forcedThinking(preservedModel, config.defaultThinking ?? preservedModel?.supportsReasoning ?? false)
142350
142577
  };
142351
142578
  }
142352
142579
  return {
142353
142580
  modelKey: managedModelKey(firstModel.id),
142354
- thinking: config.defaultThinking ?? firstModel.supportsReasoning
142581
+ thinking: forcedThinking(firstModel, config.defaultThinking ?? firstModel.supportsReasoning)
142355
142582
  };
142356
142583
  }
142357
142584
  function canPreserveDefaultModel(existingModels, defaultModel, managedModels) {
@@ -142463,12 +142690,25 @@ function toModelInfo(item) {
142463
142690
  supportsImageIn: Boolean(item["supports_image_in"]),
142464
142691
  supportsVideoIn: Boolean(item["supports_video_in"]),
142465
142692
  supportsToolUse,
142693
+ supportsThinkingType: parseSupportsThinkingType(item["supports_thinking_type"]),
142466
142694
  displayName: normalizedDisplayName
142467
142695
  };
142468
142696
  }
142469
142697
  function capabilitiesForModel(model) {
142470
142698
  const caps = /* @__PURE__ */ new Set();
142471
- if (model.supportsReasoning) caps.add("thinking");
142699
+ switch (model.supportsThinkingType) {
142700
+ case "only":
142701
+ caps.add("thinking");
142702
+ caps.add("always_thinking");
142703
+ break;
142704
+ case "both":
142705
+ caps.add("thinking");
142706
+ break;
142707
+ case "no": break;
142708
+ case void 0:
142709
+ if (model.supportsReasoning) caps.add("thinking");
142710
+ break;
142711
+ }
142472
142712
  if (model.supportsImageIn) caps.add("image_in");
142473
142713
  if (model.supportsVideoIn) caps.add("video_in");
142474
142714
  if (model.supportsToolUse ?? true) caps.add("tool_use");
@@ -143100,10 +143340,16 @@ var KimiAuthFacade = class {
143100
143340
  //#region ../../packages/node-sdk/src/rpc.ts
143101
143341
  const MAIN_AGENT_ID$2 = "main";
143102
143342
  var SDKRpcClientBase = class {
143103
- interactiveAgentId = MAIN_AGENT_ID$2;
143343
+ interactiveAgentScope = new AsyncLocalStorage();
143104
143344
  eventListeners = /* @__PURE__ */ new Set();
143105
143345
  approvalHandlers = /* @__PURE__ */ new Map();
143106
143346
  questionHandlers = /* @__PURE__ */ new Map();
143347
+ get interactiveAgentId() {
143348
+ return this.interactiveAgentScope.getStore() ?? MAIN_AGENT_ID$2;
143349
+ }
143350
+ withInteractiveAgent(agentId, fn) {
143351
+ return this.interactiveAgentScope.run(agentId, fn);
143352
+ }
143107
143353
  async createSession(input) {
143108
143354
  const rpc = await this.getRpc();
143109
143355
  const { planMode, ...coreInput } = input;
@@ -144401,6 +144647,7 @@ const FEEDBACK_VERSION_PREFIX = "kimi-code-";
144401
144647
  const FEEDBACK_TELEMETRY_EVENT = "feedback_submitted";
144402
144648
  const KIMI_CODE_CDN_BASE = "https://code.kimi.com/kimi-code";
144403
144649
  const KIMI_CODE_CDN_LATEST_URL = `${KIMI_CODE_CDN_BASE}/latest`;
144650
+ const KIMI_CODE_TIPS_BANNER_URL = "https://cdn.kimi.com/kimi-code-tips/tips.json";
144404
144651
  const KIMI_CODE_PLUGIN_MARKETPLACE_URL = `${KIMI_CODE_CDN_BASE}/plugins/marketplace.json`;
144405
144652
  const KIMI_CODE_PLUGIN_MARKETPLACE_URL_ENV = "KIMI_CODE_PLUGIN_MARKETPLACE_URL";
144406
144653
  const KIMI_CODE_INSTALL_SH_URL = `${KIMI_CODE_CDN_BASE}/install.sh`;
@@ -150542,6 +150789,9 @@ var AcpKaos = class AcpKaos {
150542
150789
  withCwd(cwd) {
150543
150790
  return new AcpKaos(this.conn, this.sessionId, this.inner.withCwd(cwd));
150544
150791
  }
150792
+ withEnv(env) {
150793
+ return new AcpKaos(this.conn, this.sessionId, this.inner.withEnv(env));
150794
+ }
150545
150795
  stat(path, options) {
150546
150796
  return this.inner.stat(path, options);
150547
150797
  }
@@ -150562,13 +150812,14 @@ var AcpKaos = class AcpKaos {
150562
150812
  * should be routed through a non-ACP Kaos.
150563
150813
  */
150564
150814
  async readText(path, _options) {
150815
+ const rpcPath = this.toClientPath(path);
150565
150816
  try {
150566
150817
  return (await this.conn.readTextFile({
150567
150818
  sessionId: this.sessionId,
150568
- path
150819
+ path: rpcPath
150569
150820
  })).content;
150570
150821
  } catch (err) {
150571
- throw wrapKaosError(`acp: readTextFile failed for ${path}`, err);
150822
+ throw wrapKaosError(`acp: readTextFile failed for ${rpcPath}`, err);
150572
150823
  }
150573
150824
  }
150574
150825
  /**
@@ -150646,16 +150897,21 @@ var AcpKaos = class AcpKaos {
150646
150897
  return data.byteLength;
150647
150898
  }
150648
150899
  async acpWrite(path, content) {
150900
+ const rpcPath = this.toClientPath(path);
150649
150901
  try {
150650
150902
  await this.conn.writeTextFile({
150651
150903
  sessionId: this.sessionId,
150652
- path,
150904
+ path: rpcPath,
150653
150905
  content
150654
150906
  });
150655
150907
  } catch (err) {
150656
- throw wrapKaosError(`acp: writeTextFile failed for ${path}`, err);
150908
+ throw wrapKaosError(`acp: writeTextFile failed for ${rpcPath}`, err);
150657
150909
  }
150658
150910
  }
150911
+ toClientPath(path) {
150912
+ if (this.inner.pathClass() !== "win32") return path;
150913
+ return path.replaceAll("/", "\\");
150914
+ }
150659
150915
  exec(...args) {
150660
150916
  return this.inner.exec(...args);
150661
150917
  }
@@ -151702,13 +151958,24 @@ function acpModeToToggles(id) {
151702
151958
  */
151703
151959
  const TOGGLEABLE_THINKING_MODELS = new Set(["kimi-for-coding", "kimi-code"]);
151704
151960
  function deriveThinkingSupported(alias) {
151705
- if ((alias.capabilities ?? []).includes("thinking")) return true;
151961
+ const declared = alias.capabilities ?? [];
151962
+ if (declared.includes("thinking") || declared.includes("always_thinking")) return true;
151706
151963
  const lower = alias.model.toLowerCase();
151707
151964
  if (lower.includes("thinking") || lower.includes("reason")) return true;
151708
151965
  if (TOGGLEABLE_THINKING_MODELS.has(alias.model)) return true;
151709
151966
  return false;
151710
151967
  }
151711
151968
  /**
151969
+ * Whether the alias declares the 'always_thinking' capability — the model
151970
+ * cannot run with thinking disabled, so the ACP toggle must lock to on.
151971
+ * Deliberately capability-only: the name heuristics above keep feeding
151972
+ * `thinkingSupported`, but only an explicit (server-derived) declaration
151973
+ * may remove the off option from the client.
151974
+ */
151975
+ function deriveAlwaysThinking(alias) {
151976
+ return (alias.capabilities ?? []).includes("always_thinking");
151977
+ }
151978
+ /**
151712
151979
  * Project `harness.getConfig().models` into a flat catalog. Returns an
151713
151980
  * empty array when the harness has no models configured, when
151714
151981
  * `getConfig` is missing on the harness (partial test stubs), or when
@@ -151729,7 +151996,8 @@ async function listModelsFromHarness(harness) {
151729
151996
  for (const [id, alias] of Object.entries(models)) out.push({
151730
151997
  id,
151731
151998
  name: alias.displayName ?? alias.model ?? id,
151732
- thinkingSupported: deriveThinkingSupported(alias)
151999
+ thinkingSupported: deriveThinkingSupported(alias),
152000
+ alwaysThinking: deriveAlwaysThinking(alias)
151733
152001
  });
151734
152002
  return out;
151735
152003
  }
@@ -151788,8 +152056,26 @@ function buildModelOption(models, currentBaseModelId) {
151788
152056
  * currently-selected model has `thinkingSupported === false`, the
151789
152057
  * snapshot omits it entirely (dynamic visibility), so the client never
151790
152058
  * shows a toggle that wouldn't do anything.
152059
+ *
152060
+ * `alwaysThinking` models (declared `always_thinking` capability — the
152061
+ * runtime cannot disable thinking) collapse the select to a single
152062
+ * locked `on` entry: the state stays visible to the client, but there
152063
+ * is no off option to pick. ACP has no "disabled entry" concept, so
152064
+ * omitting `off` is the wire-level equivalent of the TUI's greyed-out
152065
+ * `Off (Unsupported)` segment.
151791
152066
  */
151792
- function buildThinkingOption(enabled) {
152067
+ function buildThinkingOption(enabled, alwaysThinking = false) {
152068
+ if (alwaysThinking) return {
152069
+ type: "select",
152070
+ id: "thinking",
152071
+ name: "Thinking",
152072
+ category: "thought_level",
152073
+ currentValue: "on",
152074
+ options: [{
152075
+ value: "on",
152076
+ name: "Thinking On"
152077
+ }]
152078
+ };
151793
152079
  return {
151794
152080
  type: "select",
151795
152081
  id: "thinking",
@@ -151856,9 +152142,11 @@ function buildModeOption(currentModeId) {
151856
152142
  */
151857
152143
  async function buildSessionConfigOptions(harness, currentBaseModelId, currentThinkingEnabled, currentModeId) {
151858
152144
  const models = await listModelsFromHarness(harness);
151859
- const showThinking = models.find((m) => m.id === currentBaseModelId)?.thinkingSupported === true;
152145
+ const currentModelEntry = models.find((m) => m.id === currentBaseModelId);
152146
+ const showThinking = currentModelEntry?.thinkingSupported === true;
152147
+ const alwaysThinking = currentModelEntry?.alwaysThinking === true;
151860
152148
  const out = [buildModelOption(models, currentBaseModelId)];
151861
- if (showThinking) out.push(buildThinkingOption(currentThinkingEnabled));
152149
+ if (showThinking) out.push(buildThinkingOption(alwaysThinking || currentThinkingEnabled, alwaysThinking));
151862
152150
  out.push(buildModeOption(currentModeId));
151863
152151
  return out;
151864
152152
  }
@@ -152204,11 +152492,25 @@ var AcpSession = class {
152204
152492
  * carries a fresh snapshot.
152205
152493
  */
152206
152494
  async setThinking(enabled) {
152495
+ if (!enabled && await this.currentModelAlwaysThinking()) {
152496
+ this.currentThinkingEnabledInternal = true;
152497
+ await this.emitConfigOptionUpdate();
152498
+ return;
152499
+ }
152207
152500
  if (typeof this.session.setThinking === "function") await this.session.setThinking(enabled ? THINKING_ON_LEVEL : THINKING_OFF_LEVEL);
152208
152501
  this.currentThinkingEnabledInternal = enabled;
152209
152502
  await this.emitConfigOptionUpdate();
152210
152503
  }
152211
152504
  /**
152505
+ * Whether the currently-selected model declares 'always_thinking'.
152506
+ * Harness-less adapter unit tests resolve to false — the agent-core
152507
+ * runtime clamp still protects the actual request in that case.
152508
+ */
152509
+ async currentModelAlwaysThinking() {
152510
+ if (!this.harness) return false;
152511
+ return (await listModelsFromHarness(this.harness)).find((m) => m.id === this.currentModelIdInternal)?.alwaysThinking === true;
152512
+ }
152513
+ /**
152212
152514
  * Forward an ACP `session/set_mode` request to the underlying SDK
152213
152515
  * session.
152214
152516
  *
@@ -155413,7 +155715,6 @@ const LLM_NOT_SET_MESSAGE = "LLM not set, send \"/login\" to login";
155413
155715
  const NO_ACTIVE_SESSION_MESSAGE = "No active session. Send /login to login.";
155414
155716
  const CTRL_D_HINT = "Press Ctrl+D again to exit";
155415
155717
  const CTRL_C_HINT = "Press Ctrl+C again to exit";
155416
- const MAIN_AGENT_ID = "main";
155417
155718
  const OAUTH_LOGIN_REQUIRED_STARTUP_NOTICE = "OAuth login expired. Send /login to login.";
155418
155719
  const EXIT_CONFIRM_WINDOW_MS = 1500;
155419
155720
  function isManagedUsageProvider(providerKey) {
@@ -155774,7 +156075,7 @@ var SearchableList = class {
155774
156075
  * addChild(picker), setFocus(picker); the picker invokes `onSelect` or
155775
156076
  * `onCancel`, and the host tears it down.
155776
156077
  */
155777
- function wrapDescription(text, width) {
156078
+ function wrapDescription$1(text, width) {
155778
156079
  const maxWidth = Math.max(1, width);
155779
156080
  const words = text.trim().split(/\s+/).filter((word) => word.length > 0);
155780
156081
  const lines = [];
@@ -155859,7 +156160,7 @@ var ChoicePickerComponent = class extends Container {
155859
156160
  lines.push(line);
155860
156161
  if (opt.description !== void 0 && opt.description.length > 0) {
155861
156162
  const descriptionWidth = Math.max(1, width - 4);
155862
- for (const descLine of wrapDescription(opt.description, descriptionWidth)) lines.push(currentTheme.fg("textMuted", ` ${descLine}`));
156163
+ for (const descLine of wrapDescription$1(opt.description, descriptionWidth)) lines.push(currentTheme.fg("textMuted", ` ${descLine}`));
155863
156164
  }
155864
156165
  }
155865
156166
  lines.push("");
@@ -156130,9 +156431,10 @@ var ModelSelectorComponent = class extends Container {
156130
156431
  }
156131
156432
  renderThinkingControl(choice) {
156132
156433
  const segment = (label, active) => active ? currentTheme.boldFg("primary", `[ ${label} ]`) : currentTheme.fg("text", ` ${label} `);
156434
+ const unavailable = (label) => currentTheme.fg("textMuted", ` ${label} (Unsupported) `);
156133
156435
  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")}`;
156436
+ if (availability === "always-on") return ` ${segment("On", true)} ${unavailable("Off")}`;
156437
+ if (availability === "unsupported") return ` ${unavailable("On")} ${segment("Off", true)}`;
156136
156438
  const draft = this.draftFor(choice);
156137
156439
  return ` ${segment("On", draft)} ${segment("Off", !draft)}`;
156138
156440
  }
@@ -156651,7 +156953,7 @@ var EditorSelectorComponent = class extends ChoicePickerComponent {
156651
156953
  };
156652
156954
  //#endregion
156653
156955
  //#region src/tui/components/dialogs/experiments-selector.ts
156654
- const ELLIPSIS$7 = "…";
156956
+ const ELLIPSIS$8 = "…";
156655
156957
  var ExperimentsSelectorComponent = class extends Container {
156656
156958
  focused = false;
156657
156959
  opts;
@@ -156710,7 +157012,7 @@ var ExperimentsSelectorComponent = class extends Container {
156710
157012
  else if (view.page.end < view.items.length) lines.push(currentTheme.fg("textMuted", ` ▼ ${String(view.items.length - view.page.end)} more`));
156711
157013
  lines.push(this.renderApplyButton());
156712
157014
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
156713
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$7));
157015
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$8));
156714
157016
  }
156715
157017
  toggleDraft(feature) {
156716
157018
  if (isLocked(feature)) return;
@@ -156783,7 +157085,7 @@ function wrapText(text, width) {
156783
157085
  continue;
156784
157086
  }
156785
157087
  if (current.length > 0) lines.push(current);
156786
- current = visibleWidth(word) <= maxWidth ? word : truncateToWidth(word, maxWidth, ELLIPSIS$7);
157088
+ current = visibleWidth(word) <= maxWidth ? word : truncateToWidth(word, maxWidth, ELLIPSIS$8);
156787
157089
  }
156788
157090
  if (current.length > 0) lines.push(current);
156789
157091
  return lines;
@@ -158154,7 +158456,7 @@ var GoalStartPermissionPromptComponent = class extends StartPermissionPromptComp
158154
158456
  //#region src/tui/components/dialogs/goal-queue-manager.ts
158155
158457
  const MAX_GOAL_OBJECTIVE_LENGTH$2 = 4e3;
158156
158458
  const MAX_EDIT_INPUT_LINES = 8;
158157
- const ELLIPSIS$6 = "…";
158459
+ const ELLIPSIS$7 = "…";
158158
158460
  const BRACKET_PASTE_START$1 = "\x1B[200~";
158159
158461
  const BRACKET_PASTE_END$1 = "\x1B[201~";
158160
158462
  const SHIFT_ENTER_LEGACY = "\x1B\r";
@@ -158244,7 +158546,7 @@ var GoalQueueManagerComponent = class extends Container {
158244
158546
  }
158245
158547
  lines.push("");
158246
158548
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
158247
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$6));
158549
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$7));
158248
158550
  }
158249
158551
  renderGoal(goal, index, selected, width) {
158250
158552
  const moving = goal.id === this.movingGoalId;
@@ -158255,7 +158557,7 @@ var GoalQueueManagerComponent = class extends Container {
158255
158557
  const labelWidth = visibleWidth(labelPrefix);
158256
158558
  const stateWidth = visibleWidth(stateLabel);
158257
158559
  const objectiveWidth = Math.max(1, width - 5 - labelWidth - stateWidth);
158258
- const objective = truncateToWidth(formatListObjective(goal.objective), objectiveWidth, ELLIPSIS$6);
158560
+ const objective = truncateToWidth(formatListObjective(goal.objective), objectiveWidth, ELLIPSIS$7);
158259
158561
  let line = prefix + (selected ? (text) => currentTheme.boldFg("primary", text) : (text) => currentTheme.fg("text", text))(labelPrefix + objective);
158260
158562
  if (moving) line += currentTheme.fg("success", stateLabel);
158261
158563
  return line;
@@ -158326,10 +158628,10 @@ var GoalQueueEditDialogComponent = class extends Container {
158326
158628
  const innerWidth = Math.max(10, safeWidth - 4);
158327
158629
  const pad = " ";
158328
158630
  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);
158631
+ const title = truncateToWidth(currentTheme.boldFg("textStrong", "Edit upcoming goal"), innerWidth, ELLIPSIS$7);
158632
+ const subtitle = truncateToWidth(currentTheme.fg(this.error === void 0 ? "textDim" : "warning", this.error ?? "Update the queued objective."), innerWidth, ELLIPSIS$7);
158331
158633
  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);
158634
+ const footer = truncateToWidth(currentTheme.fg("textDim", "Enter submit · Shift-Enter/Ctrl-J newline · Esc cancel"), innerWidth, ELLIPSIS$7);
158333
158635
  const contentLines = [
158334
158636
  title,
158335
158637
  "",
@@ -158435,14 +158737,14 @@ var MultilineGoalInput = class {
158435
158737
  const cursor = this.cursorLocation();
158436
158738
  const range = visibleLineRange(logicalLines.length, cursor.line);
158437
158739
  const rendered = [];
158438
- if (range.start > 0) rendered.push(padInputLine(` ${ELLIPSIS$6} ${String(range.start)} previous`, safeWidth));
158740
+ if (range.start > 0) rendered.push(padInputLine(` ${ELLIPSIS$7} ${String(range.start)} previous`, safeWidth));
158439
158741
  for (let lineIndex = range.start; lineIndex < range.end; lineIndex++) {
158440
158742
  const line = logicalLines[lineIndex] ?? "";
158441
158743
  const prefix = lineIndex === 0 ? "> " : " ";
158442
158744
  rendered.push(lineIndex === cursor.line ? renderCursorLine(line, cursor.column, prefix, safeWidth, this.focused) : renderTextLine(line, prefix, safeWidth));
158443
158745
  }
158444
158746
  const remaining = logicalLines.length - range.end;
158445
- if (remaining > 0) rendered.push(padInputLine(` ${ELLIPSIS$6} ${String(remaining)} more`, safeWidth));
158747
+ if (remaining > 0) rendered.push(padInputLine(` ${ELLIPSIS$7} ${String(remaining)} more`, safeWidth));
158446
158748
  return rendered;
158447
158749
  }
158448
158750
  insert(text) {
@@ -158582,7 +158884,7 @@ function visibleLineRange(totalLines, cursorLine) {
158582
158884
  }
158583
158885
  function renderTextLine(line, prefix, width) {
158584
158886
  const prefixWidth = visibleWidth(prefix);
158585
- return padInputLine(prefix + truncateToWidth(line, Math.max(1, width - prefixWidth), ELLIPSIS$6), width);
158887
+ return padInputLine(prefix + truncateToWidth(line, Math.max(1, width - prefixWidth), ELLIPSIS$7), width);
158586
158888
  }
158587
158889
  function renderCursorLine(line, column, prefix, width, focused) {
158588
158890
  const prefixWidth = visibleWidth(prefix);
@@ -160150,7 +160452,7 @@ const OVERVIEW_PLUGIN_PREFIX = "plugin:";
160150
160452
  const MCP_SERVER_PREFIX = "mcp:";
160151
160453
  const REMOVE_CONFIRM_CANCEL = "cancel";
160152
160454
  const REMOVE_CONFIRM_REMOVE = "remove";
160153
- const ELLIPSIS$5 = "…";
160455
+ const ELLIPSIS$6 = "…";
160154
160456
  var PluginsOverviewSelectorComponent = class extends Container {
160155
160457
  focused = false;
160156
160458
  opts;
@@ -160243,7 +160545,7 @@ var PluginsOverviewSelectorComponent = class extends Container {
160243
160545
  for (let i = 0; i < actionItems.length; i++) lines.push(...this.renderItem(actionItems[i], pluginItems.length + i, width));
160244
160546
  lines.push("");
160245
160547
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
160246
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$5));
160548
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$6));
160247
160549
  }
160248
160550
  renderItem(item, index, width) {
160249
160551
  const selected = index === this.selectedIndex;
@@ -160315,7 +160617,7 @@ var PluginMarketplaceSelectorComponent = class extends Container {
160315
160617
  for (let i = 0; i < actions.length; i++) lines.push(...this.renderItem(actions[i], entries.length + i, width));
160316
160618
  lines.push("");
160317
160619
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
160318
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$5));
160620
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$6));
160319
160621
  }
160320
160622
  renderItem(item, index, width) {
160321
160623
  const selected = index === this.selectedIndex;
@@ -160394,7 +160696,7 @@ var PluginMcpSelectorComponent = class extends Container {
160394
160696
  for (let i = 0; i < actionItems.length; i++) lines.push(...this.renderItem(actionItems[i], serverItems.length + i, width));
160395
160697
  lines.push("");
160396
160698
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
160397
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$5));
160699
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$6));
160398
160700
  }
160399
160701
  renderItem(item, index, width) {
160400
160702
  const selected = index === this.selectedIndex;
@@ -160574,7 +160876,7 @@ function wrapOverviewDescription(text, width) {
160574
160876
  continue;
160575
160877
  }
160576
160878
  if (current.length > 0) lines.push(current);
160577
- current = visibleWidth(word) <= maxWidth ? word : truncateToWidth(word, maxWidth, ELLIPSIS$5);
160879
+ current = visibleWidth(word) <= maxWidth ? word : truncateToWidth(word, maxWidth, ELLIPSIS$6);
160578
160880
  }
160579
160881
  if (current.length > 0) lines.push(current);
160580
160882
  return lines;
@@ -161065,15 +161367,23 @@ function applyRuntimeConfig(host, config) {
161065
161367
  }
161066
161368
  //#endregion
161067
161369
  //#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
- }];
161370
+ const OPTIONS = [
161371
+ {
161372
+ value: "auto",
161373
+ label: "Switch to Auto and start",
161374
+ description: "Best for swarm tasks. Tools are approved automatically, and questions are skipped."
161375
+ },
161376
+ {
161377
+ value: "yolo",
161378
+ label: "Switch to YOLO and start",
161379
+ description: "Tools and plan changes are approved automatically. Kimi Code may still ask you questions."
161380
+ },
161381
+ {
161382
+ value: "manual",
161383
+ label: "Start in Manual",
161384
+ description: "Keep approvals on. Kimi Code may stop and wait for you during the swarm task."
161385
+ }
161386
+ ];
161077
161387
  const NOTICE_LINES = [
161078
161388
  "Manual mode asks you before Kimi Code runs commands, edits files, or takes other risky actions.",
161079
161389
  "Manual mode can block swarm work while agents are running.",
@@ -161151,7 +161461,7 @@ function showSwarmStartPermissionPrompt(host, commandText, cancelStatus, onSelec
161151
161461
  }));
161152
161462
  }
161153
161463
  async function startSwarmWithPermission(host, prompt, choice) {
161154
- if (choice === "auto") {
161464
+ if (choice === "auto" || choice === "yolo") {
161155
161465
  if (!await setPermissionForSwarm(host, choice)) return;
161156
161466
  }
161157
161467
  await startSwarmTask(host, prompt);
@@ -161182,7 +161492,7 @@ async function applySwarmMode(host, enabled, commandText) {
161182
161492
  }
161183
161493
  if (enabled && host.state.appState.permissionMode === "manual") {
161184
161494
  showSwarmStartPermissionPrompt(host, commandText, "Swarm mode not enabled.", async (choice) => {
161185
- if (choice === "auto" && !await setPermissionForSwarm(host, choice)) return;
161495
+ if ((choice === "auto" || choice === "yolo") && !await setPermissionForSwarm(host, choice)) return;
161186
161496
  if (!await setSwarmMode(host, true, "manual")) return;
161187
161497
  renderSwarmModeMarker(host, "active");
161188
161498
  });
@@ -166809,15 +167119,20 @@ function installPromptTerminationCleanup(promptProcess, cleanup) {
166809
167119
  };
166810
167120
  const onSigint = () => exitAfterCleanup("SIGINT");
166811
167121
  const onSigterm = () => exitAfterCleanup("SIGTERM");
167122
+ const onSighup = () => exitAfterCleanup("SIGHUP");
166812
167123
  promptProcess.once("SIGINT", onSigint);
166813
167124
  promptProcess.once("SIGTERM", onSigterm);
167125
+ promptProcess.once("SIGHUP", onSighup);
166814
167126
  return () => {
166815
167127
  promptProcess.off("SIGINT", onSigint);
166816
167128
  promptProcess.off("SIGTERM", onSigterm);
167129
+ promptProcess.off("SIGHUP", onSighup);
166817
167130
  };
166818
167131
  }
166819
167132
  function signalExitCode(signal) {
166820
- return signal === "SIGINT" ? 130 : 143;
167133
+ if (signal === "SIGINT") return 130;
167134
+ if (signal === "SIGHUP") return 129;
167135
+ return 143;
166821
167136
  }
166822
167137
  function runPromptTurn(session, prompt, outputFormat, stdout, stderr) {
166823
167138
  let activeTurnId;
@@ -167783,16 +168098,16 @@ function buildNumericHint(count) {
167783
168098
  * emulator's "snap to bottom on stdout" reflex, causing flicker and an
167784
168099
  * unscrollable history pane.
167785
168100
  */
167786
- const ELLIPSIS$4 = "…";
168101
+ const ELLIPSIS$5 = "…";
167787
168102
  function padToWidth$2(line, width) {
167788
168103
  const w = visibleWidth(line);
167789
168104
  if (w === width) return line;
167790
- if (w > width) return truncateToWidth(line, width, ELLIPSIS$4);
168105
+ if (w > width) return truncateToWidth(line, width, ELLIPSIS$5);
167791
168106
  return line + " ".repeat(width - w);
167792
168107
  }
167793
168108
  function fitExactly$2(line, width) {
167794
168109
  let s = line;
167795
- if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$4);
168110
+ if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$5);
167796
168111
  return padToWidth$2(s, width);
167797
168112
  }
167798
168113
  var ApprovalPreviewViewer = class extends Container {
@@ -168649,7 +168964,7 @@ var QuestionDialogComponent = class extends Container {
168649
168964
  /**
168650
168965
  * SessionPicker — pi-tui version of the session selection dialog.
168651
168966
  */
168652
- const ELLIPSIS$3 = "…";
168967
+ const ELLIPSIS$4 = "…";
168653
168968
  function formatRelativeTime$1(ts) {
168654
168969
  if (!Number.isFinite(ts) || ts <= 0) return "";
168655
168970
  const diffSec = Math.floor(Math.max(0, Date.now() - ts) / 1e3);
@@ -168669,7 +168984,7 @@ function homeAlias(path) {
168669
168984
  function truncatePathLeft(path, maxWidth) {
168670
168985
  if (maxWidth <= 0) return "";
168671
168986
  if (visibleWidth(path) <= maxWidth) return path;
168672
- if (maxWidth === 1) return ELLIPSIS$3;
168987
+ if (maxWidth === 1) return ELLIPSIS$4;
168673
168988
  const segments = [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(path)].map((s) => s.segment);
168674
168989
  let used = 0;
168675
168990
  const budget = maxWidth - 1;
@@ -168682,7 +168997,7 @@ function truncatePathLeft(path, maxWidth) {
168682
168997
  used += w;
168683
168998
  i--;
168684
168999
  }
168685
- return ELLIPSIS$3 + segments.slice(i + 1).join("");
169000
+ return ELLIPSIS$4 + segments.slice(i + 1).join("");
168686
169001
  }
168687
169002
  function singleLine$1(text) {
168688
169003
  return text.replaceAll(/\s+/g, " ").trim();
@@ -168734,26 +169049,26 @@ var SessionPickerComponent = class extends Container {
168734
169049
  if (matchesKey(data, Key.down)) this.selectedIndex = Math.min(this.sessions.length - 1, this.selectedIndex + 1);
168735
169050
  }
168736
169051
  render(width) {
168737
- return this.renderLines(width).map((line) => truncateToWidth(line, width, ELLIPSIS$3));
169052
+ return this.renderLines(width).map((line) => truncateToWidth(line, width, ELLIPSIS$4));
168738
169053
  }
168739
169054
  renderLines(width) {
168740
169055
  const lines = [currentTheme.fg("primary", "─".repeat(width))];
168741
169056
  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)));
169057
+ lines.push(currentTheme.boldFg("primary", truncateToWidth("Sessions", width, ELLIPSIS$4)));
169058
+ lines.push(currentTheme.fg("textMuted", truncateToWidth("Loading sessions...", width, ELLIPSIS$4)));
168744
169059
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
168745
169060
  return lines;
168746
169061
  }
168747
169062
  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)));
169063
+ lines.push(currentTheme.boldFg("primary", truncateToWidth("Sessions", width, ELLIPSIS$4)));
169064
+ lines.push(currentTheme.fg("textMuted", truncateToWidth("No sessions found. Press Escape to close.", width, ELLIPSIS$4)));
168750
169065
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
168751
169066
  return lines;
168752
169067
  }
168753
169068
  const headerLabel = "Sessions ";
168754
169069
  const headerHint = "↑↓ navigate · Enter select · Esc cancel";
168755
169070
  const labelWidth = visibleWidth(headerLabel);
168756
- const shownHint = truncateToWidth(headerHint, Math.max(0, width - labelWidth), ELLIPSIS$3);
169071
+ const shownHint = truncateToWidth(headerHint, Math.max(0, width - labelWidth), ELLIPSIS$4);
168757
169072
  lines.push(currentTheme.boldFg("primary", headerLabel) + currentTheme.fg("textMuted", shownHint));
168758
169073
  lines.push("");
168759
169074
  const visibleStart = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisibleSessions / 2), Math.max(0, this.sessions.length - this.maxVisibleSessions)));
@@ -168768,7 +169083,7 @@ var SessionPickerComponent = class extends Container {
168768
169083
  if (this.sessions.length > visibleSessions.length) {
168769
169084
  lines.push("");
168770
169085
  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)));
169086
+ lines.push(currentTheme.fg("textMuted", truncateToWidth(footer, width, ELLIPSIS$4)));
168772
169087
  }
168773
169088
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
168774
169089
  return lines;
@@ -168789,7 +169104,7 @@ var SessionPickerComponent = class extends Container {
168789
169104
  const trailingWidth = visibleWidth(trailingParts.length > 0 ? " " + trailingParts.join(" ") : "");
168790
169105
  const headerPrefixWidth = visibleWidth(pointer) + 1;
168791
169106
  const titleBudget = Math.max(8, width - headerPrefixWidth - trailingWidth);
168792
- const shownTitle = truncateToWidth(singleLine$1(titleSource), titleBudget, ELLIPSIS$3);
169107
+ const shownTitle = truncateToWidth(singleLine$1(titleSource), titleBudget, ELLIPSIS$4);
168793
169108
  let header = currentTheme.fg(isSelected ? "primary" : "textDim", pointer + " ");
168794
169109
  header += titleStyle(shownTitle);
168795
169110
  if (time.length > 0) header += " " + currentTheme.fg("textDim", time);
@@ -168804,7 +169119,7 @@ var SessionPickerComponent = class extends Container {
168804
169119
  const dirWidth = visibleWidth(aliasedDir);
168805
169120
  if (idLineWidth + metaGapWidth + dirWidth <= width) card.push(indent + currentTheme.fg("textMuted", fullId) + currentTheme.fg("textDim", metaGap) + currentTheme.fg("textMuted", aliasedDir));
168806
169121
  else {
168807
- card.push(indent + currentTheme.fg("textMuted", truncateToWidth(fullId, Math.max(idWidth, width - indentWidth), ELLIPSIS$3)));
169122
+ card.push(indent + currentTheme.fg("textMuted", truncateToWidth(fullId, Math.max(idWidth, width - indentWidth), ELLIPSIS$4)));
168808
169123
  const dir = truncatePathLeft(aliasedDir, Math.max(8, width - indentWidth));
168809
169124
  card.push(indent + currentTheme.fg("textMuted", dir));
168810
169125
  }
@@ -168813,7 +169128,7 @@ var SessionPickerComponent = class extends Container {
168813
169128
  const promptMarker = "› ";
168814
169129
  const promptMarkerWidth = visibleWidth(promptMarker);
168815
169130
  const promptBudget = Math.max(8, width - indentWidth - promptMarkerWidth);
168816
- const promptText = truncateToWidth(singleLine$1(rawPrompt), promptBudget, ELLIPSIS$3);
169131
+ const promptText = truncateToWidth(singleLine$1(rawPrompt), promptBudget, ELLIPSIS$4);
168817
169132
  const promptLine = indent + currentTheme.fg("textDim", promptMarker + promptText);
168818
169133
  card.push(promptLine);
168819
169134
  }
@@ -168910,7 +169225,7 @@ function restoreProviderAliases(config, aliases) {
168910
169225
  function restoreDefaultSelection(config, defaultModel, defaultThinking) {
168911
169226
  if (defaultModel === void 0 || config.models?.[defaultModel] === void 0) return;
168912
169227
  config.defaultModel = defaultModel;
168913
- config.defaultThinking = defaultThinking;
169228
+ config.defaultThinking = (config.models[defaultModel]?.capabilities ?? []).includes("always_thinking") ? true : defaultThinking;
168914
169229
  }
168915
169230
  function clampDanglingDefault(config) {
168916
169231
  if (config.defaultModel !== void 0 && config.models?.[config.defaultModel] === void 0) {
@@ -169539,13 +169854,7 @@ var BtwPanelController = class {
169539
169854
  return panel.isRunning() || panel.isEmpty();
169540
169855
  }
169541
169856
  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
- }
169857
+ return this.host.harness.withInteractiveAgent(agentId, fn);
169549
169858
  }
169550
169859
  };
169551
169860
  function formatBtwTurnEnd(event) {
@@ -173658,7 +173967,7 @@ var StreamingUIController = class {
173658
173967
  * TasksBrowserApp. Snapshot view (no live tail) — content is fetched
173659
173968
  * once when the viewer opens.
173660
173969
  */
173661
- const ELLIPSIS$2 = "…";
173970
+ const ELLIPSIS$3 = "…";
173662
173971
  const STATUS_LABEL$1 = {
173663
173972
  running: "running",
173664
173973
  completed: "completed",
@@ -173680,12 +173989,12 @@ function statusColor$1(status) {
173680
173989
  function padToWidth$1(line, width) {
173681
173990
  const w = visibleWidth(line);
173682
173991
  if (w === width) return line;
173683
- if (w > width) return truncateToWidth(line, width, ELLIPSIS$2);
173992
+ if (w > width) return truncateToWidth(line, width, ELLIPSIS$3);
173684
173993
  return line + " ".repeat(width - w);
173685
173994
  }
173686
173995
  function fitExactly$1(line, width) {
173687
173996
  let s = line;
173688
- if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$2);
173997
+ if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$3);
173689
173998
  return padToWidth$1(s, width);
173690
173999
  }
173691
174000
  var TaskOutputViewer = class extends Container {
@@ -173844,7 +174153,7 @@ var TaskOutputViewer = class extends Container {
173844
174153
  * Data (tasks list, tail output) flows in via `setProps`; user actions
173845
174154
  * fire the `on*` callbacks back to the controller.
173846
174155
  */
173847
- const ELLIPSIS$1 = "…";
174156
+ const ELLIPSIS$2 = "…";
173848
174157
  const STATUS_LABEL = {
173849
174158
  running: "running",
173850
174159
  completed: "completed",
@@ -173892,13 +174201,13 @@ function singleLine(text) {
173892
174201
  function padToWidth(line, width) {
173893
174202
  const w = visibleWidth(line);
173894
174203
  if (w === width) return line;
173895
- if (w > width) return truncateToWidth(line, width, ELLIPSIS$1);
174204
+ if (w > width) return truncateToWidth(line, width, ELLIPSIS$2);
173896
174205
  return line + " ".repeat(width - w);
173897
174206
  }
173898
174207
  /** Fit `line` into exactly `width` columns, even after CJK-edge truncation. */
173899
174208
  function fitExactly(line, width) {
173900
174209
  let s = line;
173901
- if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$1);
174210
+ if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$2);
173902
174211
  return padToWidth(s, width);
173903
174212
  }
173904
174213
  function visibleTasks(tasks, filter) {
@@ -174159,7 +174468,7 @@ var TasksBrowserApp = class extends Container {
174159
174468
  const prefixWidth = visibleWidth(prefix);
174160
174469
  const descBudget = Math.max(0, innerWidth - prefixWidth - 1);
174161
174470
  if (descBudget < 4) return fitExactly(prefix, innerWidth);
174162
- const desc = truncateToWidth(singleLine(task.description) || (task.kind === "process" ? singleLine(task.command) : "") || "(no description)", descBudget, ELLIPSIS$1);
174471
+ const desc = truncateToWidth(singleLine(task.description) || (task.kind === "process" ? singleLine(task.command) : "") || "(no description)", descBudget, ELLIPSIS$2);
174163
174472
  return fitExactly(`${prefix} ${currentTheme.fg("text", desc)}`, innerWidth);
174164
174473
  }
174165
174474
  adjustScroll(visibleRows) {
@@ -174556,13 +174865,23 @@ const MAX_FALLBACK_SUGGESTIONS = 50;
174556
174865
  * completer. This wrapper also keeps Kimi-specific slash-command guards.
174557
174866
  */
174558
174867
  var FileMentionProvider = class {
174868
+ slashCommands;
174559
174869
  workDir;
174560
174870
  fdPath;
174561
174871
  inner;
174562
174872
  constructor(slashCommands, workDir, fdPath) {
174873
+ this.slashCommands = slashCommands;
174563
174874
  this.workDir = workDir;
174564
174875
  this.fdPath = fdPath;
174565
- this.inner = new CombinedAutocompleteProvider(slashCommands, workDir, fdPath);
174876
+ const expanded = [];
174877
+ for (const cmd of slashCommands) {
174878
+ expanded.push(cmd);
174879
+ for (const alias of cmd.aliases ?? []) expanded.push({
174880
+ ...cmd,
174881
+ name: alias
174882
+ });
174883
+ }
174884
+ this.inner = new CombinedAutocompleteProvider(expanded, workDir, fdPath);
174566
174885
  }
174567
174886
  async getSuggestions(lines, cursorLine, cursorCol, options) {
174568
174887
  const currentLine = lines[cursorLine] ?? "";
@@ -174578,6 +174897,46 @@ var FileMentionProvider = class {
174578
174897
  return getFsMentionSuggestions(this.workDir, atPrefix, options.signal);
174579
174898
  }
174580
174899
  }
174900
+ if (!options.force && textBeforeCursor.startsWith("/")) {
174901
+ if (textBeforeCursor.indexOf(" ") === -1) {
174902
+ const tokens = textBeforeCursor.slice(1).trim().split(/\s+/).filter((t) => t.length > 0);
174903
+ const matches = [];
174904
+ for (const cmd of this.slashCommands) {
174905
+ const nameScore = scoreTokens(tokens, cmd.name);
174906
+ if (nameScore !== null) {
174907
+ matches.push({
174908
+ cmd,
174909
+ score: nameScore,
174910
+ viaAlias: false,
174911
+ label: cmd.name
174912
+ });
174913
+ continue;
174914
+ }
174915
+ const aliases = cmd.aliases ?? [];
174916
+ let bestAliasScore = null;
174917
+ for (const alias of aliases) {
174918
+ const aliasScore = scoreTokens(tokens, alias);
174919
+ if (aliasScore !== null && (bestAliasScore === null || aliasScore < bestAliasScore)) bestAliasScore = aliasScore;
174920
+ }
174921
+ if (bestAliasScore !== null) matches.push({
174922
+ cmd,
174923
+ score: bestAliasScore,
174924
+ viaAlias: true,
174925
+ label: `${cmd.name} (${aliases.join(", ")})`
174926
+ });
174927
+ }
174928
+ matches.sort((a, b) => a.score - b.score || Number(a.viaAlias) - Number(b.viaAlias));
174929
+ if (matches.length === 0) return null;
174930
+ return {
174931
+ items: matches.map((m) => ({
174932
+ value: m.cmd.name,
174933
+ label: m.label,
174934
+ description: formatSlashCommandDescription(m.cmd)
174935
+ })),
174936
+ prefix: textBeforeCursor
174937
+ };
174938
+ }
174939
+ }
174581
174940
  try {
174582
174941
  return await this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
174583
174942
  } catch {
@@ -174694,6 +175053,28 @@ function shouldSuppressSlashArgumentCompletion(textBeforeCursor, textAfterCursor
174694
175053
  if (!textBeforeCursor.includes(" ")) return false;
174695
175054
  return textAfterCursor.trimStart().length > 0;
174696
175055
  }
175056
+ /**
175057
+ * All tokens must fuzzy-match `text`; returns the summed score, or null when
175058
+ * any token misses. An empty token list matches everything with score 0.
175059
+ * Mirrors pi-tui fuzzyFilter's token semantics — keep in sync if it changes.
175060
+ */
175061
+ function scoreTokens(tokens, text) {
175062
+ let score = 0;
175063
+ for (const token of tokens) {
175064
+ const m = fuzzyMatch(token, text);
175065
+ if (!m.matches) return null;
175066
+ score += m.score;
175067
+ }
175068
+ return score;
175069
+ }
175070
+ /**
175071
+ * Mirrors CombinedAutocompleteProvider's description rendering so the
175072
+ * intercepted name completion keeps showing the argument hint.
175073
+ */
175074
+ function formatSlashCommandDescription(cmd) {
175075
+ const desc = cmd.description ?? "";
175076
+ return (cmd.argumentHint ? desc ? `${cmd.argumentHint} — ${desc}` : cmd.argumentHint : desc) || void 0;
175077
+ }
174697
175078
  //#endregion
174698
175079
  //#region src/tui/components/panes/activity-pane.ts
174699
175080
  var ActivityPaneComponent = class extends Container {
@@ -174714,7 +175095,7 @@ var ActivityPaneComponent = class extends Container {
174714
175095
  };
174715
175096
  //#endregion
174716
175097
  //#region src/tui/components/panes/queue-pane.ts
174717
- const ELLIPSIS = "…";
175098
+ const ELLIPSIS$1 = "…";
174718
175099
  var QueuePaneComponent = class extends Container {
174719
175100
  messages;
174720
175101
  hint;
@@ -174730,10 +175111,10 @@ var QueuePaneComponent = class extends Container {
174730
175111
  for (const item of this.messages) {
174731
175112
  const singleLine = item.text.replaceAll(/\s+/g, " ").trim();
174732
175113
  const prefix = ` ❯ `;
174733
- const truncated = truncateToWidth(singleLine, Math.max(1, width - visibleWidth(prefix)), ELLIPSIS);
175114
+ const truncated = truncateToWidth(singleLine, Math.max(1, width - visibleWidth(prefix)), ELLIPSIS$1);
174734
175115
  lines.push(accent(prefix + truncated));
174735
175116
  }
174736
- if (this.hint !== void 0) lines.push(dim(truncateToWidth(this.hint, width, ELLIPSIS)));
175117
+ if (this.hint !== void 0) lines.push(dim(truncateToWidth(this.hint, width, ELLIPSIS$1)));
174737
175118
  return lines;
174738
175119
  }
174739
175120
  };
@@ -175157,6 +175538,144 @@ function createApprovalRequestHandler(controller, onResponse) {
175157
175538
  };
175158
175539
  }
175159
175540
  //#endregion
175541
+ //#region src/tui/banner/banner-provider.ts
175542
+ function normalizeTag(value) {
175543
+ if (typeof value !== "string") return null;
175544
+ const trimmed = value.trim();
175545
+ return trimmed.length > 0 ? trimmed : null;
175546
+ }
175547
+ function normalizeText(value) {
175548
+ if (typeof value !== "string") return null;
175549
+ const trimmed = value.trim();
175550
+ return trimmed.length > 0 ? trimmed : null;
175551
+ }
175552
+ function normalizeUtcDate(value) {
175553
+ if (value.endsWith("Z")) return value;
175554
+ if (/[+-]\d{2}:\d{2}$/.test(value)) return value;
175555
+ return `${value}Z`;
175556
+ }
175557
+ function parseDate(value) {
175558
+ if (typeof value !== "string" || value.length === 0) return null;
175559
+ const normalized = normalizeUtcDate(value);
175560
+ const date = new Date(normalized);
175561
+ return Number.isNaN(date.getTime()) ? null : date;
175562
+ }
175563
+ function isWithinWindow(start, end, now) {
175564
+ if (start !== null && now < start) return false;
175565
+ if (end !== null && now > end) return false;
175566
+ return true;
175567
+ }
175568
+ function meetsMinVersion(minVersion, clientVersion) {
175569
+ if (minVersion === void 0 || minVersion === null) return true;
175570
+ if (typeof minVersion !== "string" || minVersion.length === 0) return true;
175571
+ const min = valid(minVersion);
175572
+ const current = valid(clientVersion);
175573
+ if (min === null || current === null) return false;
175574
+ return gte(current, min);
175575
+ }
175576
+ function pickActiveBanner(json, clientVersion, now) {
175577
+ if (json.banner_enabled !== true) return null;
175578
+ if (!meetsMinVersion(json.banner_min_version, clientVersion)) return null;
175579
+ if (!isWithinWindow(parseDate(json.banner_start_time), parseDate(json.banner_end_time), now)) return null;
175580
+ const mainText = normalizeText(json.banner_maintext);
175581
+ if (mainText === null) return null;
175582
+ return {
175583
+ tag: normalizeTag(json.banner_title),
175584
+ mainText,
175585
+ subText: normalizeText(json.banner_subtext)
175586
+ };
175587
+ }
175588
+ function pickFallbackBanner(json, clientVersion, now, random) {
175589
+ if (json.banner_fallback_enabled !== true) return null;
175590
+ const list = Array.isArray(json.banner_fallback_list) ? json.banner_fallback_list : [];
175591
+ const candidates = [];
175592
+ for (const raw of list) {
175593
+ if (typeof raw !== "object" || raw === null) continue;
175594
+ const item = raw;
175595
+ if (item.enabled !== true) continue;
175596
+ if (!meetsMinVersion(item.banner_min_version, clientVersion)) continue;
175597
+ const mainText = normalizeText(item.banner_maintext);
175598
+ if (mainText === null) continue;
175599
+ candidates.push({
175600
+ tag: normalizeTag(item.banner_title),
175601
+ mainText,
175602
+ subText: normalizeText(item.banner_subtext)
175603
+ });
175604
+ }
175605
+ if (candidates.length === 0) return null;
175606
+ return candidates[Math.floor(random() * candidates.length)];
175607
+ }
175608
+ function selectBannerState(json, clientVersion, now, random) {
175609
+ const typed = typeof json === "object" && json !== null ? json : {};
175610
+ return pickActiveBanner(typed, clientVersion, now) ?? pickFallbackBanner(typed, clientVersion, now, random);
175611
+ }
175612
+ var BannerProvider = class {
175613
+ clientVersion;
175614
+ url;
175615
+ constructor(clientVersion, url = KIMI_CODE_TIPS_BANNER_URL) {
175616
+ this.clientVersion = clientVersion;
175617
+ this.url = url;
175618
+ }
175619
+ async load(fetchImpl = fetch) {
175620
+ try {
175621
+ const controller = new AbortController();
175622
+ const timeout = setTimeout(() => {
175623
+ controller.abort();
175624
+ }, 3e3);
175625
+ const response = await fetchImpl(this.url, { signal: controller.signal });
175626
+ clearTimeout(timeout);
175627
+ if (!response.ok) return null;
175628
+ return selectBannerState(await response.json(), this.clientVersion, /* @__PURE__ */ new Date(), Math.random);
175629
+ } catch {
175630
+ return null;
175631
+ }
175632
+ }
175633
+ };
175634
+ //#endregion
175635
+ //#region src/tui/components/chrome/banner.ts
175636
+ const PREFIX_STAR = "✦";
175637
+ const PADDING = " ";
175638
+ var BannerComponent = class {
175639
+ state;
175640
+ constructor(state) {
175641
+ this.state = state;
175642
+ }
175643
+ invalidate() {}
175644
+ render(width) {
175645
+ const main = (s) => currentTheme.boldFg("textStrong", s);
175646
+ const dim = (s) => currentTheme.fg("textDim", s);
175647
+ if (width < 1) return [""];
175648
+ const tagText = this.state.tag ?? "";
175649
+ const tagLabel = tagText.length > 0 ? `${PREFIX_STAR} ${tagText}` : "";
175650
+ const tagStyled = tagLabel.length > 0 ? currentTheme.boldFg("primary", tagLabel) : "";
175651
+ const tagDisplay = tagStyled.length > 0 ? tagStyled + PADDING : "";
175652
+ const tagWidth = visibleWidth(tagDisplay);
175653
+ const showTag = tagWidth > 0 && tagWidth < width;
175654
+ const bodyIndent = showTag ? " ".repeat(tagWidth) : "";
175655
+ const descIndent = showTag ? " ".repeat(visibleWidth(PREFIX_STAR + PADDING)) : "";
175656
+ const bodyContentWidth = width - (showTag ? tagWidth : 0);
175657
+ const descContentWidth = width - (showTag ? visibleWidth(PREFIX_STAR + PADDING) : 0);
175658
+ if (bodyContentWidth <= 0) return [""];
175659
+ const mainSegments = this.state.mainText.split("\n");
175660
+ const subSegments = this.state.subText ? this.state.subText.split("\n") : [];
175661
+ const result = [];
175662
+ for (let i = 0; i < mainSegments.length; i++) {
175663
+ const wrapped = wrapTextWithAnsi(mainSegments[i], bodyContentWidth);
175664
+ for (let j = 0; j < wrapped.length; j++) {
175665
+ const boldLine = main(wrapped[j]);
175666
+ if (i === 0 && j === 0 && showTag) result.push(tagDisplay + boldLine);
175667
+ else result.push(bodyIndent + boldLine);
175668
+ }
175669
+ }
175670
+ for (const sub of subSegments) {
175671
+ const wrapped = wrapTextWithAnsi(sub, descContentWidth <= 0 ? bodyContentWidth : descContentWidth);
175672
+ for (const line of wrapped) result.push(descIndent + dim(line));
175673
+ }
175674
+ result.push("");
175675
+ return result;
175676
+ }
175677
+ };
175678
+ //#endregion
175160
175679
  //#region src/tui/reverse-rpc/modal-coordinator.ts
175161
175680
  var ReverseRpcModalCoordinator = class {
175162
175681
  hooks;
@@ -176028,6 +176547,107 @@ function styleTitle(title, status, colors) {
176028
176547
  }
176029
176548
  }
176030
176549
  //#endregion
176550
+ //#region src/tui/components/editor/wrapping-select-list.ts
176551
+ const DEFAULT_PRIMARY_COLUMN_WIDTH = 32;
176552
+ const PRIMARY_COLUMN_GAP = 2;
176553
+ const MIN_DESCRIPTION_WIDTH = 10;
176554
+ const DESCRIPTION_MAX_LINES = 2;
176555
+ const ELLIPSIS = "…";
176556
+ const ELLIPSIS_WIDTH = visibleWidth(ELLIPSIS);
176557
+ const TRAILING_ANSI_RESET = /(?:\u001B\[0m)+$/;
176558
+ function truncatePlainToWidth(text, maxWidth) {
176559
+ return truncateToWidth(text, maxWidth, "").replace(TRAILING_ANSI_RESET, "");
176560
+ }
176561
+ /**
176562
+ * SelectList that wraps item descriptions onto up to two lines instead of
176563
+ * truncating them to one. Long command / skill descriptions stay readable;
176564
+ * anything past the second line is ellipsized.
176565
+ *
176566
+ * Only `render` is replaced — selection, filtering, and key handling stay in
176567
+ * pi-tui. pi-tui keeps the row state private, so the renderer reads it
176568
+ * through a cast, the same idiom CustomEditor uses for autocomplete
176569
+ * internals.
176570
+ */
176571
+ var WrappingSelectList = class extends SelectList {
176572
+ render(width) {
176573
+ const { filteredItems, selectedIndex, maxVisible, theme } = this.internals();
176574
+ if (filteredItems.length === 0) return [theme.noMatch(" No matching commands")];
176575
+ const primaryColumnWidth = this.primaryColumnWidth();
176576
+ const startIndex = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), filteredItems.length - maxVisible));
176577
+ const endIndex = Math.min(startIndex + maxVisible, filteredItems.length);
176578
+ const lines = [];
176579
+ for (let i = startIndex; i < endIndex; i++) {
176580
+ const item = filteredItems[i];
176581
+ if (!item) continue;
176582
+ lines.push(...this.renderItemLines(item, i === selectedIndex, width, primaryColumnWidth));
176583
+ }
176584
+ if (startIndex > 0 || endIndex < filteredItems.length) {
176585
+ const scrollText = ` (${selectedIndex + 1}/${filteredItems.length})`;
176586
+ lines.push(theme.scrollInfo(truncatePlainToWidth(scrollText, width - 2)));
176587
+ }
176588
+ return lines;
176589
+ }
176590
+ renderItemLines(item, isSelected, width, primaryColumnWidth) {
176591
+ const { theme } = this.internals();
176592
+ const prefix = isSelected ? "→ " : " ";
176593
+ const prefixWidth = visibleWidth(prefix);
176594
+ const description = item.description ? item.description.replaceAll(/[\r\n]+/g, " ").trim() : void 0;
176595
+ if (description && width > 40) {
176596
+ const effectivePrimaryColumnWidth = Math.max(1, Math.min(primaryColumnWidth, width - prefixWidth - 4));
176597
+ const maxPrimaryWidth = Math.max(1, effectivePrimaryColumnWidth - PRIMARY_COLUMN_GAP);
176598
+ const truncatedValue = this.truncatePrimaryValue(item, isSelected, maxPrimaryWidth, effectivePrimaryColumnWidth);
176599
+ const truncatedValueWidth = visibleWidth(truncatedValue);
176600
+ const spacing = " ".repeat(Math.max(1, effectivePrimaryColumnWidth - truncatedValueWidth));
176601
+ const descriptionStart = prefixWidth + truncatedValueWidth + spacing.length;
176602
+ const remainingWidth = width - descriptionStart - 2;
176603
+ if (remainingWidth > MIN_DESCRIPTION_WIDTH) {
176604
+ const descriptionLines = wrapDescription(description, remainingWidth);
176605
+ const indent = " ".repeat(descriptionStart);
176606
+ if (isSelected) return descriptionLines.map((line, index) => theme.selectedText(index === 0 ? `${prefix}${truncatedValue}${spacing}${line}` : indent + line));
176607
+ return descriptionLines.map((line, index) => index === 0 ? prefix + truncatedValue + theme.description(spacing + line) : theme.description(indent + line));
176608
+ }
176609
+ }
176610
+ const maxWidth = width - prefixWidth - 2;
176611
+ const truncatedValue = this.truncatePrimaryValue(item, isSelected, maxWidth, maxWidth);
176612
+ return [isSelected ? theme.selectedText(`${prefix}${truncatedValue}`) : prefix + truncatedValue];
176613
+ }
176614
+ truncatePrimaryValue(item, isSelected, maxWidth, columnWidth) {
176615
+ const { layout } = this.internals();
176616
+ const displayValue = item.label || item.value;
176617
+ return truncatePlainToWidth(layout.truncatePrimary ? layout.truncatePrimary({
176618
+ text: displayValue,
176619
+ maxWidth,
176620
+ columnWidth,
176621
+ item,
176622
+ isSelected
176623
+ }) : displayValue, maxWidth);
176624
+ }
176625
+ primaryColumnWidth() {
176626
+ const { filteredItems, layout } = this.internals();
176627
+ const rawMin = layout.minPrimaryColumnWidth ?? layout.maxPrimaryColumnWidth ?? DEFAULT_PRIMARY_COLUMN_WIDTH;
176628
+ const rawMax = layout.maxPrimaryColumnWidth ?? layout.minPrimaryColumnWidth ?? DEFAULT_PRIMARY_COLUMN_WIDTH;
176629
+ const min = Math.max(1, Math.min(rawMin, rawMax));
176630
+ const max = Math.max(1, Math.max(rawMin, rawMax));
176631
+ const widest = filteredItems.reduce((acc, item) => Math.max(acc, visibleWidth(item.label || item.value) + PRIMARY_COLUMN_GAP), 0);
176632
+ return Math.max(min, Math.min(widest, max));
176633
+ }
176634
+ internals() {
176635
+ return this;
176636
+ }
176637
+ };
176638
+ /**
176639
+ * Wrap `text` to at most DESCRIPTION_MAX_LINES lines of `width` columns.
176640
+ * When the text needs more lines, the last visible line is rebuilt from the
176641
+ * remaining text and ellipsized.
176642
+ */
176643
+ function wrapDescription(text, width) {
176644
+ const wrapped = wrapTextWithAnsi(text, width);
176645
+ if (wrapped.length <= DESCRIPTION_MAX_LINES) return wrapped;
176646
+ const kept = wrapped.slice(0, DESCRIPTION_MAX_LINES - 1);
176647
+ const clipped = truncatePlainToWidth(wrapped.slice(DESCRIPTION_MAX_LINES - 1).join(" "), width - ELLIPSIS_WIDTH).trimEnd();
176648
+ return [...kept, `${clipped}${ELLIPSIS}`];
176649
+ }
176650
+ //#endregion
176031
176651
  //#region src/tui/components/editor/custom-editor.ts
176032
176652
  /**
176033
176653
  * Custom editor extending pi-tui Editor with app-level keybindings.
@@ -176040,6 +176660,10 @@ const KITTY_CSI_U = /^\u001B\[(\d+);(\d+)((?::\d+)*)u$/;
176040
176660
  const CAPS_LOCK_BIT = 64;
176041
176661
  const CTRL_BIT = 4;
176042
176662
  const SHIFT_BIT = 1;
176663
+ const SLASH_COMMAND_SELECT_LIST_LAYOUT = {
176664
+ minPrimaryColumnWidth: 12,
176665
+ maxPrimaryColumnWidth: 32
176666
+ };
176043
176667
  /**
176044
176668
  * Workaround for a pi-tui bug that surfaces when Kitty keyboard protocol
176045
176669
  * is active AND caps_lock is on. In that state terminals emit, e.g.,
@@ -176125,7 +176749,12 @@ var CustomEditor = class extends Editor {
176125
176749
  consumingPaste = false;
176126
176750
  consumeBuffer = "";
176127
176751
  constructor(tui) {
176128
- super(tui, createEditorTheme(), { paddingX: 4 });
176752
+ const theme = createEditorTheme();
176753
+ super(tui, theme, { paddingX: 4 });
176754
+ this.createAutocompleteList = (prefix, items) => {
176755
+ if (prefix.startsWith("/")) return new WrappingSelectList(items, this.getAutocompleteMaxVisible(), theme.selectList, SLASH_COMMAND_SELECT_LIST_LAYOUT);
176756
+ return new SelectList(items, this.getAutocompleteMaxVisible(), theme.selectList);
176757
+ };
176129
176758
  }
176130
176759
  expandPasteMarkerAtCursor() {
176131
176760
  const { line, col } = this.getCursor();
@@ -176756,7 +177385,8 @@ function createInitialAppState(input) {
176756
177385
  availableProviders: {},
176757
177386
  sessionTitle: null,
176758
177387
  goal: null,
176759
- mcpServersSummary: null
177388
+ mcpServersSummary: null,
177389
+ banner: void 0
176760
177390
  };
176761
177391
  }
176762
177392
  var KimiTUI = class {
@@ -176853,6 +177483,7 @@ var KimiTUI = class {
176853
177483
  const completer = cmd.completeArgs;
176854
177484
  return {
176855
177485
  name: cmd.name,
177486
+ aliases: cmd.aliases,
176856
177487
  description: cmd.description,
176857
177488
  ...cmd.argumentHint !== void 0 ? { argumentHint: cmd.argumentHint } : {},
176858
177489
  ...completer !== void 0 ? { getArgumentCompletions: (prefix) => completer(prefix) } : {}
@@ -176921,10 +177552,28 @@ var KimiTUI = class {
176921
177552
  throw error;
176922
177553
  }
176923
177554
  }
177555
+ async loadBanner() {
177556
+ const provider = new BannerProvider(this.state.appState.version);
177557
+ this.state.appState.banner = await provider.load();
177558
+ if (this.state.appState.banner !== null) {
177559
+ this.renderBanner();
177560
+ this.state.ui.requestRender();
177561
+ }
177562
+ }
177563
+ renderBanner() {
177564
+ if (this.state.appState.banner === null || this.state.appState.banner === void 0) return;
177565
+ if (this.state.transcriptContainer.children.some((child) => child instanceof BannerComponent)) return;
177566
+ const welcomeIndex = this.state.transcriptContainer.children.findIndex((child) => child instanceof WelcomeComponent);
177567
+ const banner = new BannerComponent(this.state.appState.banner);
177568
+ if (welcomeIndex >= 0) this.state.transcriptContainer.children.splice(welcomeIndex + 1, 0, banner);
177569
+ else this.state.transcriptContainer.children.unshift(banner);
177570
+ this.state.transcriptContainer.invalidate();
177571
+ }
176924
177572
  async initMainTui() {
176925
177573
  const shouldReplayHistory = await this.init();
176926
177574
  this.mountFooter();
176927
177575
  this.renderWelcome();
177576
+ this.loadBanner();
176928
177577
  this.setupAutocomplete();
176929
177578
  this.loadPersistedInputHistory();
176930
177579
  this.state.editorContainer.clear();
@@ -177224,10 +177873,11 @@ var KimiTUI = class {
177224
177873
  this.showError(message);
177225
177874
  }
177226
177875
  sendQueuedMessage(session, item) {
177227
- this.harness.interactiveAgentId = item.agentId ?? "main";
177228
- this.sendMessageInternal(session, item.text, {
177229
- parts: item.parts,
177230
- imageAttachmentIds: item.imageAttachmentIds
177876
+ this.harness.withInteractiveAgent(item.agentId ?? "main", () => {
177877
+ this.sendMessageInternal(session, item.text, {
177878
+ parts: item.parts,
177879
+ imageAttachmentIds: item.imageAttachmentIds
177880
+ });
177231
177881
  });
177232
177882
  }
177233
177883
  requestQueuedGoalPromotion() {
@@ -177439,7 +178089,6 @@ var KimiTUI = class {
177439
178089
  this.streamingUI.discardPending();
177440
178090
  this.state.queuedMessages = [];
177441
178091
  this.state.swarmModeEntry = void 0;
177442
- this.harness.interactiveAgentId = MAIN_AGENT_ID;
177443
178092
  this.streamingUI.resetToolCallState();
177444
178093
  this.streamingUI.resetToolUi();
177445
178094
  this.sessionEventHandler.resetRuntimeState();