@moonshot-ai/kimi-code 0.13.1 → 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 {
@@ -45872,7 +45898,7 @@ function convertMessage$1(message, reasoningKey, toolMessageConversion) {
45872
45898
  const result = { role: message.role };
45873
45899
  if (message.role === "tool") {
45874
45900
  const effectiveConversion = message.content.some((p) => p.type !== "text" && p.type !== "think") ? "extract_text" : toolMessageConversion;
45875
- if (effectiveConversion !== null) result.content = convertToolMessageContent(message, effectiveConversion);
45901
+ if (effectiveConversion !== null) result.content = convertToolMessageContentForChat(message, effectiveConversion);
45876
45902
  else {
45877
45903
  const firstPart = nonThinkParts[0];
45878
45904
  if (nonThinkParts.length === 1 && firstPart?.type === "text") result.content = firstPart.text;
@@ -45896,6 +45922,48 @@ function convertMessage$1(message, reasoningKey, toolMessageConversion) {
45896
45922
  if (reasoningContent) result[reasoningKey ?? DEFAULT_OUTBOUND_REASONING_KEY] = reasoningContent;
45897
45923
  return result;
45898
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)";
45927
+ function convertToolMessageContentForChat(message, conversion) {
45928
+ const content = convertToolMessageContent(message, conversion);
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");
45935
+ }
45936
+ function toolResultImageParts(message) {
45937
+ const images = [];
45938
+ for (const part of message.content) {
45939
+ if (part.type !== "image_url") continue;
45940
+ const converted = convertContentPart(part);
45941
+ if (converted !== null) images.push(converted);
45942
+ }
45943
+ return images;
45944
+ }
45945
+ function appendToolResultMediaMessage(messages, pendingToolResultMedia) {
45946
+ if (pendingToolResultMedia.length === 0) return;
45947
+ messages.push({
45948
+ role: "user",
45949
+ content: [{
45950
+ type: "text",
45951
+ text: TOOL_RESULT_MEDIA_PROMPT
45952
+ }, ...pendingToolResultMedia]
45953
+ });
45954
+ pendingToolResultMedia.length = 0;
45955
+ }
45956
+ function convertHistoryMessages$1(history, reasoningKey, toolMessageConversion) {
45957
+ const messages = [];
45958
+ const pendingToolResultMedia = [];
45959
+ for (const msg of history) {
45960
+ if (msg.role !== "tool") appendToolResultMediaMessage(messages, pendingToolResultMedia);
45961
+ messages.push(convertMessage$1(msg, reasoningKey, toolMessageConversion));
45962
+ if (msg.role === "tool") pendingToolResultMedia.push(...toolResultImageParts(msg));
45963
+ }
45964
+ appendToolResultMediaMessage(messages, pendingToolResultMedia);
45965
+ return messages;
45966
+ }
45899
45967
  var OpenAILegacyStreamedMessage = class {
45900
45968
  _id = null;
45901
45969
  _usage = null;
@@ -46031,7 +46099,7 @@ var OpenAILegacyChatProvider = class {
46031
46099
  content: systemPrompt
46032
46100
  });
46033
46101
  const normalizedHistory = normalizeToolCallIdsForProvider(history, OPENAI_CHAT_TOOL_CALL_ID_POLICY);
46034
- for (const msg of normalizedHistory) messages.push(convertMessage$1(msg, this._reasoningKey, this._toolMessageConversion));
46102
+ messages.push(...convertHistoryMessages$1(normalizedHistory, this._reasoningKey, this._toolMessageConversion));
46035
46103
  const kwargs = normalizeGenerationKwargs(this._model, this._generationKwargs);
46036
46104
  let reasoningEffort = this._reasoningEffort;
46037
46105
  if (reasoningEffort === void 0 && kwargs["reasoning_effort"] === void 0) {
@@ -46267,6 +46335,8 @@ function formatResponsesFailedResponse(response) {
46267
46335
  const reason = incompleteDetails === void 0 ? void 0 : readStringField(incompleteDetails, "reason");
46268
46336
  return reason === void 0 ? "Unknown error (no error details in response)" : `incomplete: ${reason}`;
46269
46337
  }
46338
+ const OMITTED_AUDIO_PLACEHOLDER = "(audio omitted: unsupported audio format)";
46339
+ const OMITTED_VIDEO_PLACEHOLDER = "(video omitted: not supported by this provider)";
46270
46340
  function contentPartsToInputItems(parts) {
46271
46341
  const items = [];
46272
46342
  for (const part of parts) switch (part.type) {
@@ -46285,11 +46355,19 @@ function contentPartsToInputItems(parts) {
46285
46355
  break;
46286
46356
  case "audio_url": {
46287
46357
  const mapped = mapAudioUrlToInputItem(part.audioUrl.url);
46288
- if (mapped !== null) items.push(mapped);
46358
+ items.push(mapped ?? {
46359
+ type: "input_text",
46360
+ text: OMITTED_AUDIO_PLACEHOLDER
46361
+ });
46289
46362
  break;
46290
46363
  }
46291
- case "think":
46292
- 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;
46293
46371
  }
46294
46372
  return items;
46295
46373
  }
@@ -46319,11 +46397,19 @@ function messageContentToFunctionOutputItems(content) {
46319
46397
  break;
46320
46398
  case "audio_url": {
46321
46399
  const mapped = mapAudioUrlToInputItem(part.audioUrl.url);
46322
- if (mapped !== null) items.push(mapped);
46400
+ items.push(mapped ?? {
46401
+ type: "input_text",
46402
+ text: OMITTED_AUDIO_PLACEHOLDER
46403
+ });
46323
46404
  break;
46324
46405
  }
46325
- case "think":
46326
- 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;
46327
46413
  }
46328
46414
  return items;
46329
46415
  }
@@ -46356,11 +46442,19 @@ function mapAudioUrlToInputItem(url) {
46356
46442
  function convertMessage(message, modelName, toolMessageConversion) {
46357
46443
  let role = message.role;
46358
46444
  if (usesOpenAIResponsesDeveloperRole(modelName) && role === "system") role = "developer";
46359
- if (role === "tool") return [{
46360
- call_id: message.toolCallId ?? "",
46361
- output: toolMessageConversion === "extract_text" ? extractText(message) : messageContentToFunctionOutputItems(message.content),
46362
- type: "function_call_output"
46363
- }];
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
+ }
46364
46458
  const result = [];
46365
46459
  if (message.content.length > 0) {
46366
46460
  const pendingParts = [];
@@ -46431,6 +46525,35 @@ function convertTool(tool) {
46431
46525
  strict: false
46432
46526
  };
46433
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
+ }
46434
46557
  var OpenAIResponsesStreamedMessage = class {
46435
46558
  _id = null;
46436
46559
  _usage = null;
@@ -46692,16 +46815,8 @@ var OpenAIResponsesChatProvider = class {
46692
46815
  }
46693
46816
  async generate(systemPrompt, tools, history, options) {
46694
46817
  const input = [];
46695
- if (systemPrompt) {
46696
- const sysItem = {
46697
- role: "system",
46698
- content: systemPrompt
46699
- };
46700
- if (usesOpenAIResponsesDeveloperRole(this._model)) sysItem["role"] = "developer";
46701
- input.push(sysItem);
46702
- }
46703
46818
  const normalizedHistory = normalizeToolCallIdsForProvider(history, OPENAI_RESPONSES_TOOL_CALL_ID_POLICY);
46704
- for (const msg of normalizedHistory) input.push(...convertMessage(msg, this._model, this._toolMessageConversion));
46819
+ input.push(...convertHistoryMessages(normalizedHistory, this._model, this._toolMessageConversion));
46705
46820
  const kwargs = { ...this._generationKwargs };
46706
46821
  const reasoningEffort = kwargs["reasoning_effort"];
46707
46822
  delete kwargs["reasoning_effort"];
@@ -46723,6 +46838,7 @@ var OpenAIResponsesChatProvider = class {
46723
46838
  stream: this._stream,
46724
46839
  ...kwargs
46725
46840
  };
46841
+ if (systemPrompt) createParams["instructions"] = systemPrompt;
46726
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.");
46727
46843
  return new OpenAIResponsesStreamedMessage(await client.responses.create(createParams, options?.signal ? { signal: options.signal } : void 0), this._stream);
46728
46844
  } catch (error) {
@@ -47178,7 +47294,7 @@ const REDACTED_KEYS = new Set([
47178
47294
  "bearer"
47179
47295
  ]);
47180
47296
  const SAFE_KEY_RE = /^[\w.-]+$/;
47181
- const ELLIPSIS$8 = "…";
47297
+ const ELLIPSIS$9 = "…";
47182
47298
  const TRUNCATED_TAIL = ` …truncated`;
47183
47299
  const REDACTED = "[REDACTED]";
47184
47300
  const RAW_SECRET_PATTERNS = [
@@ -47217,7 +47333,7 @@ function redactCtx(ctx) {
47217
47333
  return walk(ctx, 0);
47218
47334
  }
47219
47335
  function truncate$2(value, max) {
47220
- 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;
47221
47337
  }
47222
47338
  function serializeValue(raw) {
47223
47339
  if (typeof raw === "string") return redactString(raw);
@@ -47407,7 +47523,6 @@ var RotatingFileSink = class {
47407
47523
  pending = [];
47408
47524
  dropped = 0;
47409
47525
  closed = false;
47410
- failed = false;
47411
47526
  lastStderrNotice = 0;
47412
47527
  currentBytes = -1;
47413
47528
  directorySynced = false;
@@ -47464,7 +47579,6 @@ var RotatingFileSink = class {
47464
47579
  await syncDir(dirname(this.options.path));
47465
47580
  this.directorySynced = true;
47466
47581
  }
47467
- this.failed = false;
47468
47582
  return true;
47469
47583
  } catch (error) {
47470
47584
  this.noteFailure(error);
@@ -47548,7 +47662,6 @@ var RotatingFileSink = class {
47548
47662
  return line;
47549
47663
  }
47550
47664
  noteFailure(error) {
47551
- this.failed = true;
47552
47665
  const now = Date.now();
47553
47666
  if (now - this.lastStderrNotice < STDERR_NOTICE_INTERVAL_MS) return;
47554
47667
  this.lastStderrNotice = now;
@@ -49113,8 +49226,8 @@ function buildBackgroundTaskNotificationBody(info) {
49113
49226
  //#endregion
49114
49227
  //#region ../../packages/agent-core/src/utils/abort.ts
49115
49228
  var import_retry = /* @__PURE__ */ __toESM(require_retry$4(), 1);
49116
- function abortError$1() {
49117
- const error = /* @__PURE__ */ new Error("Aborted");
49229
+ function abortError$1(message = "Aborted") {
49230
+ const error = new Error(message);
49118
49231
  error.name = "AbortError";
49119
49232
  return error;
49120
49233
  }
@@ -56333,8 +56446,12 @@ var ConfigState = class {
56333
56446
  return this._modelAlias;
56334
56447
  }
56335
56448
  get thinkingLevel() {
56449
+ if (this._thinkingLevel === "off" && this.alwaysThinkingModel) return resolveThinkingEffort("on", this.agent.kimiConfig?.thinking);
56336
56450
  return this._thinkingLevel;
56337
56451
  }
56452
+ get alwaysThinkingModel() {
56453
+ return this.tryResolvedProviderConfig()?.alwaysThinking === true;
56454
+ }
56338
56455
  get profileName() {
56339
56456
  return this._profileName;
56340
56457
  }
@@ -57477,6 +57594,7 @@ const HOOK_EVENT_TYPES = [
57477
57594
  "UserPromptSubmit",
57478
57595
  "Stop",
57479
57596
  "StopFailure",
57597
+ "Interrupt",
57480
57598
  "SessionStart",
57481
57599
  "SessionEnd",
57482
57600
  "SubagentStart",
@@ -57980,6 +58098,31 @@ var InjectionManager = class {
57980
58098
  }
57981
58099
  };
57982
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
57983
58126
  //#region ../../packages/agent-core/src/agent/permission/policies/auto-mode-approve.ts
57984
58127
  var AutoModeApprovePermissionPolicy = class {
57985
58128
  agent;
@@ -58422,11 +58565,7 @@ function parseGitDir(content, markerParent) {
58422
58565
  //#endregion
58423
58566
  //#region ../../packages/agent-core/src/agent/permission/policies/file-access-ask.ts
58424
58567
  var SensitiveFileAccessAskPermissionPolicy = class {
58425
- agent;
58426
58568
  name = "sensitive-file-access-ask";
58427
- constructor(agent) {
58428
- this.agent = agent;
58429
- }
58430
58569
  evaluate(context) {
58431
58570
  const access = fileAccesses(context).find((fileAccess) => isSensitiveFile(fileAccess.path));
58432
58571
  if (access === void 0) return;
@@ -60676,6 +60815,7 @@ var YoloModeApprovePermissionPolicy = class {
60676
60815
  function createPermissionDecisionPolicies(agent) {
60677
60816
  return [
60678
60817
  new PreToolCallHookPermissionPolicy(agent),
60818
+ new AgentSwarmExclusiveDenyPermissionPolicy(),
60679
60819
  new AutoModeAskUserQuestionDenyPermissionPolicy(agent),
60680
60820
  new PlanModeGuardDenyPermissionPolicy(agent),
60681
60821
  new UserConfiguredDenyPermissionPolicy(agent),
@@ -60685,7 +60825,7 @@ function createPermissionDecisionPolicies(agent) {
60685
60825
  new UserConfiguredAllowPermissionPolicy(agent),
60686
60826
  new ExitPlanModeReviewAskPermissionPolicy(agent),
60687
60827
  new PlanModeToolApprovePermissionPolicy(agent),
60688
- new SensitiveFileAccessAskPermissionPolicy(agent),
60828
+ new SensitiveFileAccessAskPermissionPolicy(),
60689
60829
  new GitControlPathAccessAskPermissionPolicy(agent),
60690
60830
  new YoloModeApprovePermissionPolicy(agent),
60691
60831
  new SwarmModeAgentSwarmApprovePermissionPolicy(agent),
@@ -61962,12 +62102,16 @@ var AgentRecords = class {
61962
62102
  //#region ../../packages/agent-core/src/agent/replay/index.ts
61963
62103
  var ReplayBuilder = class {
61964
62104
  agent;
62105
+ captureLiveRecords = false;
61965
62106
  records = [];
61966
62107
  constructor(agent) {
61967
62108
  this.agent = agent;
61968
62109
  }
61969
62110
  push(record) {
61970
- 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
+ });
61971
62115
  }
61972
62116
  removeLastMessages(removedMessages) {
61973
62117
  if (removedMessages.size === 0) return;
@@ -65443,7 +65587,7 @@ function isUserActivatableSkillType(type) {
65443
65587
  return isInlineSkillType(type) || type === "flow";
65444
65588
  }
65445
65589
  function isSupportedSkillType(type) {
65446
- return isUserActivatableSkillType(type);
65590
+ return isUserActivatableSkillType(type) || type === "reference";
65447
65591
  }
65448
65592
  function summarizeSkill(skill) {
65449
65593
  return {
@@ -65654,7 +65798,7 @@ function isRecord$11(value) {
65654
65798
  }
65655
65799
  //#endregion
65656
65800
  //#region ../../packages/agent-core/src/skill/builtin/custom-theme.md
65657
- 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";
65658
65802
  //#endregion
65659
65803
  //#region ../../packages/agent-core/src/skill/builtin/custom-theme.ts
65660
65804
  const PSEUDO_PATH$3 = "builtin://custom-theme";
@@ -65698,7 +65842,7 @@ const IMPORT_FROM_CC_CODEX_SKILL = {
65698
65842
  };
65699
65843
  //#endregion
65700
65844
  //#region ../../packages/agent-core/src/skill/builtin/mcp-config.md
65701
- 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";
65702
65846
  //#endregion
65703
65847
  //#region ../../packages/agent-core/src/skill/builtin/mcp-config.ts
65704
65848
  const PSEUDO_PATH$1 = "builtin://mcp-config";
@@ -65755,7 +65899,7 @@ const SUB_SKILL_REVIEW = makeBuiltin(SKILL_default$1, "sub-skill.review", "built
65755
65899
  const SUB_SKILL_CONSOLIDATE = makeBuiltin(SKILL_default$2, "sub-skill.consolidate", "builtin://sub-skill/consolidate", { disableModelInvocation: true });
65756
65900
  //#endregion
65757
65901
  //#region ../../packages/agent-core/src/skill/builtin/update-config.md
65758
- 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";
65759
65903
  //#endregion
65760
65904
  //#region ../../packages/agent-core/src/skill/builtin/update-config.ts
65761
65905
  const PSEUDO_PATH = "builtin://update-config";
@@ -73771,6 +73915,10 @@ function abortedToolOutput(toolName, signal) {
73771
73915
  }
73772
73916
  async function runToolCallBatch(step, response) {
73773
73917
  if (response.toolCalls.length === 0) return { stopTurn: false };
73918
+ const batchStep = {
73919
+ ...step,
73920
+ toolCalls: response.toolCalls
73921
+ };
73774
73922
  const calls = response.toolCalls.map((toolCall) => preflightToolCall(step.tools, toolCall));
73775
73923
  const scheduler = new ToolScheduler();
73776
73924
  const pendingResults = [];
@@ -73778,19 +73926,19 @@ async function runToolCallBatch(step, response) {
73778
73926
  try {
73779
73927
  for (let index = 0; index < calls.length; index += 1) {
73780
73928
  const call = calls[index];
73781
- const prepared = await prepareToolCall(step, call);
73929
+ const prepared = await prepareToolCall(batchStep, call);
73782
73930
  pendingResults.push(scheduler.add(prepared.task));
73783
73931
  if (prepared.stopBatchAfterThis === true) {
73784
73932
  stopTurn = true;
73785
73933
  for (const skippedCall of calls.slice(index + 1)) {
73786
- const skippedTask = await prepareSkippedToolCall(step, skippedCall);
73934
+ const skippedTask = await prepareSkippedToolCall(batchStep, skippedCall);
73787
73935
  pendingResults.push(scheduler.add(skippedTask));
73788
73936
  }
73789
73937
  break;
73790
73938
  }
73791
73939
  }
73792
73940
  for (const pendingResult of pendingResults) {
73793
- const result = await finalizePendingToolResult(step, await pendingResult);
73941
+ const result = await finalizePendingToolResult(batchStep, await pendingResult);
73794
73942
  if (result.stopTurn === true) stopTurn = true;
73795
73943
  await step.dispatchEvent({
73796
73944
  type: "tool.result",
@@ -73945,6 +74093,7 @@ async function runPrepareToolExecutionHook(step, call) {
73945
74093
  try {
73946
74094
  hookResult = await hooks.prepareToolExecution({
73947
74095
  toolCall,
74096
+ toolCalls: step.toolCalls,
73948
74097
  tool: call.tool,
73949
74098
  args,
73950
74099
  turnId,
@@ -73987,6 +74136,7 @@ async function runAuthorizeToolExecutionHook(step, call, args, execution) {
73987
74136
  try {
73988
74137
  return await hooks.authorizeToolExecution({
73989
74138
  toolCall: call.toolCall,
74139
+ toolCalls: step.toolCalls,
73990
74140
  tool: call.tool,
73991
74141
  args,
73992
74142
  execution,
@@ -74042,6 +74192,7 @@ async function finalizePendingToolResult(step, pendingResult) {
74042
74192
  try {
74043
74193
  const effectiveResult = coerceToolResult(await hooks.finalizeToolResult({
74044
74194
  toolCall: pendingResult.toolCall,
74195
+ toolCalls: step.toolCalls,
74045
74196
  args: pendingResult.args,
74046
74197
  result: pendingResult.result,
74047
74198
  turnId,
@@ -81688,7 +81839,7 @@ function buildSubagentDescriptions(subagents) {
81688
81839
  }
81689
81840
  //#endregion
81690
81841
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-swarm.md
81691
- 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";
81692
81843
  //#endregion
81693
81844
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-swarm.ts
81694
81845
  const DEFAULT_SUBAGENT_TYPE = "coder";
@@ -89216,6 +89367,10 @@ var TurnFlow = class {
89216
89367
  }
89217
89368
  }
89218
89369
  if (this.currentId === turnId) this.agent.usage.endTurn();
89370
+ if (ended.reason === "cancelled" && isUserCancellation(signal.reason)) this.agent.hooks?.fireAndForgetTrigger("Interrupt", { inputData: {
89371
+ turnId,
89372
+ reason: "cancelled"
89373
+ } });
89219
89374
  this.agent.emitEvent(ended);
89220
89375
  if (standalone && this.currentId === turnId) this.activeTurn = null;
89221
89376
  if (this.agent.swarmMode.shouldAutoExit) this.agent.swarmMode.exit();
@@ -90190,7 +90345,8 @@ var BashTool = class {
90190
90345
  }, timeoutMs);
90191
90346
  try {
90192
90347
  const builder = new ToolResultBuilder();
90193
- 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()]);
90194
90350
  if (timedOut) {
90195
90351
  const timeoutLabel = timeoutMs % 1e3 === 0 ? `${String(timeoutMs / 1e3)}s` : `${String(timeoutMs)}ms`;
90196
90352
  return builder.error(`Command killed by timeout (${timeoutLabel})`, { brief: `Killed by timeout (${timeoutLabel})` });
@@ -90253,7 +90409,7 @@ var BashTool = class {
90253
90409
  const info = backgroundManager.getTask(taskId);
90254
90410
  if (info && info.status === "running") backgroundManager.stop(taskId, "Timed out");
90255
90411
  })();
90256
- }, timeoutMs);
90412
+ }, timeoutMs).unref?.();
90257
90413
  const status = backgroundManager.getTask(taskId).status;
90258
90414
  const builder = new ToolResultBuilder();
90259
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.
@@ -90263,14 +90419,21 @@ human_shell_hint: Tell the human to run /tasks to open the interactive backgroun
90263
90419
  return builder.ok("Background task started", { brief: `Started ${taskId}` });
90264
90420
  }
90265
90421
  };
90266
- async function readStreamIntoBuilder(stream, builder) {
90422
+ async function readStreamIntoBuilder(stream, builder, suppressPrematureClose) {
90267
90423
  const decoder = new StringDecoder("utf8");
90268
- for await (const chunk of stream) {
90269
- const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
90270
- 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;
90271
90431
  }
90272
90432
  builder.write(decoder.end());
90273
90433
  }
90434
+ function isPrematureCloseError(error) {
90435
+ return error instanceof Error && error.code === "ERR_STREAM_PREMATURE_CLOSE";
90436
+ }
90274
90437
  function shellQuote(s) {
90275
90438
  return `'${s.replaceAll("'", "'\\''")}'`;
90276
90439
  }
@@ -121053,6 +121216,32 @@ var McpConnectionManager = class {
121053
121216
  this.initialLoad = initialLoad;
121054
121217
  return initialLoad;
121055
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
+ }
121056
121245
  waitForInitialLoad(signal) {
121057
121246
  signal?.throwIfAborted();
121058
121247
  if (signal === void 0) return this.initialLoad;
@@ -121402,6 +121591,20 @@ function mergeCallerMcpServers(base, callerServers) {
121402
121591
  //#endregion
121403
121592
  //#region ../../packages/agent-core/src/session/index.ts
121404
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
+ }
121405
121608
  var Session$1 = class {
121406
121609
  options;
121407
121610
  rpc;
@@ -121493,6 +121696,7 @@ var Session$1 = class {
121493
121696
  async close() {
121494
121697
  try {
121495
121698
  await Promise.allSettled(Array.from(this.readyAgents(), async (agent) => agent.cron?.stop()));
121699
+ await this.cancelActiveTurnsOnClose();
121496
121700
  await this.stopBackgroundTasksOnExit();
121497
121701
  await this.flushMetadata();
121498
121702
  await this.triggerSessionEnd("exit");
@@ -121516,12 +121720,46 @@ var Session$1 = class {
121516
121720
  }
121517
121721
  }
121518
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
+ }
121519
121757
  async stopBackgroundTasksOnExit() {
121520
121758
  if (resolveConfigValue({
121521
121759
  env: process.env,
121522
121760
  envKey: BACKGROUND_KEEP_ALIVE_ON_EXIT_ENV,
121523
121761
  configValue: this.options.background?.keepAliveOnExit,
121524
- defaultValue: true,
121762
+ defaultValue: false,
121525
121763
  parseEnv: parseBooleanEnv
121526
121764
  })) return;
121527
121765
  await Promise.all(Array.from(this.readyAgents(), (agent) => agent.background.stopAll("Session closed")));
@@ -136903,6 +137141,7 @@ var ProviderManager = class {
136903
137141
  providerName,
136904
137142
  provider,
136905
137143
  modelCapabilities: resolveModelCapabilities(alias, provider),
137144
+ alwaysThinking: (alias.capabilities ?? []).some((c) => c.trim().toLowerCase() === "always_thinking"),
136906
137145
  maxOutputSize: alias.maxOutputSize
136907
137146
  };
136908
137147
  }
@@ -138287,9 +138526,11 @@ var LocalKaos = class LocalKaos {
138287
138526
  name = "local";
138288
138527
  osEnv;
138289
138528
  _cwd;
138290
- constructor(osEnv, cwd) {
138529
+ _envLayers;
138530
+ constructor(osEnv, cwd, envLayers = []) {
138291
138531
  this._cwd = normalize(cwd ?? process.cwd());
138292
138532
  this.osEnv = osEnv;
138533
+ this._envLayers = envLayers;
138293
138534
  }
138294
138535
  /**
138295
138536
  * Construct a fresh `LocalKaos` after probing the host environment.
@@ -138302,7 +138543,10 @@ var LocalKaos = class LocalKaos {
138302
138543
  return new LocalKaos(await detectEnvironmentFromNode());
138303
138544
  }
138304
138545
  withCwd(cwd) {
138305
- 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]);
138306
138550
  }
138307
138551
  _resolvePath(path) {
138308
138552
  if (isAbsolute(path)) return normalize(path);
@@ -138495,7 +138739,8 @@ var LocalKaos = class LocalKaos {
138495
138739
  "pipe",
138496
138740
  "pipe"
138497
138741
  ],
138498
- detached: !isWindows
138742
+ detached: !isWindows,
138743
+ env: this._buildExecEnv()
138499
138744
  });
138500
138745
  await waitForSpawn(child);
138501
138746
  return new LocalProcess(child);
@@ -138511,11 +138756,20 @@ var LocalKaos = class LocalKaos {
138511
138756
  "pipe"
138512
138757
  ],
138513
138758
  detached: !isWindows,
138514
- env
138759
+ env: this._buildExecEnv(env)
138515
138760
  });
138516
138761
  await waitForSpawn(child);
138517
138762
  return new LocalProcess(child);
138518
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
+ }
138519
138773
  };
138520
138774
  function waitForSpawn(child) {
138521
138775
  return new Promise((resolve, reject) => {
@@ -139601,8 +139855,8 @@ var KimiHarness = class {
139601
139855
  get interactiveAgentId() {
139602
139856
  return this.rpc.interactiveAgentId;
139603
139857
  }
139604
- set interactiveAgentId(agentId) {
139605
- this.rpc.interactiveAgentId = agentId;
139858
+ withInteractiveAgent(agentId, fn) {
139859
+ return this.rpc.withInteractiveAgent(agentId, fn);
139606
139860
  }
139607
139861
  track(event, properties) {
139608
139862
  this.telemetry.track(event, properties);
@@ -142054,7 +142308,19 @@ function managedModelKey(modelId) {
142054
142308
  }
142055
142309
  function capabilitiesForModel$1(model) {
142056
142310
  const caps = /* @__PURE__ */ new Set();
142057
- 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
+ }
142058
142324
  if (model.supportsImageIn) caps.add("image_in");
142059
142325
  if (model.supportsVideoIn) caps.add("video_in");
142060
142326
  if (model.supportsToolUse ?? true) caps.add("tool_use");
@@ -142197,9 +142463,13 @@ function toModelInfo$1(item) {
142197
142463
  supportsImageIn: Boolean(item["supports_image_in"]),
142198
142464
  supportsVideoIn: Boolean(item["supports_video_in"]),
142199
142465
  supportsToolUse,
142466
+ supportsThinkingType: parseSupportsThinkingType(item["supports_thinking_type"]),
142200
142467
  displayName: normalizedDisplayName
142201
142468
  };
142202
142469
  }
142470
+ function parseSupportsThinkingType(value) {
142471
+ return value === "only" || value === "no" || value === "both" ? value : void 0;
142472
+ }
142203
142473
  async function fetchManagedKimiCodeModels(options) {
142204
142474
  const fetchImpl = options.fetchImpl ?? fetch;
142205
142475
  const baseUrl = defaultBaseUrl(options.baseUrl);
@@ -142288,6 +142558,11 @@ function applyManagedKimiCodeLogoutConfig(config) {
142288
142558
  if (Object.keys(config.services).length === 0) config.services = void 0;
142289
142559
  }
142290
142560
  }
142561
+ function forcedThinking(model, fallback) {
142562
+ if (model?.supportsThinkingType === "only") return true;
142563
+ if (model?.supportsThinkingType === "no") return false;
142564
+ return fallback;
142565
+ }
142291
142566
  function selectDefaultModel(config, models, options) {
142292
142567
  const firstModel = models[0];
142293
142568
  if (firstModel === void 0) throw new Error("No models available for Kimi Code.");
@@ -142298,12 +142573,12 @@ function selectDefaultModel(config, models, options) {
142298
142573
  const preservedModel = managedModels.get(currentDefault);
142299
142574
  return {
142300
142575
  modelKey: currentDefault,
142301
- thinking: config.defaultThinking ?? preservedModel?.supportsReasoning ?? false
142576
+ thinking: forcedThinking(preservedModel, config.defaultThinking ?? preservedModel?.supportsReasoning ?? false)
142302
142577
  };
142303
142578
  }
142304
142579
  return {
142305
142580
  modelKey: managedModelKey(firstModel.id),
142306
- thinking: config.defaultThinking ?? firstModel.supportsReasoning
142581
+ thinking: forcedThinking(firstModel, config.defaultThinking ?? firstModel.supportsReasoning)
142307
142582
  };
142308
142583
  }
142309
142584
  function canPreserveDefaultModel(existingModels, defaultModel, managedModels) {
@@ -142415,12 +142690,25 @@ function toModelInfo(item) {
142415
142690
  supportsImageIn: Boolean(item["supports_image_in"]),
142416
142691
  supportsVideoIn: Boolean(item["supports_video_in"]),
142417
142692
  supportsToolUse,
142693
+ supportsThinkingType: parseSupportsThinkingType(item["supports_thinking_type"]),
142418
142694
  displayName: normalizedDisplayName
142419
142695
  };
142420
142696
  }
142421
142697
  function capabilitiesForModel(model) {
142422
142698
  const caps = /* @__PURE__ */ new Set();
142423
- 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
+ }
142424
142712
  if (model.supportsImageIn) caps.add("image_in");
142425
142713
  if (model.supportsVideoIn) caps.add("video_in");
142426
142714
  if (model.supportsToolUse ?? true) caps.add("tool_use");
@@ -143052,10 +143340,16 @@ var KimiAuthFacade = class {
143052
143340
  //#region ../../packages/node-sdk/src/rpc.ts
143053
143341
  const MAIN_AGENT_ID$2 = "main";
143054
143342
  var SDKRpcClientBase = class {
143055
- interactiveAgentId = MAIN_AGENT_ID$2;
143343
+ interactiveAgentScope = new AsyncLocalStorage();
143056
143344
  eventListeners = /* @__PURE__ */ new Set();
143057
143345
  approvalHandlers = /* @__PURE__ */ new Map();
143058
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
+ }
143059
143353
  async createSession(input) {
143060
143354
  const rpc = await this.getRpc();
143061
143355
  const { planMode, ...coreInput } = input;
@@ -144353,6 +144647,7 @@ const FEEDBACK_VERSION_PREFIX = "kimi-code-";
144353
144647
  const FEEDBACK_TELEMETRY_EVENT = "feedback_submitted";
144354
144648
  const KIMI_CODE_CDN_BASE = "https://code.kimi.com/kimi-code";
144355
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";
144356
144651
  const KIMI_CODE_PLUGIN_MARKETPLACE_URL = `${KIMI_CODE_CDN_BASE}/plugins/marketplace.json`;
144357
144652
  const KIMI_CODE_PLUGIN_MARKETPLACE_URL_ENV = "KIMI_CODE_PLUGIN_MARKETPLACE_URL";
144358
144653
  const KIMI_CODE_INSTALL_SH_URL = `${KIMI_CODE_CDN_BASE}/install.sh`;
@@ -150494,6 +150789,9 @@ var AcpKaos = class AcpKaos {
150494
150789
  withCwd(cwd) {
150495
150790
  return new AcpKaos(this.conn, this.sessionId, this.inner.withCwd(cwd));
150496
150791
  }
150792
+ withEnv(env) {
150793
+ return new AcpKaos(this.conn, this.sessionId, this.inner.withEnv(env));
150794
+ }
150497
150795
  stat(path, options) {
150498
150796
  return this.inner.stat(path, options);
150499
150797
  }
@@ -150514,13 +150812,14 @@ var AcpKaos = class AcpKaos {
150514
150812
  * should be routed through a non-ACP Kaos.
150515
150813
  */
150516
150814
  async readText(path, _options) {
150815
+ const rpcPath = this.toClientPath(path);
150517
150816
  try {
150518
150817
  return (await this.conn.readTextFile({
150519
150818
  sessionId: this.sessionId,
150520
- path
150819
+ path: rpcPath
150521
150820
  })).content;
150522
150821
  } catch (err) {
150523
- throw wrapKaosError(`acp: readTextFile failed for ${path}`, err);
150822
+ throw wrapKaosError(`acp: readTextFile failed for ${rpcPath}`, err);
150524
150823
  }
150525
150824
  }
150526
150825
  /**
@@ -150598,16 +150897,21 @@ var AcpKaos = class AcpKaos {
150598
150897
  return data.byteLength;
150599
150898
  }
150600
150899
  async acpWrite(path, content) {
150900
+ const rpcPath = this.toClientPath(path);
150601
150901
  try {
150602
150902
  await this.conn.writeTextFile({
150603
150903
  sessionId: this.sessionId,
150604
- path,
150904
+ path: rpcPath,
150605
150905
  content
150606
150906
  });
150607
150907
  } catch (err) {
150608
- throw wrapKaosError(`acp: writeTextFile failed for ${path}`, err);
150908
+ throw wrapKaosError(`acp: writeTextFile failed for ${rpcPath}`, err);
150609
150909
  }
150610
150910
  }
150911
+ toClientPath(path) {
150912
+ if (this.inner.pathClass() !== "win32") return path;
150913
+ return path.replaceAll("/", "\\");
150914
+ }
150611
150915
  exec(...args) {
150612
150916
  return this.inner.exec(...args);
150613
150917
  }
@@ -151654,13 +151958,24 @@ function acpModeToToggles(id) {
151654
151958
  */
151655
151959
  const TOGGLEABLE_THINKING_MODELS = new Set(["kimi-for-coding", "kimi-code"]);
151656
151960
  function deriveThinkingSupported(alias) {
151657
- if ((alias.capabilities ?? []).includes("thinking")) return true;
151961
+ const declared = alias.capabilities ?? [];
151962
+ if (declared.includes("thinking") || declared.includes("always_thinking")) return true;
151658
151963
  const lower = alias.model.toLowerCase();
151659
151964
  if (lower.includes("thinking") || lower.includes("reason")) return true;
151660
151965
  if (TOGGLEABLE_THINKING_MODELS.has(alias.model)) return true;
151661
151966
  return false;
151662
151967
  }
151663
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
+ /**
151664
151979
  * Project `harness.getConfig().models` into a flat catalog. Returns an
151665
151980
  * empty array when the harness has no models configured, when
151666
151981
  * `getConfig` is missing on the harness (partial test stubs), or when
@@ -151681,7 +151996,8 @@ async function listModelsFromHarness(harness) {
151681
151996
  for (const [id, alias] of Object.entries(models)) out.push({
151682
151997
  id,
151683
151998
  name: alias.displayName ?? alias.model ?? id,
151684
- thinkingSupported: deriveThinkingSupported(alias)
151999
+ thinkingSupported: deriveThinkingSupported(alias),
152000
+ alwaysThinking: deriveAlwaysThinking(alias)
151685
152001
  });
151686
152002
  return out;
151687
152003
  }
@@ -151740,8 +152056,26 @@ function buildModelOption(models, currentBaseModelId) {
151740
152056
  * currently-selected model has `thinkingSupported === false`, the
151741
152057
  * snapshot omits it entirely (dynamic visibility), so the client never
151742
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.
151743
152066
  */
151744
- 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
+ };
151745
152079
  return {
151746
152080
  type: "select",
151747
152081
  id: "thinking",
@@ -151808,9 +152142,11 @@ function buildModeOption(currentModeId) {
151808
152142
  */
151809
152143
  async function buildSessionConfigOptions(harness, currentBaseModelId, currentThinkingEnabled, currentModeId) {
151810
152144
  const models = await listModelsFromHarness(harness);
151811
- 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;
151812
152148
  const out = [buildModelOption(models, currentBaseModelId)];
151813
- if (showThinking) out.push(buildThinkingOption(currentThinkingEnabled));
152149
+ if (showThinking) out.push(buildThinkingOption(alwaysThinking || currentThinkingEnabled, alwaysThinking));
151814
152150
  out.push(buildModeOption(currentModeId));
151815
152151
  return out;
151816
152152
  }
@@ -152156,11 +152492,25 @@ var AcpSession = class {
152156
152492
  * carries a fresh snapshot.
152157
152493
  */
152158
152494
  async setThinking(enabled) {
152495
+ if (!enabled && await this.currentModelAlwaysThinking()) {
152496
+ this.currentThinkingEnabledInternal = true;
152497
+ await this.emitConfigOptionUpdate();
152498
+ return;
152499
+ }
152159
152500
  if (typeof this.session.setThinking === "function") await this.session.setThinking(enabled ? THINKING_ON_LEVEL : THINKING_OFF_LEVEL);
152160
152501
  this.currentThinkingEnabledInternal = enabled;
152161
152502
  await this.emitConfigOptionUpdate();
152162
152503
  }
152163
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
+ /**
152164
152514
  * Forward an ACP `session/set_mode` request to the underlying SDK
152165
152515
  * session.
152166
152516
  *
@@ -155365,7 +155715,6 @@ const LLM_NOT_SET_MESSAGE = "LLM not set, send \"/login\" to login";
155365
155715
  const NO_ACTIVE_SESSION_MESSAGE = "No active session. Send /login to login.";
155366
155716
  const CTRL_D_HINT = "Press Ctrl+D again to exit";
155367
155717
  const CTRL_C_HINT = "Press Ctrl+C again to exit";
155368
- const MAIN_AGENT_ID = "main";
155369
155718
  const OAUTH_LOGIN_REQUIRED_STARTUP_NOTICE = "OAuth login expired. Send /login to login.";
155370
155719
  const EXIT_CONFIRM_WINDOW_MS = 1500;
155371
155720
  function isManagedUsageProvider(providerKey) {
@@ -155726,7 +156075,7 @@ var SearchableList = class {
155726
156075
  * addChild(picker), setFocus(picker); the picker invokes `onSelect` or
155727
156076
  * `onCancel`, and the host tears it down.
155728
156077
  */
155729
- function wrapDescription(text, width) {
156078
+ function wrapDescription$1(text, width) {
155730
156079
  const maxWidth = Math.max(1, width);
155731
156080
  const words = text.trim().split(/\s+/).filter((word) => word.length > 0);
155732
156081
  const lines = [];
@@ -155811,7 +156160,7 @@ var ChoicePickerComponent = class extends Container {
155811
156160
  lines.push(line);
155812
156161
  if (opt.description !== void 0 && opt.description.length > 0) {
155813
156162
  const descriptionWidth = Math.max(1, width - 4);
155814
- 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}`));
155815
156164
  }
155816
156165
  }
155817
156166
  lines.push("");
@@ -156082,9 +156431,10 @@ var ModelSelectorComponent = class extends Container {
156082
156431
  }
156083
156432
  renderThinkingControl(choice) {
156084
156433
  const segment = (label, active) => active ? currentTheme.boldFg("primary", `[ ${label} ]`) : currentTheme.fg("text", ` ${label} `);
156434
+ const unavailable = (label) => currentTheme.fg("textMuted", ` ${label} (Unsupported) `);
156085
156435
  const availability = thinkingAvailability(choice.model);
156086
- if (availability === "always-on") return ` ${segment("Always on", true)}`;
156087
- 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)}`;
156088
156438
  const draft = this.draftFor(choice);
156089
156439
  return ` ${segment("On", draft)} ${segment("Off", !draft)}`;
156090
156440
  }
@@ -156603,7 +156953,7 @@ var EditorSelectorComponent = class extends ChoicePickerComponent {
156603
156953
  };
156604
156954
  //#endregion
156605
156955
  //#region src/tui/components/dialogs/experiments-selector.ts
156606
- const ELLIPSIS$7 = "…";
156956
+ const ELLIPSIS$8 = "…";
156607
156957
  var ExperimentsSelectorComponent = class extends Container {
156608
156958
  focused = false;
156609
156959
  opts;
@@ -156662,7 +157012,7 @@ var ExperimentsSelectorComponent = class extends Container {
156662
157012
  else if (view.page.end < view.items.length) lines.push(currentTheme.fg("textMuted", ` ▼ ${String(view.items.length - view.page.end)} more`));
156663
157013
  lines.push(this.renderApplyButton());
156664
157014
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
156665
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$7));
157015
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$8));
156666
157016
  }
156667
157017
  toggleDraft(feature) {
156668
157018
  if (isLocked(feature)) return;
@@ -156735,7 +157085,7 @@ function wrapText(text, width) {
156735
157085
  continue;
156736
157086
  }
156737
157087
  if (current.length > 0) lines.push(current);
156738
- current = visibleWidth(word) <= maxWidth ? word : truncateToWidth(word, maxWidth, ELLIPSIS$7);
157088
+ current = visibleWidth(word) <= maxWidth ? word : truncateToWidth(word, maxWidth, ELLIPSIS$8);
156739
157089
  }
156740
157090
  if (current.length > 0) lines.push(current);
156741
157091
  return lines;
@@ -158106,7 +158456,7 @@ var GoalStartPermissionPromptComponent = class extends StartPermissionPromptComp
158106
158456
  //#region src/tui/components/dialogs/goal-queue-manager.ts
158107
158457
  const MAX_GOAL_OBJECTIVE_LENGTH$2 = 4e3;
158108
158458
  const MAX_EDIT_INPUT_LINES = 8;
158109
- const ELLIPSIS$6 = "…";
158459
+ const ELLIPSIS$7 = "…";
158110
158460
  const BRACKET_PASTE_START$1 = "\x1B[200~";
158111
158461
  const BRACKET_PASTE_END$1 = "\x1B[201~";
158112
158462
  const SHIFT_ENTER_LEGACY = "\x1B\r";
@@ -158196,7 +158546,7 @@ var GoalQueueManagerComponent = class extends Container {
158196
158546
  }
158197
158547
  lines.push("");
158198
158548
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
158199
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$6));
158549
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$7));
158200
158550
  }
158201
158551
  renderGoal(goal, index, selected, width) {
158202
158552
  const moving = goal.id === this.movingGoalId;
@@ -158207,7 +158557,7 @@ var GoalQueueManagerComponent = class extends Container {
158207
158557
  const labelWidth = visibleWidth(labelPrefix);
158208
158558
  const stateWidth = visibleWidth(stateLabel);
158209
158559
  const objectiveWidth = Math.max(1, width - 5 - labelWidth - stateWidth);
158210
- const objective = truncateToWidth(formatListObjective(goal.objective), objectiveWidth, ELLIPSIS$6);
158560
+ const objective = truncateToWidth(formatListObjective(goal.objective), objectiveWidth, ELLIPSIS$7);
158211
158561
  let line = prefix + (selected ? (text) => currentTheme.boldFg("primary", text) : (text) => currentTheme.fg("text", text))(labelPrefix + objective);
158212
158562
  if (moving) line += currentTheme.fg("success", stateLabel);
158213
158563
  return line;
@@ -158278,10 +158628,10 @@ var GoalQueueEditDialogComponent = class extends Container {
158278
158628
  const innerWidth = Math.max(10, safeWidth - 4);
158279
158629
  const pad = " ";
158280
158630
  const border = (s) => currentTheme.fg("primary", s);
158281
- const title = truncateToWidth(currentTheme.boldFg("textStrong", "Edit upcoming goal"), innerWidth, ELLIPSIS$6);
158282
- 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);
158283
158633
  const inputLines = this.input.render(innerWidth);
158284
- 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);
158285
158635
  const contentLines = [
158286
158636
  title,
158287
158637
  "",
@@ -158387,14 +158737,14 @@ var MultilineGoalInput = class {
158387
158737
  const cursor = this.cursorLocation();
158388
158738
  const range = visibleLineRange(logicalLines.length, cursor.line);
158389
158739
  const rendered = [];
158390
- 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));
158391
158741
  for (let lineIndex = range.start; lineIndex < range.end; lineIndex++) {
158392
158742
  const line = logicalLines[lineIndex] ?? "";
158393
158743
  const prefix = lineIndex === 0 ? "> " : " ";
158394
158744
  rendered.push(lineIndex === cursor.line ? renderCursorLine(line, cursor.column, prefix, safeWidth, this.focused) : renderTextLine(line, prefix, safeWidth));
158395
158745
  }
158396
158746
  const remaining = logicalLines.length - range.end;
158397
- 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));
158398
158748
  return rendered;
158399
158749
  }
158400
158750
  insert(text) {
@@ -158534,7 +158884,7 @@ function visibleLineRange(totalLines, cursorLine) {
158534
158884
  }
158535
158885
  function renderTextLine(line, prefix, width) {
158536
158886
  const prefixWidth = visibleWidth(prefix);
158537
- 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);
158538
158888
  }
158539
158889
  function renderCursorLine(line, column, prefix, width, focused) {
158540
158890
  const prefixWidth = visibleWidth(prefix);
@@ -160102,7 +160452,7 @@ const OVERVIEW_PLUGIN_PREFIX = "plugin:";
160102
160452
  const MCP_SERVER_PREFIX = "mcp:";
160103
160453
  const REMOVE_CONFIRM_CANCEL = "cancel";
160104
160454
  const REMOVE_CONFIRM_REMOVE = "remove";
160105
- const ELLIPSIS$5 = "…";
160455
+ const ELLIPSIS$6 = "…";
160106
160456
  var PluginsOverviewSelectorComponent = class extends Container {
160107
160457
  focused = false;
160108
160458
  opts;
@@ -160195,7 +160545,7 @@ var PluginsOverviewSelectorComponent = class extends Container {
160195
160545
  for (let i = 0; i < actionItems.length; i++) lines.push(...this.renderItem(actionItems[i], pluginItems.length + i, width));
160196
160546
  lines.push("");
160197
160547
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
160198
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$5));
160548
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$6));
160199
160549
  }
160200
160550
  renderItem(item, index, width) {
160201
160551
  const selected = index === this.selectedIndex;
@@ -160267,7 +160617,7 @@ var PluginMarketplaceSelectorComponent = class extends Container {
160267
160617
  for (let i = 0; i < actions.length; i++) lines.push(...this.renderItem(actions[i], entries.length + i, width));
160268
160618
  lines.push("");
160269
160619
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
160270
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$5));
160620
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$6));
160271
160621
  }
160272
160622
  renderItem(item, index, width) {
160273
160623
  const selected = index === this.selectedIndex;
@@ -160346,7 +160696,7 @@ var PluginMcpSelectorComponent = class extends Container {
160346
160696
  for (let i = 0; i < actionItems.length; i++) lines.push(...this.renderItem(actionItems[i], serverItems.length + i, width));
160347
160697
  lines.push("");
160348
160698
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
160349
- return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$5));
160699
+ return lines.map((line) => truncateToWidth(line, width, ELLIPSIS$6));
160350
160700
  }
160351
160701
  renderItem(item, index, width) {
160352
160702
  const selected = index === this.selectedIndex;
@@ -160526,7 +160876,7 @@ function wrapOverviewDescription(text, width) {
160526
160876
  continue;
160527
160877
  }
160528
160878
  if (current.length > 0) lines.push(current);
160529
- current = visibleWidth(word) <= maxWidth ? word : truncateToWidth(word, maxWidth, ELLIPSIS$5);
160879
+ current = visibleWidth(word) <= maxWidth ? word : truncateToWidth(word, maxWidth, ELLIPSIS$6);
160530
160880
  }
160531
160881
  if (current.length > 0) lines.push(current);
160532
160882
  return lines;
@@ -161017,15 +161367,23 @@ function applyRuntimeConfig(host, config) {
161017
161367
  }
161018
161368
  //#endregion
161019
161369
  //#region src/tui/components/dialogs/swarm-start-permission-prompt.ts
161020
- const OPTIONS = [{
161021
- value: "auto",
161022
- label: "Switch to Auto and start",
161023
- description: "Best for swarm tasks. Tools are approved automatically, and questions are skipped."
161024
- }, {
161025
- value: "manual",
161026
- label: "Start in Manual",
161027
- description: "Keep approvals on. Kimi Code may stop and wait for you during the swarm task."
161028
- }];
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
+ ];
161029
161387
  const NOTICE_LINES = [
161030
161388
  "Manual mode asks you before Kimi Code runs commands, edits files, or takes other risky actions.",
161031
161389
  "Manual mode can block swarm work while agents are running.",
@@ -161103,7 +161461,7 @@ function showSwarmStartPermissionPrompt(host, commandText, cancelStatus, onSelec
161103
161461
  }));
161104
161462
  }
161105
161463
  async function startSwarmWithPermission(host, prompt, choice) {
161106
- if (choice === "auto") {
161464
+ if (choice === "auto" || choice === "yolo") {
161107
161465
  if (!await setPermissionForSwarm(host, choice)) return;
161108
161466
  }
161109
161467
  await startSwarmTask(host, prompt);
@@ -161134,7 +161492,7 @@ async function applySwarmMode(host, enabled, commandText) {
161134
161492
  }
161135
161493
  if (enabled && host.state.appState.permissionMode === "manual") {
161136
161494
  showSwarmStartPermissionPrompt(host, commandText, "Swarm mode not enabled.", async (choice) => {
161137
- if (choice === "auto" && !await setPermissionForSwarm(host, choice)) return;
161495
+ if ((choice === "auto" || choice === "yolo") && !await setPermissionForSwarm(host, choice)) return;
161138
161496
  if (!await setSwarmMode(host, true, "manual")) return;
161139
161497
  renderSwarmModeMarker(host, "active");
161140
161498
  });
@@ -166761,15 +167119,20 @@ function installPromptTerminationCleanup(promptProcess, cleanup) {
166761
167119
  };
166762
167120
  const onSigint = () => exitAfterCleanup("SIGINT");
166763
167121
  const onSigterm = () => exitAfterCleanup("SIGTERM");
167122
+ const onSighup = () => exitAfterCleanup("SIGHUP");
166764
167123
  promptProcess.once("SIGINT", onSigint);
166765
167124
  promptProcess.once("SIGTERM", onSigterm);
167125
+ promptProcess.once("SIGHUP", onSighup);
166766
167126
  return () => {
166767
167127
  promptProcess.off("SIGINT", onSigint);
166768
167128
  promptProcess.off("SIGTERM", onSigterm);
167129
+ promptProcess.off("SIGHUP", onSighup);
166769
167130
  };
166770
167131
  }
166771
167132
  function signalExitCode(signal) {
166772
- return signal === "SIGINT" ? 130 : 143;
167133
+ if (signal === "SIGINT") return 130;
167134
+ if (signal === "SIGHUP") return 129;
167135
+ return 143;
166773
167136
  }
166774
167137
  function runPromptTurn(session, prompt, outputFormat, stdout, stderr) {
166775
167138
  let activeTurnId;
@@ -167735,16 +168098,16 @@ function buildNumericHint(count) {
167735
168098
  * emulator's "snap to bottom on stdout" reflex, causing flicker and an
167736
168099
  * unscrollable history pane.
167737
168100
  */
167738
- const ELLIPSIS$4 = "…";
168101
+ const ELLIPSIS$5 = "…";
167739
168102
  function padToWidth$2(line, width) {
167740
168103
  const w = visibleWidth(line);
167741
168104
  if (w === width) return line;
167742
- if (w > width) return truncateToWidth(line, width, ELLIPSIS$4);
168105
+ if (w > width) return truncateToWidth(line, width, ELLIPSIS$5);
167743
168106
  return line + " ".repeat(width - w);
167744
168107
  }
167745
168108
  function fitExactly$2(line, width) {
167746
168109
  let s = line;
167747
- if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$4);
168110
+ if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$5);
167748
168111
  return padToWidth$2(s, width);
167749
168112
  }
167750
168113
  var ApprovalPreviewViewer = class extends Container {
@@ -168601,7 +168964,7 @@ var QuestionDialogComponent = class extends Container {
168601
168964
  /**
168602
168965
  * SessionPicker — pi-tui version of the session selection dialog.
168603
168966
  */
168604
- const ELLIPSIS$3 = "…";
168967
+ const ELLIPSIS$4 = "…";
168605
168968
  function formatRelativeTime$1(ts) {
168606
168969
  if (!Number.isFinite(ts) || ts <= 0) return "";
168607
168970
  const diffSec = Math.floor(Math.max(0, Date.now() - ts) / 1e3);
@@ -168621,7 +168984,7 @@ function homeAlias(path) {
168621
168984
  function truncatePathLeft(path, maxWidth) {
168622
168985
  if (maxWidth <= 0) return "";
168623
168986
  if (visibleWidth(path) <= maxWidth) return path;
168624
- if (maxWidth === 1) return ELLIPSIS$3;
168987
+ if (maxWidth === 1) return ELLIPSIS$4;
168625
168988
  const segments = [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(path)].map((s) => s.segment);
168626
168989
  let used = 0;
168627
168990
  const budget = maxWidth - 1;
@@ -168634,7 +168997,7 @@ function truncatePathLeft(path, maxWidth) {
168634
168997
  used += w;
168635
168998
  i--;
168636
168999
  }
168637
- return ELLIPSIS$3 + segments.slice(i + 1).join("");
169000
+ return ELLIPSIS$4 + segments.slice(i + 1).join("");
168638
169001
  }
168639
169002
  function singleLine$1(text) {
168640
169003
  return text.replaceAll(/\s+/g, " ").trim();
@@ -168686,26 +169049,26 @@ var SessionPickerComponent = class extends Container {
168686
169049
  if (matchesKey(data, Key.down)) this.selectedIndex = Math.min(this.sessions.length - 1, this.selectedIndex + 1);
168687
169050
  }
168688
169051
  render(width) {
168689
- return this.renderLines(width).map((line) => truncateToWidth(line, width, ELLIPSIS$3));
169052
+ return this.renderLines(width).map((line) => truncateToWidth(line, width, ELLIPSIS$4));
168690
169053
  }
168691
169054
  renderLines(width) {
168692
169055
  const lines = [currentTheme.fg("primary", "─".repeat(width))];
168693
169056
  if (this.loading) {
168694
- lines.push(currentTheme.boldFg("primary", truncateToWidth("Sessions", width, ELLIPSIS$3)));
168695
- 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)));
168696
169059
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
168697
169060
  return lines;
168698
169061
  }
168699
169062
  if (this.sessions.length === 0) {
168700
- lines.push(currentTheme.boldFg("primary", truncateToWidth("Sessions", width, ELLIPSIS$3)));
168701
- 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)));
168702
169065
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
168703
169066
  return lines;
168704
169067
  }
168705
169068
  const headerLabel = "Sessions ";
168706
169069
  const headerHint = "↑↓ navigate · Enter select · Esc cancel";
168707
169070
  const labelWidth = visibleWidth(headerLabel);
168708
- const shownHint = truncateToWidth(headerHint, Math.max(0, width - labelWidth), ELLIPSIS$3);
169071
+ const shownHint = truncateToWidth(headerHint, Math.max(0, width - labelWidth), ELLIPSIS$4);
168709
169072
  lines.push(currentTheme.boldFg("primary", headerLabel) + currentTheme.fg("textMuted", shownHint));
168710
169073
  lines.push("");
168711
169074
  const visibleStart = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisibleSessions / 2), Math.max(0, this.sessions.length - this.maxVisibleSessions)));
@@ -168720,7 +169083,7 @@ var SessionPickerComponent = class extends Container {
168720
169083
  if (this.sessions.length > visibleSessions.length) {
168721
169084
  lines.push("");
168722
169085
  const footer = `Showing ${String(visibleStart + 1)}-${String(visibleStart + visibleSessions.length)} of ${String(this.sessions.length)} sessions`;
168723
- lines.push(currentTheme.fg("textMuted", truncateToWidth(footer, width, ELLIPSIS$3)));
169086
+ lines.push(currentTheme.fg("textMuted", truncateToWidth(footer, width, ELLIPSIS$4)));
168724
169087
  }
168725
169088
  lines.push(currentTheme.fg("primary", "─".repeat(width)));
168726
169089
  return lines;
@@ -168741,7 +169104,7 @@ var SessionPickerComponent = class extends Container {
168741
169104
  const trailingWidth = visibleWidth(trailingParts.length > 0 ? " " + trailingParts.join(" ") : "");
168742
169105
  const headerPrefixWidth = visibleWidth(pointer) + 1;
168743
169106
  const titleBudget = Math.max(8, width - headerPrefixWidth - trailingWidth);
168744
- const shownTitle = truncateToWidth(singleLine$1(titleSource), titleBudget, ELLIPSIS$3);
169107
+ const shownTitle = truncateToWidth(singleLine$1(titleSource), titleBudget, ELLIPSIS$4);
168745
169108
  let header = currentTheme.fg(isSelected ? "primary" : "textDim", pointer + " ");
168746
169109
  header += titleStyle(shownTitle);
168747
169110
  if (time.length > 0) header += " " + currentTheme.fg("textDim", time);
@@ -168756,7 +169119,7 @@ var SessionPickerComponent = class extends Container {
168756
169119
  const dirWidth = visibleWidth(aliasedDir);
168757
169120
  if (idLineWidth + metaGapWidth + dirWidth <= width) card.push(indent + currentTheme.fg("textMuted", fullId) + currentTheme.fg("textDim", metaGap) + currentTheme.fg("textMuted", aliasedDir));
168758
169121
  else {
168759
- 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)));
168760
169123
  const dir = truncatePathLeft(aliasedDir, Math.max(8, width - indentWidth));
168761
169124
  card.push(indent + currentTheme.fg("textMuted", dir));
168762
169125
  }
@@ -168765,7 +169128,7 @@ var SessionPickerComponent = class extends Container {
168765
169128
  const promptMarker = "› ";
168766
169129
  const promptMarkerWidth = visibleWidth(promptMarker);
168767
169130
  const promptBudget = Math.max(8, width - indentWidth - promptMarkerWidth);
168768
- const promptText = truncateToWidth(singleLine$1(rawPrompt), promptBudget, ELLIPSIS$3);
169131
+ const promptText = truncateToWidth(singleLine$1(rawPrompt), promptBudget, ELLIPSIS$4);
168769
169132
  const promptLine = indent + currentTheme.fg("textDim", promptMarker + promptText);
168770
169133
  card.push(promptLine);
168771
169134
  }
@@ -168862,7 +169225,7 @@ function restoreProviderAliases(config, aliases) {
168862
169225
  function restoreDefaultSelection(config, defaultModel, defaultThinking) {
168863
169226
  if (defaultModel === void 0 || config.models?.[defaultModel] === void 0) return;
168864
169227
  config.defaultModel = defaultModel;
168865
- config.defaultThinking = defaultThinking;
169228
+ config.defaultThinking = (config.models[defaultModel]?.capabilities ?? []).includes("always_thinking") ? true : defaultThinking;
168866
169229
  }
168867
169230
  function clampDanglingDefault(config) {
168868
169231
  if (config.defaultModel !== void 0 && config.models?.[config.defaultModel] === void 0) {
@@ -169491,13 +169854,7 @@ var BtwPanelController = class {
169491
169854
  return panel.isRunning() || panel.isEmpty();
169492
169855
  }
169493
169856
  withInteractiveAgent(agentId, fn) {
169494
- const previousAgentId = this.host.harness.interactiveAgentId;
169495
- this.host.harness.interactiveAgentId = agentId;
169496
- try {
169497
- return fn();
169498
- } finally {
169499
- this.host.harness.interactiveAgentId = previousAgentId;
169500
- }
169857
+ return this.host.harness.withInteractiveAgent(agentId, fn);
169501
169858
  }
169502
169859
  };
169503
169860
  function formatBtwTurnEnd(event) {
@@ -173610,7 +173967,7 @@ var StreamingUIController = class {
173610
173967
  * TasksBrowserApp. Snapshot view (no live tail) — content is fetched
173611
173968
  * once when the viewer opens.
173612
173969
  */
173613
- const ELLIPSIS$2 = "…";
173970
+ const ELLIPSIS$3 = "…";
173614
173971
  const STATUS_LABEL$1 = {
173615
173972
  running: "running",
173616
173973
  completed: "completed",
@@ -173632,12 +173989,12 @@ function statusColor$1(status) {
173632
173989
  function padToWidth$1(line, width) {
173633
173990
  const w = visibleWidth(line);
173634
173991
  if (w === width) return line;
173635
- if (w > width) return truncateToWidth(line, width, ELLIPSIS$2);
173992
+ if (w > width) return truncateToWidth(line, width, ELLIPSIS$3);
173636
173993
  return line + " ".repeat(width - w);
173637
173994
  }
173638
173995
  function fitExactly$1(line, width) {
173639
173996
  let s = line;
173640
- if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$2);
173997
+ if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$3);
173641
173998
  return padToWidth$1(s, width);
173642
173999
  }
173643
174000
  var TaskOutputViewer = class extends Container {
@@ -173796,7 +174153,7 @@ var TaskOutputViewer = class extends Container {
173796
174153
  * Data (tasks list, tail output) flows in via `setProps`; user actions
173797
174154
  * fire the `on*` callbacks back to the controller.
173798
174155
  */
173799
- const ELLIPSIS$1 = "…";
174156
+ const ELLIPSIS$2 = "…";
173800
174157
  const STATUS_LABEL = {
173801
174158
  running: "running",
173802
174159
  completed: "completed",
@@ -173844,13 +174201,13 @@ function singleLine(text) {
173844
174201
  function padToWidth(line, width) {
173845
174202
  const w = visibleWidth(line);
173846
174203
  if (w === width) return line;
173847
- if (w > width) return truncateToWidth(line, width, ELLIPSIS$1);
174204
+ if (w > width) return truncateToWidth(line, width, ELLIPSIS$2);
173848
174205
  return line + " ".repeat(width - w);
173849
174206
  }
173850
174207
  /** Fit `line` into exactly `width` columns, even after CJK-edge truncation. */
173851
174208
  function fitExactly(line, width) {
173852
174209
  let s = line;
173853
- if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$1);
174210
+ if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$2);
173854
174211
  return padToWidth(s, width);
173855
174212
  }
173856
174213
  function visibleTasks(tasks, filter) {
@@ -174111,7 +174468,7 @@ var TasksBrowserApp = class extends Container {
174111
174468
  const prefixWidth = visibleWidth(prefix);
174112
174469
  const descBudget = Math.max(0, innerWidth - prefixWidth - 1);
174113
174470
  if (descBudget < 4) return fitExactly(prefix, innerWidth);
174114
- 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);
174115
174472
  return fitExactly(`${prefix} ${currentTheme.fg("text", desc)}`, innerWidth);
174116
174473
  }
174117
174474
  adjustScroll(visibleRows) {
@@ -174508,13 +174865,23 @@ const MAX_FALLBACK_SUGGESTIONS = 50;
174508
174865
  * completer. This wrapper also keeps Kimi-specific slash-command guards.
174509
174866
  */
174510
174867
  var FileMentionProvider = class {
174868
+ slashCommands;
174511
174869
  workDir;
174512
174870
  fdPath;
174513
174871
  inner;
174514
174872
  constructor(slashCommands, workDir, fdPath) {
174873
+ this.slashCommands = slashCommands;
174515
174874
  this.workDir = workDir;
174516
174875
  this.fdPath = fdPath;
174517
- 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);
174518
174885
  }
174519
174886
  async getSuggestions(lines, cursorLine, cursorCol, options) {
174520
174887
  const currentLine = lines[cursorLine] ?? "";
@@ -174530,6 +174897,46 @@ var FileMentionProvider = class {
174530
174897
  return getFsMentionSuggestions(this.workDir, atPrefix, options.signal);
174531
174898
  }
174532
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
+ }
174533
174940
  try {
174534
174941
  return await this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
174535
174942
  } catch {
@@ -174646,6 +175053,28 @@ function shouldSuppressSlashArgumentCompletion(textBeforeCursor, textAfterCursor
174646
175053
  if (!textBeforeCursor.includes(" ")) return false;
174647
175054
  return textAfterCursor.trimStart().length > 0;
174648
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
+ }
174649
175078
  //#endregion
174650
175079
  //#region src/tui/components/panes/activity-pane.ts
174651
175080
  var ActivityPaneComponent = class extends Container {
@@ -174666,7 +175095,7 @@ var ActivityPaneComponent = class extends Container {
174666
175095
  };
174667
175096
  //#endregion
174668
175097
  //#region src/tui/components/panes/queue-pane.ts
174669
- const ELLIPSIS = "…";
175098
+ const ELLIPSIS$1 = "…";
174670
175099
  var QueuePaneComponent = class extends Container {
174671
175100
  messages;
174672
175101
  hint;
@@ -174682,10 +175111,10 @@ var QueuePaneComponent = class extends Container {
174682
175111
  for (const item of this.messages) {
174683
175112
  const singleLine = item.text.replaceAll(/\s+/g, " ").trim();
174684
175113
  const prefix = ` ❯ `;
174685
- const truncated = truncateToWidth(singleLine, Math.max(1, width - visibleWidth(prefix)), ELLIPSIS);
175114
+ const truncated = truncateToWidth(singleLine, Math.max(1, width - visibleWidth(prefix)), ELLIPSIS$1);
174686
175115
  lines.push(accent(prefix + truncated));
174687
175116
  }
174688
- 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)));
174689
175118
  return lines;
174690
175119
  }
174691
175120
  };
@@ -175109,6 +175538,144 @@ function createApprovalRequestHandler(controller, onResponse) {
175109
175538
  };
175110
175539
  }
175111
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
175112
175679
  //#region src/tui/reverse-rpc/modal-coordinator.ts
175113
175680
  var ReverseRpcModalCoordinator = class {
175114
175681
  hooks;
@@ -175980,6 +176547,107 @@ function styleTitle(title, status, colors) {
175980
176547
  }
175981
176548
  }
175982
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
175983
176651
  //#region src/tui/components/editor/custom-editor.ts
175984
176652
  /**
175985
176653
  * Custom editor extending pi-tui Editor with app-level keybindings.
@@ -175992,6 +176660,10 @@ const KITTY_CSI_U = /^\u001B\[(\d+);(\d+)((?::\d+)*)u$/;
175992
176660
  const CAPS_LOCK_BIT = 64;
175993
176661
  const CTRL_BIT = 4;
175994
176662
  const SHIFT_BIT = 1;
176663
+ const SLASH_COMMAND_SELECT_LIST_LAYOUT = {
176664
+ minPrimaryColumnWidth: 12,
176665
+ maxPrimaryColumnWidth: 32
176666
+ };
175995
176667
  /**
175996
176668
  * Workaround for a pi-tui bug that surfaces when Kitty keyboard protocol
175997
176669
  * is active AND caps_lock is on. In that state terminals emit, e.g.,
@@ -176077,7 +176749,12 @@ var CustomEditor = class extends Editor {
176077
176749
  consumingPaste = false;
176078
176750
  consumeBuffer = "";
176079
176751
  constructor(tui) {
176080
- 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
+ };
176081
176758
  }
176082
176759
  expandPasteMarkerAtCursor() {
176083
176760
  const { line, col } = this.getCursor();
@@ -176708,7 +177385,8 @@ function createInitialAppState(input) {
176708
177385
  availableProviders: {},
176709
177386
  sessionTitle: null,
176710
177387
  goal: null,
176711
- mcpServersSummary: null
177388
+ mcpServersSummary: null,
177389
+ banner: void 0
176712
177390
  };
176713
177391
  }
176714
177392
  var KimiTUI = class {
@@ -176805,6 +177483,7 @@ var KimiTUI = class {
176805
177483
  const completer = cmd.completeArgs;
176806
177484
  return {
176807
177485
  name: cmd.name,
177486
+ aliases: cmd.aliases,
176808
177487
  description: cmd.description,
176809
177488
  ...cmd.argumentHint !== void 0 ? { argumentHint: cmd.argumentHint } : {},
176810
177489
  ...completer !== void 0 ? { getArgumentCompletions: (prefix) => completer(prefix) } : {}
@@ -176873,10 +177552,28 @@ var KimiTUI = class {
176873
177552
  throw error;
176874
177553
  }
176875
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
+ }
176876
177572
  async initMainTui() {
176877
177573
  const shouldReplayHistory = await this.init();
176878
177574
  this.mountFooter();
176879
177575
  this.renderWelcome();
177576
+ this.loadBanner();
176880
177577
  this.setupAutocomplete();
176881
177578
  this.loadPersistedInputHistory();
176882
177579
  this.state.editorContainer.clear();
@@ -177176,10 +177873,11 @@ var KimiTUI = class {
177176
177873
  this.showError(message);
177177
177874
  }
177178
177875
  sendQueuedMessage(session, item) {
177179
- this.harness.interactiveAgentId = item.agentId ?? "main";
177180
- this.sendMessageInternal(session, item.text, {
177181
- parts: item.parts,
177182
- 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
+ });
177183
177881
  });
177184
177882
  }
177185
177883
  requestQueuedGoalPromotion() {
@@ -177391,7 +178089,6 @@ var KimiTUI = class {
177391
178089
  this.streamingUI.discardPending();
177392
178090
  this.state.queuedMessages = [];
177393
178091
  this.state.swarmModeEntry = void 0;
177394
- this.harness.interactiveAgentId = MAIN_AGENT_ID;
177395
178092
  this.streamingUI.resetToolCallState();
177396
178093
  this.streamingUI.resetToolUi();
177397
178094
  this.sessionEventHandler.resetRuntimeState();