@moonshot-ai/kimi-code 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.mjs +927 -281
  2. package/package.json +5 -5
package/dist/main.mjs CHANGED
@@ -40,9 +40,9 @@ import * as z4mini from "zod/v4-mini";
40
40
  import { fileURLToPath, pathToFileURL } from "node:url";
41
41
  import process$1 from "node:process";
42
42
  import { AsyncLocalStorage } from "node:async_hooks";
43
- import { Command, Option } from "commander";
44
- import { CombinedAutocompleteProvider, Container, Editor, Image, Input, Key, Markdown, ProcessTerminal, Spacer, TUI, Text, decodeKittyPrintable, deleteAllKittyImages, fuzzyFilter, fuzzyMatch, getCapabilities, isKeyRelease, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
43
+ import { CURSOR_MARKER, CombinedAutocompleteProvider, Container, Editor, Image, Input, Key, Markdown, ProcessTerminal, Spacer, TUI, Text, decodeKittyPrintable, deleteAllKittyImages, fuzzyFilter, fuzzyMatch, getCapabilities, isKeyRelease, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
45
44
  import chalk, { chalkStderr } from "chalk";
45
+ import { Command, Option } from "commander";
46
46
  import { Buffer as Buffer$2 } from "node:buffer";
47
47
  import { createInterface } from "node:readline/promises";
48
48
  import { highlight, supportsLanguage } from "cli-highlight";
@@ -1089,9 +1089,13 @@ var APIContextOverflowError = class extends APIStatusError {
1089
1089
  * The API returned an empty response (no content, no tool calls).
1090
1090
  */
1091
1091
  var APIEmptyResponseError = class extends ChatProviderError {
1092
- constructor(message) {
1092
+ finishReason;
1093
+ rawFinishReason;
1094
+ constructor(message, options = {}) {
1093
1095
  super(message);
1094
1096
  this.name = "APIEmptyResponseError";
1097
+ this.finishReason = options.finishReason ?? null;
1098
+ this.rawFinishReason = options.rawFinishReason ?? null;
1095
1099
  }
1096
1100
  };
1097
1101
  function isRetryableGenerateError(error) {
@@ -8609,6 +8613,11 @@ const OPENAI_RESPONSES_DEVELOPER_ROLE_MODELS = new Set([
8609
8613
  "o3-pro",
8610
8614
  "o4-mini"
8611
8615
  ]);
8616
+ const OPENAI_CHAT_COMPLETIONS_XHIGH_REASONING_MODELS = new Set([
8617
+ "gpt-5.2",
8618
+ "gpt-5.4",
8619
+ "gpt-5.5"
8620
+ ]);
8612
8621
  const OPENAI_VISION_TOOL_PREFIXES = [
8613
8622
  "gpt-4o",
8614
8623
  "gpt-4-turbo",
@@ -8720,6 +8729,9 @@ const ANTHROPIC_CAPABILITY_CATALOG = [{
8720
8729
  function normalizeModelName(modelName) {
8721
8730
  return modelName.toLowerCase();
8722
8731
  }
8732
+ function modelIdLeaf(modelName) {
8733
+ return normalizeModelName(modelName).trim().split("/").at(-1) ?? "";
8734
+ }
8723
8735
  function hasPrefix(modelName, prefixes) {
8724
8736
  return prefixes.some((prefix) => modelName.startsWith(prefix));
8725
8737
  }
@@ -8747,6 +8759,9 @@ function getGoogleGenAIModelCapability(modelName) {
8747
8759
  if (normalized.startsWith("gemini-2.5-") || normalized.includes("thinking")) return GEMINI_THINKING_MULTIMODAL_TOOL_CAPABILITY;
8748
8760
  return GEMINI_MULTIMODAL_TOOL_CAPABILITY;
8749
8761
  }
8762
+ function supportsOpenAIChatCompletionsXHighReasoning(modelName) {
8763
+ return OPENAI_CHAT_COMPLETIONS_XHIGH_REASONING_MODELS.has(modelIdLeaf(modelName));
8764
+ }
8750
8765
  function usesOpenAIResponsesDeveloperRole(modelName) {
8751
8766
  const normalized = normalizeModelName(modelName);
8752
8767
  if (OPENAI_RESPONSES_DEVELOPER_ROLE_MODELS.has(normalized)) return true;
@@ -46253,6 +46268,10 @@ function normalizeGenerationKwargs(model, source) {
46253
46268
  }
46254
46269
  return kwargs;
46255
46270
  }
46271
+ function clampChatCompletionsReasoningEffort(reasoningEffort, model) {
46272
+ if (reasoningEffort !== "xhigh") return reasoningEffort;
46273
+ return supportsOpenAIChatCompletionsXHighReasoning(model) ? "xhigh" : "high";
46274
+ }
46256
46275
  function convertMessage$1(message, reasoningKey, toolMessageConversion) {
46257
46276
  let reasoningContent = "";
46258
46277
  const nonThinkParts = [];
@@ -46443,7 +46462,7 @@ var OpenAILegacyChatProvider = class {
46443
46462
  }
46444
46463
  }
46445
46464
  withThinking(effort) {
46446
- const reasoningEffort = thinkingEffortToReasoningEffort(effort);
46465
+ const reasoningEffort = clampChatCompletionsReasoningEffort(thinkingEffortToReasoningEffort(effort), this._model);
46447
46466
  const clone = this._clone();
46448
46467
  clone._reasoningEffort = reasoningEffort;
46449
46468
  return clone;
@@ -47322,11 +47341,17 @@ async function generate(provider, systemPrompt, tools, history, callbacks, optio
47322
47341
  await throwIfAborted(options?.signal, stream);
47323
47342
  options?.onStreamEnd?.();
47324
47343
  if (pendingPart !== null) flushPart(message, pendingPart, toolCallIndexMap);
47325
- if (message.content.length === 0 && message.toolCalls.length === 0) throw new APIEmptyResponseError(`The API returned an empty response (no content, no tool calls). Provider: ${provider.name}, model: ${provider.modelName}`);
47344
+ if (message.content.length === 0 && message.toolCalls.length === 0) throw new APIEmptyResponseError("The API returned an empty response (no content, no tool calls)." + formatFinishReasonHint(stream) + ` Provider: ${provider.name}, model: ${provider.modelName}`, {
47345
+ finishReason: stream.finishReason,
47346
+ rawFinishReason: stream.rawFinishReason
47347
+ });
47326
47348
  const hasThink = message.content.some((p) => p.type === "think");
47327
47349
  const hasText = message.content.some((p) => p.type === "text" && p.text.trim().length > 0);
47328
47350
  const hasToolCalls = message.toolCalls.length > 0;
47329
- if (hasThink && !hasText && !hasToolCalls) throw new APIEmptyResponseError(`The API returned a response containing only thinking content without any text or tool calls. This usually indicates the stream was interrupted or the output token budget was exhausted during reasoning. Provider: ${provider.name}, model: ${provider.modelName}`);
47351
+ if (hasThink && !hasText && !hasToolCalls) throw new APIEmptyResponseError("The API returned a response containing only thinking content without any text or tool calls. This usually indicates the stream was interrupted or the output token budget was exhausted during reasoning." + formatFinishReasonHint(stream) + ` Provider: ${provider.name}, model: ${provider.modelName}`, {
47352
+ finishReason: stream.finishReason,
47353
+ rawFinishReason: stream.rawFinishReason
47354
+ });
47330
47355
  if (callbacks?.onToolCall !== void 0) for (const toolCall of message.toolCalls) {
47331
47356
  await throwIfAborted(options?.signal, stream);
47332
47357
  await callbacks.onToolCall(toolCall);
@@ -47387,6 +47412,12 @@ function flushPart(message, part, toolCallIndexMap) {
47387
47412
  if (streamIndex !== void 0) toolCallIndexMap.set(streamIndex, ordinal);
47388
47413
  }
47389
47414
  }
47415
+ function formatFinishReasonHint(stream) {
47416
+ if (stream.finishReason === null && stream.rawFinishReason === null) return "";
47417
+ const raw = stream.rawFinishReason === null ? "" : `, rawFinishReason=${stream.rawFinishReason}`;
47418
+ const filteredHint = stream.finishReason === "filtered" ? " The provider filtered the response before visible output was emitted." : "";
47419
+ return ` Provider stop details: finishReason=${stream.finishReason ?? "unknown"}${raw}.${filteredHint}`;
47420
+ }
47390
47421
  /**
47391
47422
  * Produce a shallow-ish copy of a StreamedMessagePart.
47392
47423
  *
@@ -47500,6 +47531,16 @@ function toKimiErrorPayload(error) {
47500
47531
  name: error.name,
47501
47532
  retryable: KIMI_ERROR_INFO[ErrorCodes.PROVIDER_CONNECTION_ERROR].retryable
47502
47533
  };
47534
+ if (error instanceof APIEmptyResponseError) return {
47535
+ code: ErrorCodes.PROVIDER_API_ERROR,
47536
+ message: error.message,
47537
+ name: error.name,
47538
+ details: {
47539
+ finishReason: error.finishReason,
47540
+ rawFinishReason: error.rawFinishReason
47541
+ },
47542
+ retryable: KIMI_ERROR_INFO[ErrorCodes.PROVIDER_API_ERROR].retryable
47543
+ };
47503
47544
  if (error instanceof ChatProviderError) return {
47504
47545
  code: ErrorCodes.PROVIDER_API_ERROR,
47505
47546
  message: error.message,
@@ -48298,6 +48339,14 @@ const FLAG_DEFINITIONS = [
48298
48339
  env: "KIMI_CODE_EXPERIMENTAL_BACKGROUND_ASK",
48299
48340
  default: false,
48300
48341
  surface: "core"
48342
+ },
48343
+ {
48344
+ id: "sub_skill",
48345
+ title: "Sub-skill",
48346
+ description: "Enable discovery of nested skills inside skill bundles that declare has-sub-skill.",
48347
+ env: "KIMI_CODE_EXPERIMENTAL_SUB_SKILL",
48348
+ default: false,
48349
+ surface: "core"
48301
48350
  }
48302
48351
  ];
48303
48352
  //#endregion
@@ -48324,6 +48373,19 @@ function parseBooleanEnv(value) {
48324
48373
  if (FALSE_BOOLEAN_ENV_VALUES.has(normalized)) return false;
48325
48374
  }
48326
48375
  /**
48376
+ * Parse a floating-point environment value (e.g. `KIMI_MODEL_TEMPERATURE`).
48377
+ * Returns `undefined` when unset/blank; throws `KimiError(CONFIG_INVALID)` on a
48378
+ * non-numeric value so a typo fails fast like the other `KIMI_MODEL_*` vars.
48379
+ * No range validation — callers pass values the upstream API accepts.
48380
+ */
48381
+ function parseFloatEnv(value, varName) {
48382
+ const trimmed = value?.trim();
48383
+ if (trimmed === void 0 || trimmed.length === 0) return void 0;
48384
+ const parsed = Number(trimmed);
48385
+ if (!Number.isFinite(parsed)) throw new KimiError(ErrorCodes.CONFIG_INVALID, `${varName} must be a number, got "${value}".`);
48386
+ return parsed;
48387
+ }
48388
+ /**
48327
48389
  * Pure, synchronous flag resolver. State comes entirely from (env, registry) and nothing is
48328
48390
  * cached: env is read live on every call, so a single shared instance always reflects the current
48329
48391
  * process env. Defaults to process.env + FLAG_DEFINITIONS; tests can inject a custom env / defs.
@@ -48384,7 +48446,12 @@ var FlagResolver = class {
48384
48446
  };
48385
48447
  }
48386
48448
  };
48387
- new FlagResolver();
48449
+ /**
48450
+ * Compatibility accessor for callers that only need process-global env behavior.
48451
+ * Runtime code that belongs to a KimiCore/Session/Agent should use the scoped
48452
+ * resolver on that owner instead.
48453
+ */
48454
+ const flags = new FlagResolver();
48388
48455
  //#endregion
48389
48456
  //#region ../../packages/agent-core/src/telemetry.ts
48390
48457
  const noopTelemetryClient = {
@@ -48396,56 +48463,6 @@ function withTelemetryContext$1(telemetry, patch) {
48396
48463
  return telemetry.withContext?.(patch) ?? telemetry;
48397
48464
  }
48398
48465
  //#endregion
48399
- //#region ../../packages/agent-core/src/utils/tokens.ts
48400
- /**
48401
- * Estimate token count from text using a character-based heuristic.
48402
- * - ASCII (~4 chars per token)
48403
- * - CJK and other non-ASCII (~1 char per token)
48404
- * The estimate is transient — the next LLM call returns the real count
48405
- * and supersedes this value. Used to keep `tokenCountWithPending`
48406
- * monotonic between LLM round-trips without paying for a tokenizer.
48407
- */
48408
- function estimateTokens(text) {
48409
- let asciiCount = 0;
48410
- let nonAsciiCount = 0;
48411
- for (const char of text) if (char.codePointAt(0) <= 127) asciiCount++;
48412
- else nonAsciiCount++;
48413
- return Math.ceil(asciiCount / 4) + nonAsciiCount;
48414
- }
48415
- function estimateTokensForMessages(messages) {
48416
- let total = 0;
48417
- for (const message of messages) total += estimateTokensForMessage(message);
48418
- return total;
48419
- }
48420
- function estimateTokensForTools(tools) {
48421
- let total = 0;
48422
- for (const tool of tools) {
48423
- total += estimateTokens(tool.name);
48424
- total += estimateTokens(tool.description);
48425
- total += estimateTokens(JSON.stringify(tool.parameters));
48426
- }
48427
- return total;
48428
- }
48429
- function estimateTokensForMessage(message) {
48430
- let total = estimateTokens(message.role);
48431
- total += estimateTokensForContentParts(message.content);
48432
- if (message.toolCalls !== void 0) for (const call of message.toolCalls) {
48433
- total += estimateTokens(call.name);
48434
- total += estimateTokens(JSON.stringify(call.arguments));
48435
- }
48436
- return total;
48437
- }
48438
- function estimateTokensForContentParts(parts) {
48439
- let total = 0;
48440
- for (const part of parts) total += estimateTokensForContentPart(part);
48441
- return total;
48442
- }
48443
- function estimateTokensForContentPart(part) {
48444
- if (part.type === "text") return estimateTokens(part.text);
48445
- else if (part.type === "think") return estimateTokens(part.think);
48446
- return 0;
48447
- }
48448
- //#endregion
48449
48466
  //#region ../../packages/agent-core/src/loop/errors.ts
48450
48467
  /**
48451
48468
  * Loop-local error helpers.
@@ -54575,6 +54592,47 @@ function renderPrompt(template, vars) {
54575
54592
  return env.renderString(template, vars);
54576
54593
  }
54577
54594
  //#endregion
54595
+ //#region ../../packages/agent-core/src/utils/tokens.ts
54596
+ /**
54597
+ * Estimate token count from text using a character-based heuristic.
54598
+ * - ASCII (~4 chars per token)
54599
+ * - CJK and other non-ASCII (~1 char per token)
54600
+ * The estimate is transient — the next LLM call returns the real count
54601
+ * and supersedes this value. Used to keep `tokenCountWithPending`
54602
+ * monotonic between LLM round-trips without paying for a tokenizer.
54603
+ */
54604
+ function estimateTokens(text) {
54605
+ let asciiCount = 0;
54606
+ let nonAsciiCount = 0;
54607
+ for (const char of text) if (char.codePointAt(0) <= 127) asciiCount++;
54608
+ else nonAsciiCount++;
54609
+ return Math.ceil(asciiCount / 4) + nonAsciiCount;
54610
+ }
54611
+ function estimateTokensForMessages(messages) {
54612
+ let total = 0;
54613
+ for (const message of messages) total += estimateTokensForMessage(message);
54614
+ return total;
54615
+ }
54616
+ function estimateTokensForMessage(message) {
54617
+ let total = estimateTokens(message.role);
54618
+ total += estimateTokensForContentParts(message.content);
54619
+ if (message.toolCalls !== void 0) for (const call of message.toolCalls) {
54620
+ total += estimateTokens(call.name);
54621
+ total += estimateTokens(JSON.stringify(call.arguments));
54622
+ }
54623
+ return total;
54624
+ }
54625
+ function estimateTokensForContentParts(parts) {
54626
+ let total = 0;
54627
+ for (const part of parts) total += estimateTokensForContentPart(part);
54628
+ return total;
54629
+ }
54630
+ function estimateTokensForContentPart(part) {
54631
+ if (part.type === "text") return estimateTokens(part.text);
54632
+ else if (part.type === "think") return estimateTokens(part.think);
54633
+ return 0;
54634
+ }
54635
+ //#endregion
54578
54636
  //#region ../../packages/agent-core/src/utils/completion-budget.ts
54579
54637
  const MIN_FLOOR = 1;
54580
54638
  const DEFAULT_UNKNOWN_CONTEXT_FALLBACK = 32e3;
@@ -56570,6 +56628,44 @@ var CronManager = class {
56570
56628
  }
56571
56629
  };
56572
56630
  //#endregion
56631
+ //#region ../../packages/agent-core/src/agent/kimi-env-params.ts
56632
+ /**
56633
+ * Apply Kimi sampling params (`KIMI_MODEL_TEMPERATURE`, `KIMI_MODEL_TOP_P`) from
56634
+ * the environment to a chat provider. Applied at provider construction
56635
+ * (`ConfigState.provider`) so every request built from `config.provider` — the
56636
+ * main loop AND full-history compaction — carries them, matching kimi-cli where
56637
+ * these live on the shared `create_llm` provider. Applies globally to any Kimi
56638
+ * provider (not tied to `KIMI_MODEL_NAME`).
56639
+ *
56640
+ * Non-Kimi providers — and Kimi providers with neither var set — are returned
56641
+ * unchanged. `max_tokens` is intentionally NOT handled here: `KIMI_MODEL_MAX_TOKENS`
56642
+ * already flows through the completion-budget path (`resolveCompletionBudget`).
56643
+ */
56644
+ function applyKimiEnvSamplingParams(provider, env = process.env) {
56645
+ if (!(provider instanceof KimiChatProvider)) return provider;
56646
+ const kwargs = {};
56647
+ const temperature = parseFloatEnv(env["KIMI_MODEL_TEMPERATURE"], "KIMI_MODEL_TEMPERATURE");
56648
+ if (temperature !== void 0) kwargs.temperature = temperature;
56649
+ const topP = parseFloatEnv(env["KIMI_MODEL_TOP_P"], "KIMI_MODEL_TOP_P");
56650
+ if (topP !== void 0) kwargs.top_p = topP;
56651
+ return Object.keys(kwargs).length > 0 ? provider.withGenerationKwargs(kwargs) : provider;
56652
+ }
56653
+ /**
56654
+ * Apply the Moonshot preserved-thinking passthrough (`KIMI_MODEL_THINKING_KEEP`
56655
+ * -> `thinking.keep`) to a chat provider. Applied in `Agent.llm` after
56656
+ * `withThinking`, and only while thinking is on — otherwise the API would
56657
+ * receive a `thinking.keep` with no accompanying `thinking.type` it honors.
56658
+ * (Compaction uses a raw provider with thinking off, so it correctly skips this.)
56659
+ *
56660
+ * Non-Kimi providers — and an unset/blank value — are returned unchanged.
56661
+ */
56662
+ function applyKimiEnvThinkingKeep(provider, thinkingLevel, env = process.env) {
56663
+ if (!(provider instanceof KimiChatProvider)) return provider;
56664
+ const keep = env["KIMI_MODEL_THINKING_KEEP"]?.trim();
56665
+ if (keep === void 0 || keep.length === 0 || thinkingLevel === "off") return provider;
56666
+ return provider.withExtraBody({ thinking: { keep } });
56667
+ }
56668
+ //#endregion
56573
56669
  //#region ../../packages/agent-core/src/agent/config/thinking.ts
56574
56670
  const DEFAULT_THINKING_EFFORT = "high";
56575
56671
  const THINKING_EFFORTS = new Set([
@@ -56659,7 +56755,7 @@ var ConfigState = class {
56659
56755
  return provider;
56660
56756
  }
56661
56757
  get provider() {
56662
- return createProvider(this.providerConfig);
56758
+ return applyKimiEnvThinkingKeep(applyKimiEnvSamplingParams(createProvider(this.providerConfig).withThinking(this.thinkingLevel)), this.thinkingLevel);
56663
56759
  }
56664
56760
  get model() {
56665
56761
  if (this._modelAlias === void 0) throw new KimiError(ErrorCodes.MODEL_NOT_CONFIGURED, "Model not set");
@@ -65445,6 +65541,41 @@ const MCP_CONFIG_SKILL = {
65445
65541
  }
65446
65542
  };
65447
65543
  //#endregion
65544
+ //#region ../../packages/agent-core/src/skill/builtin/sub-skill/consolidate/SKILL.md
65545
+ var SKILL_default$2 = "---\nname: sub-skill.consolidate\ndescription: Apply an approved sub-skill grouping by moving user-specified skills into a parent bundle, with timestamped backups of every modified directory.\ndisable-model-invocation: true\n---\n\n# Consolidate sub-skills (`sub-skill.consolidate`)\n\nExecute the reorganization by moving user-specified skills into a parent bundle, forming a sub-skill hierarchy.\n\n## When to use\n\n- The user has approved a grouping proposal (typically from `sub-skill.review`) and wants to apply it.\n- Migrating standalone skills into a new or existing parent bundle.\n\n## Process\n\n1. **Confirm the plan.** Restate which skills will move and where, and ask the user to confirm **before** making any file changes.\n2. **Back up every original skill directory.** Before moving anything, create a timestamped backup of each skill directory that will be modified.\n - For a skill at `<root>/<skill-name>/SKILL.md`, back up the entire `<skill-name>` directory:\n ```bash\n cp -r <skill-name> \"<skill-name>.$(date +%Y%m%d-%H%M%S).bak\"\n ```\n - Keep all backups; never overwrite an existing backup file.\n3. **Create or update the parent bundle.**\n - If the parent does not exist, create `<parent-name>/SKILL.md` with `has-sub-skill: true` in the frontmatter.\n - If the parent already exists, ensure its frontmatter includes `has-sub-skill: true`.\n4. **Move child skills into the parent.** Move each child skill's entire directory under the parent bundle.\n - Example: `web-search/` → `web-research/web-search/`\n5. **Verify the result.** List the new directory structure and confirm each moved skill still has a valid `SKILL.md` with required frontmatter (`name` and `description`).\n6. **Report the change.** Summarize what was moved, the new structure, and where backups are located.\n\n## Don'ts\n\n- **Never move skills without backing up first.**\n- **Never overwrite an existing backup** — always use a fresh timestamped suffix.\n- **Don't drop frontmatter or payload files** during the move; the entire directory must be preserved.\n- **Don't create deeply nested hierarchies** (3+ levels) unless the user explicitly requests it.\n";
65546
+ //#endregion
65547
+ //#region ../../packages/agent-core/src/skill/builtin/sub-skill/review/SKILL.md
65548
+ var SKILL_default$1 = "---\nname: sub-skill.review\ndescription: Analyze the available skill set and recommend candidate groups that could be consolidated into sub-skill bundles. Read-only — proposes a plan, does not move files.\ndisable-model-invocation: true\n---\n\n# Review sub-skills (`sub-skill.review`)\n\nAnalyze the current skill inventory and identify candidate groups that could be consolidated into sub-skill bundles. This sub-skill is **read-only**: it produces a proposal for the user to review before any file changes are made by `sub-skill.consolidate`.\n\n## When to use\n\n- The user asks to review, reorganize, or audit the skill inventory.\n- There are many loosely-related skills that might benefit from hierarchical grouping.\n- The user wants to evaluate whether a new skill should be a sub-skill of an existing parent.\n\n## Process\n\n1. **List the current inventory.** Use the skill registry (or scan the configured skill roots) to get a full list of skill names, descriptions, and source scopes.\n2. **Categorize by domain.** Group skills by functional domain — e.g. file operations, web tools, collaboration, observability.\n3. **Detect coupling.** Identify skills that are frequently used together or share similar `whenToUse` conditions.\n4. **Flag granularity issues.** Call out skills that are too fine-grained (e.g. one skill per CLI flag) or too broad to land in any single domain.\n5. **Propose sub-skill structures.** For each candidate group, list:\n - The parent skill name and a one-line description.\n - The children that should move under it.\n - Whether the parent needs `has-sub-skill: true` (it does, if children should be discovered).\n6. **Output a summary report.** Present findings as a concise grouped list with rationale, and stop. Do **not** edit any file — that's `sub-skill.consolidate`'s job.\n\n## Criteria for a good sub-skill grouping\n\n- **Shared context.** Children operate within the same domain or workflow.\n- **Composable entry point.** The parent gives a natural top-level handle; children handle specifics.\n- **Shallow nesting.** Prefer 2 levels (parent → child). Avoid 3+ unless strictly necessary.\n- **Backward compatibility.** Existing skill names should ideally remain discoverable.\n\n## Example output format\n\n```\nProposed sub-skill: web-research\n - Parent: web-research (has-sub-skill: true)\n - Children:\n - web-search → move under web-research/search\n - fetch-url → move under web-research/fetch\n - Rationale: Both deal with online information retrieval and are often chained together.\n```\n";
65549
+ //#endregion
65550
+ //#region ../../packages/agent-core/src/skill/builtin/sub-skill/SKILL.md
65551
+ var SKILL_default = "---\nname: sub-skill\ndescription: Discover and reorganize the skill inventory into hierarchical sub-skill bundles. Use when the user asks to review, group, or consolidate skills into a parent bundle.\ndisable-model-invocation: true\nhas-sub-skill: true\n---\n\n# Sub-skill\n\nContainer skill for analyzing the local skill inventory and reorganizing it into hierarchical sub-skill bundles (`has-sub-skill: true` parents with children inside).\n\n## When to use\n\n- The user asks to review, reorganize, or consolidate skills.\n- There are many loosely-related skills that might benefit from hierarchical grouping.\n- The user wants to evaluate whether a new skill should be a sub-skill of an existing one.\n\n## Sub-skills\n\n- **`sub-skill.review`** — Analyze the current inventory and propose candidate sub-skill groupings.\n- **`sub-skill.consolidate`** — Apply an approved grouping by moving skills into a parent bundle, with timestamped backups.\n\nThe usual flow is `sub-skill.review` first (read-only proposal), then `sub-skill.consolidate` after the user approves a plan. Never run consolidate without an explicit go-ahead from the user.\n";
65552
+ //#endregion
65553
+ //#region ../../packages/agent-core/src/skill/builtin/sub-skill.ts
65554
+ function makeBuiltin(body, dirName, pseudoPath, extraMetadata = {}) {
65555
+ const parsed = parseSkillText({
65556
+ skillMdPath: `/builtin/skills/${dirName}/SKILL.md`,
65557
+ skillDirName: dirName,
65558
+ source: "builtin",
65559
+ text: body
65560
+ });
65561
+ return {
65562
+ ...parsed,
65563
+ path: pseudoPath,
65564
+ dir: pseudoPath,
65565
+ metadata: {
65566
+ ...parsed.metadata,
65567
+ type: parsed.metadata.type ?? "inline",
65568
+ ...extraMetadata
65569
+ }
65570
+ };
65571
+ }
65572
+ const SUB_SKILL_PARENT = makeBuiltin(SKILL_default, "sub-skill", "builtin://sub-skill", {
65573
+ disableModelInvocation: true,
65574
+ "has-sub-skill": true
65575
+ });
65576
+ const SUB_SKILL_REVIEW = makeBuiltin(SKILL_default$1, "sub-skill.review", "builtin://sub-skill/review", { disableModelInvocation: true });
65577
+ const SUB_SKILL_CONSOLIDATE = makeBuiltin(SKILL_default$2, "sub-skill.consolidate", "builtin://sub-skill/consolidate", { disableModelInvocation: true });
65578
+ //#endregion
65448
65579
  //#region ../../packages/agent-core/src/skill/builtin/update-config.md
65449
65580
  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";
65450
65581
  //#endregion
@@ -65467,9 +65598,15 @@ const UPDATE_CONFIG_SKILL = {
65467
65598
  };
65468
65599
  //#endregion
65469
65600
  //#region ../../packages/agent-core/src/skill/builtin/index.ts
65470
- function registerBuiltinSkills(registry) {
65601
+ function registerBuiltinSkills(registry, options = {}) {
65602
+ const experimentalFlags = options.experimentalFlags ?? flags;
65471
65603
  registry.registerBuiltinSkill(MCP_CONFIG_SKILL);
65472
65604
  registry.registerBuiltinSkill(UPDATE_CONFIG_SKILL);
65605
+ if (experimentalFlags.enabled("sub_skill")) {
65606
+ registry.registerBuiltinSkill(SUB_SKILL_PARENT);
65607
+ registry.registerBuiltinSkill(SUB_SKILL_REVIEW);
65608
+ registry.registerBuiltinSkill(SUB_SKILL_CONSOLIDATE);
65609
+ }
65473
65610
  }
65474
65611
  //#endregion
65475
65612
  //#region ../../packages/agent-core/src/skill/scanner.ts
@@ -65502,6 +65639,7 @@ async function discoverSkills(options) {
65502
65639
  const isFile = options.isFile ?? defaultIsFile;
65503
65640
  const isDir = options.isDir ?? defaultIsDir;
65504
65641
  const parse = options.parse ?? parseSkillFromFile;
65642
+ const subSkillFlags = options.experimentalFlags ?? flags;
65505
65643
  const warn = options.onWarning ?? (() => {});
65506
65644
  const skip = options.onSkippedByPolicy ?? (() => {});
65507
65645
  const byName = /* @__PURE__ */ new Map();
@@ -65517,23 +65655,25 @@ async function discoverSkills(options) {
65517
65655
  const directorySkills = /* @__PURE__ */ new Set();
65518
65656
  const subdirs = [];
65519
65657
  for (const entry of entries) {
65520
- if (await isFile(posix$1.join(dirPath, entry, "SKILL.md"))) {
65521
- directorySkills.add(entry);
65522
- continue;
65523
- }
65658
+ const entryPath = posix$1.join(dirPath, entry);
65659
+ if (await isFile(posix$1.join(entryPath, "SKILL.md"))) directorySkills.add(entry);
65524
65660
  if (entry === "node_modules" || entry.startsWith(".")) continue;
65525
- if (await isDir(posix$1.join(dirPath, entry))) subdirs.push(entry);
65661
+ if (await isDir(entryPath)) subdirs.push(entry);
65662
+ }
65663
+ const allowedSubSkillBundles = /* @__PURE__ */ new Set();
65664
+ for (const entry of directorySkills) {
65665
+ const skill = await parseAndRegister({
65666
+ parse,
65667
+ byName,
65668
+ skillMdPath: posix$1.join(dirPath, entry, "SKILL.md"),
65669
+ skillDirName: entry,
65670
+ root,
65671
+ onDiscoveredSkill: options.onDiscoveredSkill,
65672
+ warn,
65673
+ skip
65674
+ });
65675
+ if (skill !== void 0 && hasSubSkillEnabled(skill, subSkillFlags)) allowedSubSkillBundles.add(entry);
65526
65676
  }
65527
- for (const entry of directorySkills) await parseAndRegister({
65528
- parse,
65529
- byName,
65530
- skillMdPath: posix$1.join(dirPath, entry, "SKILL.md"),
65531
- skillDirName: entry,
65532
- root,
65533
- onDiscoveredSkill: options.onDiscoveredSkill,
65534
- warn,
65535
- skip
65536
- });
65537
65677
  if (isTopLevel) {
65538
65678
  if (root.plugin !== void 0) {
65539
65679
  const rootSkillMd = posix$1.join(dirPath, "SKILL.md");
@@ -65570,7 +65710,10 @@ async function discoverSkills(options) {
65570
65710
  });
65571
65711
  }
65572
65712
  }
65573
- for (const entry of subdirs) await walkSkillDir(posix$1.join(dirPath, entry), root, false, depth + 1);
65713
+ for (const entry of subdirs) {
65714
+ if (directorySkills.has(entry) && !allowedSubSkillBundles.has(entry)) continue;
65715
+ await walkSkillDir(posix$1.join(dirPath, entry), root, false, depth + 1);
65716
+ }
65574
65717
  }
65575
65718
  for (const root of options.roots) await walkSkillDir(root.path, root, true, 0);
65576
65719
  return sortSkills([...byName.values()]);
@@ -65645,6 +65788,7 @@ async function parseAndRegister(input) {
65645
65788
  input.onDiscoveredSkill?.(discovered);
65646
65789
  const key = normalizeSkillName(discovered.name);
65647
65790
  if (!input.byName.has(key)) input.byName.set(key, discovered);
65791
+ return skill;
65648
65792
  } catch (error) {
65649
65793
  if (error instanceof UnsupportedSkillTypeError) input.skip({
65650
65794
  path: input.skillMdPath,
@@ -65653,8 +65797,15 @@ async function parseAndRegister(input) {
65653
65797
  });
65654
65798
  else if (error instanceof SkillParseError) input.warn(`Skipping invalid skill at ${input.skillMdPath}: ${error.message}`, error);
65655
65799
  else input.warn(`Skipping skill at ${input.skillMdPath} due to unexpected error`, error);
65800
+ return;
65656
65801
  }
65657
65802
  }
65803
+ function hasSubSkillEnabled(skill, experimentalFlags) {
65804
+ if (!experimentalFlags.enabled("sub_skill")) return false;
65805
+ const nested = skill.metadata["metadata"];
65806
+ const nestedFlag = typeof nested === "object" && nested !== null ? nested["has-sub-skill"] === true || nested["hasSubSkill"] === true : false;
65807
+ return skill.metadata["has-sub-skill"] === true || skill.metadata["hasSubSkill"] === true || nestedFlag;
65808
+ }
65658
65809
  async function defaultIsDir(p) {
65659
65810
  try {
65660
65811
  return (await promises.stat(p)).isDirectory();
@@ -65706,10 +65857,12 @@ var SkillRegistry = class {
65706
65857
  roots = [];
65707
65858
  skipped = [];
65708
65859
  discoverImpl;
65860
+ experimentalFlags;
65709
65861
  onWarning;
65710
65862
  sessionId;
65711
65863
  constructor(options = {}) {
65712
65864
  this.discoverImpl = options.discover ?? discoverSkills;
65865
+ this.experimentalFlags = options.experimentalFlags ?? flags;
65713
65866
  this.onWarning = options.onWarning ?? (() => {});
65714
65867
  this.sessionId = options.sessionId;
65715
65868
  }
@@ -65717,6 +65870,7 @@ var SkillRegistry = class {
65717
65870
  for (const root of roots) if (!this.roots.includes(root.path)) this.roots.push(root.path);
65718
65871
  const skills = await this.discoverImpl({
65719
65872
  roots,
65873
+ experimentalFlags: this.experimentalFlags,
65720
65874
  onWarning: this.onWarning,
65721
65875
  onSkippedByPolicy: (skill) => this.skipped.push(skill),
65722
65876
  onDiscoveredSkill: (skill) => {
@@ -78894,10 +79048,10 @@ var CronListTool = class {
78894
79048
  var agent_background_disabled_default = "Background agent execution is disabled for this agent. Do not set `run_in_background=true`.";
78895
79049
  //#endregion
78896
79050
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-background-enabled.md
78897
- var agent_background_enabled_default = "When `run_in_background=true`, the subagent runs detached from this turn. The completion arrives in a later turn as a synthetic user-role message containing its result — you do not need to poll, sleep, or check on its progress. Continue with other work or respond to the user. Never fabricate or predict what the result will say.\n\nFor a background task, when `timeout` is omitted it falls back to the operator-configured background timeout, if one is set. If the operator has not configured a background timeout, an omitted `timeout` means the task runs with no time limit.\n";
79051
+ var agent_background_enabled_default = "When `run_in_background=true`, the subagent runs detached from this turn. The completion arrives in a later turn as a synthetic user-role message containing its result — you do not need to poll, sleep, or check on its progress. Continue with other work or respond to the user. Never fabricate or predict what the result will say.\n";
78898
79052
  //#endregion
78899
79053
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent.md
78900
- var agent_default = "Launch a subagent to handle a task. The subagent runs as a same-process loop instance with its own context and wire file.\n\nWriting the prompt:\n- The subagent starts with zero context — it has not seen this conversation. Brief it like a colleague who just walked into the room: state the goal, list what you already know, hand over the specifics.\n- Lookups (read this file, run that test): put the exact path or command in the prompt. The subagent should not have to search for things you already know.\n- Investigations (figure out X, find why Y): give the question, not prescribed steps — fixed steps become dead weight when the premise is wrong.\n- Do not delegate understanding. If the task hinges on a file path or line number, find it yourself first and write it into the prompt.\n\nUsage notes:\n- When the task continues earlier work a subagent already did, prefer resuming that agent (pass its `resume` id) over spawning a fresh instance — the resumed agent keeps its prior context.\n- A subagent's result is only visible to you, not to the user. When the user needs to see what a subagent produced, summarize the relevant parts yourself in your own reply.\n\nWhen NOT to use Agent: skip delegation for trivial work you can do directly — reading a file whose path you already know, searching a small known set of files, or any task that takes only a step or two. Delegation has a context-handoff cost; it pays off only when the task is substantial enough to outweigh it.\n\nOnce a subagent is running, leave that scope to it: do not redo its searches or reads in parallel, and do not abandon it midway and finish the job manually. Both undo the context savings the delegation was meant to buy.";
79054
+ var agent_default = "Launch a subagent to handle a task. The subagent runs as a same-process loop instance with its own context and wire file.\n\nWriting the prompt:\n- The subagent starts with zero context — it has not seen this conversation. Brief it like a colleague who just walked into the room: state the goal, list what you already know, hand over the specifics.\n- Lookups (read this file, run that test): put the exact path or command in the prompt. The subagent should not have to search for things you already know.\n- Investigations (figure out X, find why Y): give the question, not prescribed steps — fixed steps become dead weight when the premise is wrong.\n- Do not delegate understanding. If the task hinges on a file path or line number, find it yourself first and write it into the prompt.\n\nUsage notes:\n- When the task continues earlier work a subagent already did, prefer resuming that agent (pass its `resume` id) over spawning a fresh instance — the resumed agent keeps its prior context.\n- A subagent's result is only visible to you, not to the user. When the user needs to see what a subagent produced, summarize the relevant parts yourself in your own reply.\n- Subagents use a fixed 30-minute timeout. If one times out, resume the same agent instead of starting over.\n\nWhen NOT to use Agent: skip delegation for trivial work you can do directly — reading a file whose path you already know, searching a small known set of files, or any task that takes only a step or two. Delegation has a context-handoff cost; it pays off only when the task is substantial enough to outweigh it.\n\nOnce a subagent is running, leave that scope to it: do not redo its searches or reads in parallel, and do not abandon it midway and finish the job manually. Both undo the context savings the delegation was meant to buy.\n";
78901
79055
  //#endregion
78902
79056
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent.ts
78903
79057
  /**
@@ -78930,8 +79084,7 @@ const AgentToolInputSchema = z.preprocess((input) => {
78930
79084
  description: z.string().describe("Short task description (3-5 words) for UI display"),
78931
79085
  subagent_type: z.string().optional().describe("One of the available agent types (see \"Available agent types\" in this tool description). Defaults to \"coder\" when omitted."),
78932
79086
  resume: z.string().optional().describe("Optional agent ID to resume instead of creating a new instance"),
78933
- run_in_background: z.boolean().optional().describe("If true, return immediately without waiting for completion. Prefer false unless the task can run independently and there is a clear benefit to not waiting."),
78934
- timeout: z.number().int().min(30).max(3600).optional().describe("Timeout in seconds for the agent task (min 30s, max 3600s / 1hr). When omitted, a foreground task runs until completion with no timeout. The agent is stopped if it exceeds this limit.")
79087
+ run_in_background: z.boolean().optional().describe("If true, return immediately without waiting for completion. Prefer false unless the task can run independently and there is a clear benefit to not waiting.")
78935
79088
  }));
78936
79089
  z.object({
78937
79090
  result: z.string().describe("Aggregated text output from the subagent"),
@@ -78943,6 +79096,8 @@ z.object({
78943
79096
  }).describe("Cumulative token usage")
78944
79097
  });
78945
79098
  const BACKGROUND_AGENT_UNAVAILABLE = "Background agent execution is not available for this agent because TaskList, TaskOutput, and TaskStop are not enabled.";
79099
+ const AGENT_TIMEOUT_MS = 1800 * 1e3;
79100
+ const AGENT_TIMEOUT_DESCRIPTION = "30 minutes";
78946
79101
  var AgentTool = class {
78947
79102
  subagentHost;
78948
79103
  backgroundManager;
@@ -78995,8 +79150,7 @@ var AgentTool = class {
78995
79150
  };
78996
79151
  }
78997
79152
  const backgroundController = runInBackground ? new AbortController() : void 0;
78998
- const timeoutMs = args.timeout === void 0 ? void 0 : args.timeout * 1e3;
78999
- foregroundDeadline = !runInBackground && timeoutMs !== void 0 ? createDeadlineAbortSignal(signal, timeoutMs) : void 0;
79153
+ foregroundDeadline = !runInBackground ? createDeadlineAbortSignal(signal, AGENT_TIMEOUT_MS) : void 0;
79000
79154
  const options = {
79001
79155
  parentToolCallId: toolCallId,
79002
79156
  prompt: args.prompt,
@@ -79027,7 +79181,7 @@ var AgentTool = class {
79027
79181
  let taskId;
79028
79182
  try {
79029
79183
  taskId = this.backgroundManager.registerTask(new AgentBackgroundTask(handle.completion, args.description, {
79030
- timeoutMs: timeoutMs ?? this.subagentHost.backgroundTaskTimeoutMs,
79184
+ timeoutMs: AGENT_TIMEOUT_MS,
79031
79185
  agentId: handle.agentId,
79032
79186
  subagentType: handle.profileName,
79033
79187
  abort: () => {
@@ -79073,24 +79227,27 @@ var AgentTool = class {
79073
79227
  ].join("\n") };
79074
79228
  } catch (error) {
79075
79229
  let message;
79076
- if (foregroundDeadline?.timedOut() === true && args.timeout !== void 0) message = `Agent timed out after ${args.timeout}s.`;
79230
+ const timedOut = foregroundDeadline?.timedOut() === true;
79231
+ if (timedOut) message = `Agent timed out after ${AGENT_TIMEOUT_DESCRIPTION}.`;
79077
79232
  else if (isUserCancellation(signal.reason)) message = "The user manually interrupted this subagent (and any sibling agents launched alongside it). This was a deliberate user action, not a system error, a timeout, or a capacity/concurrency limit. Do not retry automatically or speculate about why it failed — wait for the user's next instruction.";
79078
79233
  else if (isAbortError$3(error)) message = "The subagent was stopped before it finished.";
79079
79234
  else message = error instanceof Error ? error.message : String(error);
79235
+ const lines = [
79236
+ `agent_id: ${handle.agentId}`,
79237
+ `actual_subagent_type: ${handle.profileName}`,
79238
+ "status: failed",
79239
+ "",
79240
+ `subagent error: ${message}`
79241
+ ];
79242
+ if (timedOut) lines.push(`resume_hint: Continue with Agent(resume="${handle.agentId}", prompt="continue"). Use agent_id only; do not set subagent_type. The subagent retains its prior context; redo any unfinished tool call if its result was lost.`);
79080
79243
  return {
79081
- output: [
79082
- `agent_id: ${handle.agentId}`,
79083
- `actual_subagent_type: ${handle.profileName}`,
79084
- "status: failed",
79085
- "",
79086
- `subagent error: ${message}`
79087
- ].join("\n"),
79244
+ output: lines.join("\n"),
79088
79245
  isError: true
79089
79246
  };
79090
79247
  }
79091
79248
  } catch (error) {
79092
79249
  let message;
79093
- if (foregroundDeadline?.timedOut() === true && args.timeout !== void 0) message = `Agent timed out after ${args.timeout}s.`;
79250
+ if (foregroundDeadline?.timedOut() === true) message = `Agent timed out after ${AGENT_TIMEOUT_DESCRIPTION}.`;
79094
79251
  else if (isUserCancellation(signal.reason)) message = "The user manually interrupted this subagent (and any sibling agents launched alongside it). This was a deliberate user action, not a system error, a timeout, or a capacity/concurrency limit. Do not retry automatically or speculate about why it failed — wait for the user's next instruction.";
79095
79252
  else if (isAbortError$3(error)) message = "The subagent was stopped before it finished.";
79096
79253
  else message = error instanceof Error ? error.message : String(error);
@@ -88328,7 +88485,7 @@ var Agent = class {
88328
88485
  }
88329
88486
  get llm() {
88330
88487
  const model = this.config.model;
88331
- const provider = this.config.provider.withThinking(this.config.thinkingLevel);
88488
+ const provider = this.config.provider;
88332
88489
  const loopControl = this.kimiConfig?.loopControl;
88333
88490
  const completionBudgetConfig = resolveCompletionBudget({
88334
88491
  maxOutputSize: this.config.maxOutputSize,
@@ -88349,7 +88506,7 @@ var Agent = class {
88349
88506
  this.logLlmConfigIfChanged(context, configMetadata, buildLlmConfigSignature(configMetadata, systemPrompt, tools));
88350
88507
  let partialMessageCount = 0;
88351
88508
  for (const message of history) if (message.partial === true) partialMessageCount += 1;
88352
- const requestMetadata = { estimatedInputTokens: estimateTokens(systemPrompt) + estimateTokensForMessages(history) + estimateTokensForTools(tools) };
88509
+ const requestMetadata = {};
88353
88510
  if (partialMessageCount > 0) requestMetadata.partialMessageCount = partialMessageCount;
88354
88511
  this.log.info("llm request", {
88355
88512
  ...context,
@@ -89101,7 +89258,6 @@ const BackgroundConfigSchema = z.object({
89101
89258
  maxRunningTasks: z.number().int().min(1).optional(),
89102
89259
  keepAliveOnExit: z.boolean().optional(),
89103
89260
  killGracePeriodMs: z.number().int().min(0).optional(),
89104
- agentTaskTimeoutS: z.number().int().min(1).optional(),
89105
89261
  printWaitCeilingS: z.number().int().min(1).optional()
89106
89262
  });
89107
89263
  const ExperimentalFlagIdSet = new Set(FLAG_DEFINITIONS.map((def) => def.id));
@@ -93248,19 +93404,19 @@ async function readMcpJson(filePath, options = {}) {
93248
93404
  text = await readFile(filePath, "utf-8");
93249
93405
  } catch (error) {
93250
93406
  if (isFileNotFound(error)) return {};
93251
- throw new KimiError(ErrorCodes.CONFIG_INVALID, `Failed to read ${filePath}: ${describeError(error)}`, { cause: error });
93407
+ throw new KimiError(ErrorCodes.CONFIG_INVALID, `Failed to read ${filePath}: ${describeError$1(error)}`, { cause: error });
93252
93408
  }
93253
93409
  if (text.trim().length === 0) return {};
93254
93410
  let data;
93255
93411
  try {
93256
93412
  data = JSON.parse(text);
93257
93413
  } catch (error) {
93258
- throw new KimiError(ErrorCodes.CONFIG_INVALID, `Invalid JSON in ${filePath}: ${describeError(error)}`, { cause: error });
93414
+ throw new KimiError(ErrorCodes.CONFIG_INVALID, `Invalid JSON in ${filePath}: ${describeError$1(error)}`, { cause: error });
93259
93415
  }
93260
93416
  try {
93261
93417
  return normalizeMcpServers(McpJsonFileSchema.parse(data).mcpServers, options);
93262
93418
  } catch (error) {
93263
- throw new KimiError(ErrorCodes.CONFIG_INVALID, `Invalid MCP server config in ${filePath}: ${describeError(error)}`, { cause: error });
93419
+ throw new KimiError(ErrorCodes.CONFIG_INVALID, `Invalid MCP server config in ${filePath}: ${describeError$1(error)}`, { cause: error });
93264
93420
  }
93265
93421
  }
93266
93422
  function normalizeMcpServers(servers, options) {
@@ -93290,7 +93446,7 @@ function getErrorCode(error) {
93290
93446
  if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
93291
93447
  return error.code;
93292
93448
  }
93293
- function describeError(error) {
93449
+ function describeError$1(error) {
93294
93450
  return error instanceof Error ? error.message : String(error);
93295
93451
  }
93296
93452
  //#endregion
@@ -93500,12 +93656,10 @@ IMPORTANT:
93500
93656
  var SessionSubagentHost = class {
93501
93657
  session;
93502
93658
  ownerAgentId;
93503
- backgroundTaskTimeoutMs;
93504
93659
  activeChildren = /* @__PURE__ */ new Map();
93505
- constructor(session, ownerAgentId, backgroundTaskTimeoutMs) {
93660
+ constructor(session, ownerAgentId) {
93506
93661
  this.session = session;
93507
93662
  this.ownerAgentId = ownerAgentId;
93508
- this.backgroundTaskTimeoutMs = backgroundTaskTimeoutMs;
93509
93663
  }
93510
93664
  async spawn(profileName, options) {
93511
93665
  options.signal.throwIfAborted();
@@ -93785,7 +93939,10 @@ var Session$1 = class {
93785
93939
  },
93786
93940
  telemetry: this.telemetry
93787
93941
  });
93788
- this.skills = new SkillRegistry({ sessionId: options.id });
93942
+ this.skills = new SkillRegistry({
93943
+ sessionId: options.id,
93944
+ experimentalFlags: this.experimentalFlags
93945
+ });
93789
93946
  this.mcp = new McpConnectionManager({
93790
93947
  oauthService: new McpOAuthService({ kimiHomeDir: options.kimiHomeDir }),
93791
93948
  log: this.log
@@ -93965,7 +94122,7 @@ var Session$1 = class {
93965
94122
  builtinDir: this.options.skills?.builtinDir
93966
94123
  });
93967
94124
  await this.skills.loadRoots(roots);
93968
- registerBuiltinSkills(this.skills);
94125
+ registerBuiltinSkills(this.skills, { experimentalFlags: this.experimentalFlags });
93969
94126
  }
93970
94127
  async loadMcpServers() {
93971
94128
  const servers = this.options.mcpConfig?.servers;
@@ -94007,10 +94164,6 @@ var Session$1 = class {
94007
94164
  }
94008
94165
  });
94009
94166
  }
94010
- backgroundTaskTimeoutMs() {
94011
- const timeoutS = this.options.background?.agentTaskTimeoutS;
94012
- return timeoutS === void 0 ? void 0 : timeoutS * 1e3;
94013
- }
94014
94167
  refreshAgentBuiltinTools() {
94015
94168
  for (const agent of this.readyAgents()) {
94016
94169
  if (!agent.config.hasProvider) continue;
@@ -94030,7 +94183,7 @@ var Session$1 = class {
94030
94183
  rpc: proxyWithExtraPayload(this.rpc, { agentId: id }),
94031
94184
  modelProvider: this.options.providerManager,
94032
94185
  hookEngine: config.hookEngine ?? this.hookEngine,
94033
- subagentHost: config.subagentHost ?? new SessionSubagentHost(this, id, this.backgroundTaskTimeoutMs()),
94186
+ subagentHost: config.subagentHost ?? new SessionSubagentHost(this, id),
94034
94187
  mcp: this.mcp,
94035
94188
  goals: this.goals,
94036
94189
  permission: this.permissionOptions(parentAgentId, config.permission),
@@ -114550,39 +114703,6 @@ function canPreserveDefaultModel(existingModels, defaultModel, managedModels) {
114550
114703
  const existing = existingModels[defaultModel];
114551
114704
  return isRecord$8(existing) && existing["provider"] !== "managed:kimi-code";
114552
114705
  }
114553
- function clearManagedKimiCodeConfig(config) {
114554
- const removedProvider = Object.hasOwn(config.providers, KIMI_CODE_PROVIDER_NAME);
114555
- delete config.providers[KIMI_CODE_PROVIDER_NAME];
114556
- const removedModels = [];
114557
- const models = config.models;
114558
- if (models !== void 0) for (const [key, model] of Object.entries(models)) {
114559
- if (!isRecord$8(model) || model["provider"] !== "managed:kimi-code") continue;
114560
- delete models[key];
114561
- removedModels.push(key);
114562
- }
114563
- let defaultModelCleared = false;
114564
- if (typeof config.defaultModel === "string" && removedModels.includes(config.defaultModel)) {
114565
- config.defaultModel = void 0;
114566
- defaultModelCleared = true;
114567
- }
114568
- const removedServices = [];
114569
- if (config.services?.moonshotSearch !== void 0) {
114570
- delete config.services.moonshotSearch;
114571
- removedServices.push("moonshotSearch");
114572
- }
114573
- if (config.services?.moonshotFetch !== void 0) {
114574
- delete config.services.moonshotFetch;
114575
- removedServices.push("moonshotFetch");
114576
- }
114577
- if (config.services !== void 0 && Object.keys(config.services).length === 0) config.services = void 0;
114578
- return {
114579
- providerName: KIMI_CODE_PROVIDER_NAME,
114580
- removedProvider,
114581
- removedModels,
114582
- defaultModelCleared,
114583
- removedServices
114584
- };
114585
- }
114586
114706
  function assertPositiveContextLength(model) {
114587
114707
  if (!Number.isInteger(model.contextLength) || model.contextLength <= 0) throw new Error(`Kimi Code model "${model.id}" must include a positive context_length.`);
114588
114708
  }
@@ -114751,19 +114871,6 @@ function applyOpenPlatformConfig(config, options) {
114751
114871
  defaultThinking: options.thinking
114752
114872
  };
114753
114873
  }
114754
- function removeOpenPlatformConfig(config, platformId) {
114755
- delete config.providers[platformId];
114756
- let removedDefault = false;
114757
- const existingModels = config.models ?? {};
114758
- for (const [key, model] of Object.entries(existingModels)) {
114759
- if (!isRecord$8(model) || model["provider"] !== platformId) continue;
114760
- delete existingModels[key];
114761
- if (config.defaultModel === key) removedDefault = true;
114762
- }
114763
- config.models = existingModels;
114764
- if (removedDefault) config.defaultModel = void 0;
114765
- if (config["defaultProvider"] === platformId) config["defaultProvider"] = void 0;
114766
- }
114767
114874
  //#endregion
114768
114875
  //#region ../../packages/oauth/src/custom-registry.ts
114769
114876
  /**
@@ -126347,7 +126454,7 @@ function errorMessage(error) {
126347
126454
  //#endregion
126348
126455
  //#region src/cli/commands.ts
126349
126456
  function createProgram(version, onMain, onMigrate, onPluginNodeRunner = () => {}, onUpgrade = () => {}) {
126350
- const program = new Command(CLI_COMMAND_NAME).description("The Starting Point for Next-Gen Agents").version(version, "-V, --version").allowUnknownOption(false).configureHelp({ helpWidth: 100 }).helpOption("-h, --help", "Show help.").addHelpText("after", "\nDocumentation: https://moonshotai.github.io/kimi-code/\n");
126457
+ const program = new Command(CLI_COMMAND_NAME).description("The Starting Point for Next-Gen Agents").version(version, "-V, --version").allowUnknownOption(false).configureHelp({ helpWidth: 100 }).helpOption("-h, --help", "Show help.").usage("[options] [command]").addHelpText("after", "\nDocumentation: https://moonshotai.github.io/kimi-code/\n");
126351
126458
  program.addOption(new Option("-S, --session [id]", "Resume a session. With ID: resume that session. Without ID: interactively pick.").argParser((val) => val === true ? "" : val)).addOption(new Option("-r, --resume [id]").hideHelp().argParser((val) => val === true ? "" : val)).option("-C, --continue", "Continue the previous session for the working directory.", false).option("-y, --yolo", "Automatically approve all actions.", false).option("--auto", "Start in auto permission mode.", false).addOption(new Option("-m, --model <model>", "LLM model alias to use for this invocation. Defaults to default_model in config.toml.")).addOption(new Option("-p, --prompt <prompt>", "Run one prompt non-interactively and print the response.")).addOption(new Option("--output-format <format>", "Output format for prompt mode. Defaults to text.").choices(["text", "stream-json"])).addOption(new Option("--skills-dir <dir>", "Load skills from this directory instead of auto-discovered user and project directories. Can be repeated.").argParser((value, previous) => [...previous ?? [], value]).default([])).addOption(new Option("--yes").hideHelp().default(false)).addOption(new Option("--auto-approve").hideHelp().default(false)).option("--plan", "Start in plan mode.", false);
126352
126459
  registerExportCommand(program);
126353
126460
  registerProviderCommand(program);
@@ -126361,7 +126468,8 @@ function createProgram(version, onMain, onMigrate, onPluginNodeRunner = () => {}
126361
126468
  program.command("__plugin_run_node", { hidden: true }).argument("<entry>").argument("[args...]").allowUnknownOption(true).action((entry, args) => {
126362
126469
  onPluginNodeRunner(entry, args);
126363
126470
  });
126364
- program.action(() => {
126471
+ program.argument("[args...]").action((args) => {
126472
+ if (args.length > 0) program.error(`unknown command '${args[0]}'. See '${CLI_COMMAND_NAME} --help'.`);
126365
126473
  const raw = program.opts();
126366
126474
  const rawSession = raw["session"] ?? raw["resume"];
126367
126475
  const sessionValue = rawSession === true ? "" : rawSession;
@@ -126805,11 +126913,17 @@ function slashBusyMessage(commandName, reason) {
126805
126913
  function isUserActivatableSkill(skill) {
126806
126914
  return skill.type === void 0 || skill.type === "prompt" || skill.type === "inline" || skill.type === "flow";
126807
126915
  }
126916
+ function compareSkillSlashCommands(a, b) {
126917
+ return getSkillSlashCommandGroup(a.source) - getSkillSlashCommandGroup(b.source) || a.name.localeCompare(b.name);
126918
+ }
126919
+ function getSkillSlashCommandGroup(source) {
126920
+ return source === "builtin" ? 0 : 1;
126921
+ }
126808
126922
  function buildSkillSlashCommands(skills) {
126809
126923
  const commandMap = /* @__PURE__ */ new Map();
126810
126924
  return {
126811
- commands: skills.filter(isUserActivatableSkill).map((skill) => {
126812
- const commandName = `skill:${skill.name}`;
126925
+ commands: [...skills].toSorted(compareSkillSlashCommands).filter(isUserActivatableSkill).map((skill) => {
126926
+ const commandName = skill.source === "builtin" ? skill.name : `skill:${skill.name}`;
126813
126927
  commandMap.set(commandName, skill.name);
126814
126928
  return {
126815
126929
  name: commandName,
@@ -126890,9 +127004,28 @@ function isTodoItemShape(value) {
126890
127004
  return rec.status === "pending" || rec.status === "in_progress" || rec.status === "done";
126891
127005
  }
126892
127006
  function formatErrorMessage$2(error) {
126893
- if (isKimiError(error)) return `[${error.code}] ${error.message}`;
127007
+ if (isKimiError(error)) return formatErrorPayload({
127008
+ code: error.code,
127009
+ message: error.message,
127010
+ details: error.details
127011
+ });
126894
127012
  return error instanceof Error ? error.message : String(error);
126895
127013
  }
127014
+ function formatErrorPayload(error) {
127015
+ const filteredMessage = formatProviderFilteredMessage(error.details);
127016
+ if (filteredMessage !== void 0) return `[${error.code}] ${filteredMessage}`;
127017
+ return `[${error.code}] ${error.message}`;
127018
+ }
127019
+ function formatProviderFilteredMessage(details) {
127020
+ const finishReason = stringDetail(details, "finishReason");
127021
+ const rawFinishReason = stringDetail(details, "rawFinishReason");
127022
+ if (finishReason !== "filtered" && rawFinishReason !== "content_filter") return void 0;
127023
+ return `Provider filtered the response before visible output (finishReason=${finishReason ?? "filtered"}${rawFinishReason === void 0 ? "" : `, rawFinishReason=${rawFinishReason}`}).`;
127024
+ }
127025
+ function stringDetail(details, key) {
127026
+ const value = details?.[key];
127027
+ return typeof value === "string" ? value : void 0;
127028
+ }
126896
127029
  function stringValue(value) {
126897
127030
  return typeof value === "string" ? value : void 0;
126898
127031
  }
@@ -129740,7 +129873,7 @@ function handleSettingsSelection(host, value) {
129740
129873
  }
129741
129874
  //#endregion
129742
129875
  //#region src/tui/components/dialogs/goal-start-permission-prompt.ts
129743
- const OPTIONS = [
129876
+ const MANUAL_OPTIONS = [
129744
129877
  {
129745
129878
  value: "auto",
129746
129879
  label: "Switch to Auto and start",
@@ -129762,11 +129895,33 @@ const OPTIONS = [
129762
129895
  description: "Return to the input box with your goal command."
129763
129896
  }
129764
129897
  ];
129765
- const NOTICE_LINES = [
129898
+ const YOLO_OPTIONS = [
129899
+ {
129900
+ value: "auto",
129901
+ label: "Switch to Auto and start",
129902
+ description: "Best if you want Kimi Code to keep working while you are away. Tools are approved automatically, and questions are skipped."
129903
+ },
129904
+ {
129905
+ value: "yolo",
129906
+ label: "Keep YOLO and start",
129907
+ description: "Tools and plan changes stay approved automatically. Kimi Code may still ask you questions."
129908
+ },
129909
+ {
129910
+ value: "cancel",
129911
+ label: "Do not start",
129912
+ description: "Return to the input box with your goal command."
129913
+ }
129914
+ ];
129915
+ const MANUAL_NOTICE_LINES = [
129766
129916
  "Manual mode asks you before Kimi Code runs commands, edits files, or takes other risky actions.",
129767
129917
  "Manual mode is not suitable for unattended goal work.",
129768
129918
  "You can go back without losing your command."
129769
129919
  ];
129920
+ const YOLO_NOTICE_LINES = [
129921
+ "YOLO mode approves tools and plan changes automatically.",
129922
+ "YOLO mode can still stop for questions.",
129923
+ "Switch to Auto if you want questions skipped during goal work."
129924
+ ];
129770
129925
  var GoalStartPermissionPromptComponent = class {
129771
129926
  opts;
129772
129927
  focused = false;
@@ -129785,27 +129940,27 @@ var GoalStartPermissionPromptComponent = class {
129785
129940
  return;
129786
129941
  }
129787
129942
  if (matchesKey(data, Key.down)) {
129788
- this.selectedIndex = Math.min(OPTIONS.length - 1, this.selectedIndex + 1);
129943
+ this.selectedIndex = Math.min(this.options.length - 1, this.selectedIndex + 1);
129789
129944
  return;
129790
129945
  }
129791
- if (matchesKey(data, Key.enter) || matchesKey(data, Key.space)) this.opts.onSelect(OPTIONS[this.selectedIndex].value);
129946
+ if (matchesKey(data, Key.enter) || matchesKey(data, Key.space)) this.opts.onSelect(this.options[this.selectedIndex].value);
129792
129947
  }
129793
129948
  render(width) {
129794
129949
  const { colors } = this.opts;
129795
129950
  const rule = chalk.hex(colors.primary)("─".repeat(width));
129796
129951
  const lines = [
129797
129952
  rule,
129798
- chalk.hex(colors.primary).bold(" Start a goal with approvals on?"),
129799
- chalk.hex(colors.textMuted)(" ↑↓ navigate · Enter select · Esc return to input box"),
129953
+ chalk.hex(colors.primary).bold(` ${this.title}`),
129954
+ chalk.hex(colors.textMuted)(" ↑↓ navigate · Enter select · Esc cancel"),
129800
129955
  ""
129801
129956
  ];
129802
129957
  const textWidth = Math.max(20, width - 2);
129803
- for (const paragraph of NOTICE_LINES) {
129958
+ for (const paragraph of this.noticeLines) {
129804
129959
  for (const line of wrapPlain(paragraph, textWidth)) lines.push(` ${styleModeNames(line, colors, colors.textMuted)}`);
129805
129960
  lines.push("");
129806
129961
  }
129807
- for (let i = 0; i < OPTIONS.length; i += 1) {
129808
- const option = OPTIONS[i];
129962
+ for (let i = 0; i < this.options.length; i += 1) {
129963
+ const option = this.options[i];
129809
129964
  const selected = i === this.selectedIndex;
129810
129965
  const pointer = selected ? "❯" : " ";
129811
129966
  lines.push(chalk.hex(selected ? colors.primary : colors.textDim)(` ${pointer} `) + styleLabel(option.label, selected, colors));
@@ -129815,6 +129970,15 @@ var GoalStartPermissionPromptComponent = class {
129815
129970
  lines.push(rule);
129816
129971
  return lines.map((line) => truncateToWidth(line, width));
129817
129972
  }
129973
+ get options() {
129974
+ return this.opts.mode === "yolo" ? YOLO_OPTIONS : MANUAL_OPTIONS;
129975
+ }
129976
+ get noticeLines() {
129977
+ return this.opts.mode === "yolo" ? YOLO_NOTICE_LINES : MANUAL_NOTICE_LINES;
129978
+ }
129979
+ get title() {
129980
+ return this.opts.mode === "yolo" ? "Start a goal in YOLO mode?" : "Start a goal with approvals on?";
129981
+ }
129818
129982
  };
129819
129983
  function styleLabel(label, selected, colors) {
129820
129984
  if (selected) return chalk.hex(colors.primary).bold(label);
@@ -129847,8 +130011,14 @@ function wrapPlain(text, width) {
129847
130011
  //#endregion
129848
130012
  //#region src/tui/components/dialogs/goal-queue-manager.ts
129849
130013
  const MAX_GOAL_OBJECTIVE_LENGTH$2 = 4e3;
130014
+ const MAX_EDIT_INPUT_LINES = 8;
129850
130015
  const ELLIPSIS$5 = "…";
129851
- const END_KEY = "\x1B[F";
130016
+ const BRACKET_PASTE_START$1 = "\x1B[200~";
130017
+ const BRACKET_PASTE_END$1 = "\x1B[201~";
130018
+ const SHIFT_ENTER_LEGACY = "\x1B\r";
130019
+ const SHIFT_ENTER_CSI = "\x1B[13;2~";
130020
+ const SEGMENTER = new Intl.Segmenter(void 0, { granularity: "grapheme" });
130021
+ const ANSI_CSI = /\u001B\[[0-?]*[ -/]*[@-~]/g;
129852
130022
  var GoalQueueManagerComponent = class extends Container {
129853
130023
  focused = false;
129854
130024
  opts;
@@ -129945,7 +130115,7 @@ var GoalQueueManagerComponent = class extends Container {
129945
130115
  const labelWidth = visibleWidth(labelPrefix);
129946
130116
  const stateWidth = visibleWidth(stateLabel);
129947
130117
  const objectiveWidth = Math.max(1, width - 5 - labelWidth - stateWidth);
129948
- const objective = truncateToWidth(goal.objective, objectiveWidth, ELLIPSIS$5);
130118
+ const objective = truncateToWidth(formatListObjective(goal.objective), objectiveWidth, ELLIPSIS$5);
129949
130119
  let line = prefix + (selected ? chalk.hex(colors.primary).bold : chalk.hex(colors.text))(labelPrefix + objective);
129950
130120
  if (moving) line += chalk.hex(colors.success)(stateLabel);
129951
130121
  return line;
@@ -129981,7 +130151,7 @@ var GoalQueueManagerComponent = class extends Container {
129981
130151
  };
129982
130152
  var GoalQueueEditDialogComponent = class extends Container {
129983
130153
  focused = false;
129984
- input = new Input();
130154
+ input = new MultilineGoalInput();
129985
130155
  opts;
129986
130156
  done = false;
129987
130157
  error;
@@ -129989,7 +130159,6 @@ var GoalQueueEditDialogComponent = class extends Container {
129989
130159
  super();
129990
130160
  this.opts = opts;
129991
130161
  this.input.setValue(opts.goal.objective);
129992
- this.input.handleInput(END_KEY);
129993
130162
  this.input.onSubmit = (value) => {
129994
130163
  this.submit(value);
129995
130164
  };
@@ -130018,14 +130187,18 @@ var GoalQueueEditDialogComponent = class extends Container {
130018
130187
  const pad = " ";
130019
130188
  const { colors } = this.opts;
130020
130189
  const border = (s) => chalk.hex(colors.primary)(s);
130190
+ const title = truncateToWidth(chalk.hex(colors.textStrong).bold("Edit upcoming goal"), innerWidth, ELLIPSIS$5);
130191
+ const subtitle = truncateToWidth(chalk.hex(this.error === void 0 ? colors.textDim : colors.warning)(this.error ?? "Update the queued objective."), innerWidth, ELLIPSIS$5);
130192
+ const inputLines = this.input.render(innerWidth);
130193
+ const footer = truncateToWidth(chalk.hex(colors.textDim)("Enter submit · Shift-Enter/Ctrl-J newline · Esc cancel"), innerWidth, ELLIPSIS$5);
130021
130194
  const contentLines = [
130022
- truncateToWidth(chalk.hex(colors.textStrong).bold("Edit upcoming goal"), innerWidth, ELLIPSIS$5),
130195
+ title,
130023
130196
  "",
130024
- truncateToWidth(chalk.hex(this.error === void 0 ? colors.textDim : colors.warning)(this.error ?? "Update the queued objective."), innerWidth, ELLIPSIS$5),
130197
+ subtitle,
130025
130198
  "",
130026
- this.input.render(innerWidth)[0] ?? "> ",
130199
+ ...inputLines,
130027
130200
  "",
130028
- truncateToWidth(chalk.hex(colors.textDim)("Enter submit · Esc cancel"), innerWidth, ELLIPSIS$5)
130201
+ footer
130029
130202
  ];
130030
130203
  const lines = [
130031
130204
  "",
@@ -130058,6 +130231,261 @@ var GoalQueueEditDialogComponent = class extends Container {
130058
130231
  });
130059
130232
  }
130060
130233
  };
130234
+ var MultilineGoalInput = class {
130235
+ focused = false;
130236
+ onSubmit;
130237
+ value = "";
130238
+ cursor = 0;
130239
+ pasteBuffer;
130240
+ getValue() {
130241
+ return this.value;
130242
+ }
130243
+ setValue(value) {
130244
+ this.value = normalizeNewlines(value);
130245
+ this.cursor = this.value.length;
130246
+ }
130247
+ handleInput(data) {
130248
+ if (this.handleBracketedPaste(data)) return;
130249
+ if (isNewlineInput(data)) {
130250
+ this.insert("\n");
130251
+ return;
130252
+ }
130253
+ if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
130254
+ this.onSubmit?.(this.value);
130255
+ return;
130256
+ }
130257
+ if (matchesKey(data, Key.backspace)) {
130258
+ this.deleteBeforeCursor();
130259
+ return;
130260
+ }
130261
+ if (matchesKey(data, Key.delete)) {
130262
+ this.deleteAfterCursor();
130263
+ return;
130264
+ }
130265
+ if (matchesKey(data, Key.left)) {
130266
+ this.cursor = previousGraphemeStart(this.value, this.cursor);
130267
+ return;
130268
+ }
130269
+ if (matchesKey(data, Key.right)) {
130270
+ this.cursor = nextGraphemeEnd(this.value, this.cursor);
130271
+ return;
130272
+ }
130273
+ if (matchesKey(data, Key.up)) {
130274
+ this.moveVertical(-1);
130275
+ return;
130276
+ }
130277
+ if (matchesKey(data, Key.down)) {
130278
+ this.moveVertical(1);
130279
+ return;
130280
+ }
130281
+ if (matchesKey(data, Key.home) || matchesKey(data, Key.ctrl("a"))) {
130282
+ this.cursor = this.currentLineStart();
130283
+ return;
130284
+ }
130285
+ if (matchesKey(data, Key.end) || matchesKey(data, Key.ctrl("e"))) {
130286
+ this.cursor = this.currentLineEnd();
130287
+ return;
130288
+ }
130289
+ const decoded = printableChar(data);
130290
+ if (isPrintableText(decoded)) this.insert(decoded);
130291
+ }
130292
+ invalidate() {}
130293
+ render(width) {
130294
+ const safeWidth = Math.max(4, width);
130295
+ const logicalLines = this.value.split("\n");
130296
+ const cursor = this.cursorLocation();
130297
+ const range = visibleLineRange(logicalLines.length, cursor.line);
130298
+ const rendered = [];
130299
+ if (range.start > 0) rendered.push(padInputLine(` ${ELLIPSIS$5} ${String(range.start)} previous`, safeWidth));
130300
+ for (let lineIndex = range.start; lineIndex < range.end; lineIndex++) {
130301
+ const line = logicalLines[lineIndex] ?? "";
130302
+ const prefix = lineIndex === 0 ? "> " : " ";
130303
+ rendered.push(lineIndex === cursor.line ? renderCursorLine(line, cursor.column, prefix, safeWidth, this.focused) : renderTextLine(line, prefix, safeWidth));
130304
+ }
130305
+ const remaining = logicalLines.length - range.end;
130306
+ if (remaining > 0) rendered.push(padInputLine(` ${ELLIPSIS$5} ${String(remaining)} more`, safeWidth));
130307
+ return rendered;
130308
+ }
130309
+ insert(text) {
130310
+ const normalized = normalizeNewlines(text);
130311
+ this.value = this.value.slice(0, this.cursor) + normalized + this.value.slice(this.cursor);
130312
+ this.cursor += normalized.length;
130313
+ }
130314
+ deleteBeforeCursor() {
130315
+ if (this.cursor === 0) return;
130316
+ const start = previousGraphemeStart(this.value, this.cursor);
130317
+ this.value = this.value.slice(0, start) + this.value.slice(this.cursor);
130318
+ this.cursor = start;
130319
+ }
130320
+ deleteAfterCursor() {
130321
+ if (this.cursor >= this.value.length) return;
130322
+ const end = nextGraphemeEnd(this.value, this.cursor);
130323
+ this.value = this.value.slice(0, this.cursor) + this.value.slice(end);
130324
+ }
130325
+ moveVertical(delta) {
130326
+ const starts = lineStarts(this.value);
130327
+ const location = this.cursorLocation(starts);
130328
+ const targetLine = location.line + delta;
130329
+ if (targetLine < 0 || targetLine >= starts.length) return;
130330
+ const targetStart = starts[targetLine] ?? 0;
130331
+ const targetEnd = lineEndForStart(this.value, starts, targetLine);
130332
+ this.cursor = Math.min(targetStart + location.column, targetEnd);
130333
+ }
130334
+ currentLineStart() {
130335
+ return this.value.lastIndexOf("\n", Math.max(0, this.cursor - 1)) + 1;
130336
+ }
130337
+ currentLineEnd() {
130338
+ return lineEndAt(this.value, this.cursor);
130339
+ }
130340
+ cursorLocation(starts = lineStarts(this.value)) {
130341
+ let line = 0;
130342
+ for (let i = 0; i < starts.length; i++) {
130343
+ if ((starts[i] ?? 0) > this.cursor) break;
130344
+ line = i;
130345
+ }
130346
+ const lineStart = starts[line] ?? 0;
130347
+ return {
130348
+ line,
130349
+ column: this.cursor - lineStart
130350
+ };
130351
+ }
130352
+ handleBracketedPaste(data) {
130353
+ if (this.pasteBuffer !== void 0) {
130354
+ this.appendPasteChunk(data);
130355
+ return true;
130356
+ }
130357
+ const start = data.indexOf(BRACKET_PASTE_START$1);
130358
+ if (start === -1) return false;
130359
+ this.pasteBuffer = "";
130360
+ const before = data.slice(0, start);
130361
+ if (isPrintableText(before)) this.insert(before);
130362
+ this.appendPasteChunk(data.slice(start + 6));
130363
+ return true;
130364
+ }
130365
+ appendPasteChunk(data) {
130366
+ if (this.pasteBuffer === void 0) return;
130367
+ this.pasteBuffer += data;
130368
+ const end = this.pasteBuffer.indexOf(BRACKET_PASTE_END$1);
130369
+ if (end === -1) return;
130370
+ const pasted = this.pasteBuffer.slice(0, end);
130371
+ const remaining = this.pasteBuffer.slice(end + 6);
130372
+ this.pasteBuffer = void 0;
130373
+ this.insert(sanitizePastedText(pasted));
130374
+ if (remaining.length > 0) this.handleInput(remaining);
130375
+ }
130376
+ };
130377
+ function isNewlineInput(data) {
130378
+ return data === "\n" || data === SHIFT_ENTER_LEGACY || data === SHIFT_ENTER_CSI || matchesKey(data, Key.ctrl("j"));
130379
+ }
130380
+ function normalizeNewlines(text) {
130381
+ return text.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
130382
+ }
130383
+ function formatListObjective(objective) {
130384
+ return objective.replaceAll(/\s+/g, " ").trim();
130385
+ }
130386
+ function sanitizePastedText(text) {
130387
+ const normalized = normalizeNewlines(text).replaceAll(ANSI_CSI, "");
130388
+ let out = "";
130389
+ for (let i = 0; i < normalized.length;) {
130390
+ const code = normalized.codePointAt(i);
130391
+ if (code === void 0) break;
130392
+ const char = String.fromCodePoint(code);
130393
+ if (char === "\n" || isPrintableText(char)) out += char;
130394
+ i += code > 65535 ? 2 : 1;
130395
+ }
130396
+ return out;
130397
+ }
130398
+ function isPrintableText(text) {
130399
+ if (text.length === 0) return false;
130400
+ for (let i = 0; i < text.length;) {
130401
+ const code = text.codePointAt(i);
130402
+ if (code === void 0) return false;
130403
+ if (code < 32 || code === 127 || code >= 128 && code <= 159) return false;
130404
+ i += code > 65535 ? 2 : 1;
130405
+ }
130406
+ return true;
130407
+ }
130408
+ function lineStarts(text) {
130409
+ const starts = [0];
130410
+ for (let i = 0; i < text.length; i++) if (text[i] === "\n") starts.push(i + 1);
130411
+ return starts;
130412
+ }
130413
+ function lineEndAt(text, offset) {
130414
+ const end = text.indexOf("\n", offset);
130415
+ return end === -1 ? text.length : end;
130416
+ }
130417
+ function lineEndForStart(text, starts, line) {
130418
+ const nextStart = starts[line + 1];
130419
+ return nextStart === void 0 ? text.length : nextStart - 1;
130420
+ }
130421
+ function previousGraphemeStart(text, offset) {
130422
+ if (offset <= 0) return 0;
130423
+ let previous = 0;
130424
+ for (const segment of SEGMENTER.segment(text.slice(0, offset))) previous = segment.index;
130425
+ return previous;
130426
+ }
130427
+ function nextGraphemeEnd(text, offset) {
130428
+ if (offset >= text.length) return text.length;
130429
+ const segment = SEGMENTER.segment(text.slice(offset))[Symbol.iterator]().next().value;
130430
+ return segment === void 0 ? text.length : offset + segment.segment.length;
130431
+ }
130432
+ function visibleLineRange(totalLines, cursorLine) {
130433
+ if (totalLines <= MAX_EDIT_INPUT_LINES) return {
130434
+ start: 0,
130435
+ end: totalLines
130436
+ };
130437
+ const half = Math.floor(MAX_EDIT_INPUT_LINES / 2);
130438
+ const start = Math.min(Math.max(0, cursorLine - half), Math.max(0, totalLines - MAX_EDIT_INPUT_LINES));
130439
+ return {
130440
+ start,
130441
+ end: start + MAX_EDIT_INPUT_LINES
130442
+ };
130443
+ }
130444
+ function renderTextLine(line, prefix, width) {
130445
+ const prefixWidth = visibleWidth(prefix);
130446
+ return padInputLine(prefix + truncateToWidth(line, Math.max(1, width - prefixWidth), ELLIPSIS$5), width);
130447
+ }
130448
+ function renderCursorLine(line, column, prefix, width, focused) {
130449
+ const prefixWidth = visibleWidth(prefix);
130450
+ const textWidth = Math.max(1, width - prefixWidth);
130451
+ const cursorEnd = nextGraphemeEnd(line, column);
130452
+ const before = line.slice(0, column);
130453
+ const cursorText = line.slice(column, cursorEnd) || " ";
130454
+ const after = line.slice(cursorEnd);
130455
+ const cursorWidth = Math.max(1, visibleWidth(cursorText));
130456
+ const beforeView = takeEndByWidth(before, Math.max(0, textWidth - cursorWidth));
130457
+ const afterView = takeStartByWidth(after, Math.max(0, textWidth - visibleWidth(beforeView) - cursorWidth));
130458
+ const marker = focused ? CURSOR_MARKER : "";
130459
+ return padInputLine(prefix + beforeView + marker + chalk.inverse(cursorText) + afterView, width);
130460
+ }
130461
+ function takeStartByWidth(text, width) {
130462
+ let out = "";
130463
+ let used = 0;
130464
+ for (const segment of SEGMENTER.segment(text)) {
130465
+ const segmentWidth = visibleWidth(segment.segment);
130466
+ if (used + segmentWidth > width) break;
130467
+ out += segment.segment;
130468
+ used += segmentWidth;
130469
+ }
130470
+ return out;
130471
+ }
130472
+ function takeEndByWidth(text, width) {
130473
+ let out = "";
130474
+ let used = 0;
130475
+ const segments = [...SEGMENTER.segment(text)];
130476
+ for (let i = segments.length - 1; i >= 0; i--) {
130477
+ const segment = segments[i];
130478
+ if (segment === void 0) continue;
130479
+ const segmentWidth = visibleWidth(segment.segment);
130480
+ if (used + segmentWidth > width) break;
130481
+ out = segment.segment + out;
130482
+ used += segmentWidth;
130483
+ }
130484
+ return out;
130485
+ }
130486
+ function padInputLine(line, width) {
130487
+ return line + " ".repeat(Math.max(0, width - visibleWidth(line)));
130488
+ }
130061
130489
  //#endregion
130062
130490
  //#region src/tui/constant/rendering.ts
130063
130491
  const BRAILLE_SPINNER_FRAMES = [
@@ -130088,6 +130516,9 @@ const WRAP_WIDTH = 72;
130088
130516
  const MAX_OBJECTIVE_LINES = 6;
130089
130517
  const MAX_CRITERION_LINES = 3;
130090
130518
  const LABEL_WIDTH = 11;
130519
+ function renderLifecycleLine(label, colors) {
130520
+ return ["", chalk.hex(colors.primary).bold(STATUS_BULLET) + chalk.hex(colors.primary).bold(label)];
130521
+ }
130091
130522
  /**
130092
130523
  * The "Goal set" confirmation shown after `/goal <objective>`. The objective is
130093
130524
  * rendered as the following user prompt, so this message only marks the state
@@ -130100,7 +130531,17 @@ var GoalSetMessageComponent = class {
130100
130531
  }
130101
130532
  invalidate() {}
130102
130533
  render(_width) {
130103
- return ["", chalk.hex(this.colors.primary).bold(STATUS_BULLET) + chalk.hex(this.colors.primary).bold("Goal set")];
130534
+ return renderLifecycleLine("Goal set", this.colors);
130535
+ }
130536
+ };
130537
+ var UpcomingGoalAddedMessageComponent = class {
130538
+ colors;
130539
+ constructor(colors) {
130540
+ this.colors = colors;
130541
+ }
130542
+ invalidate() {}
130543
+ render(_width) {
130544
+ return renderLifecycleLine("Upcoming goal added. It will start after the current goal is complete.", this.colors);
130104
130545
  }
130105
130546
  };
130106
130547
  var GoalCompletionMessageComponent = class {
@@ -130184,7 +130625,7 @@ function statusHex(status, colors) {
130184
130625
  case "active": return colors.primary;
130185
130626
  case "complete": return colors.success;
130186
130627
  case "blocked": return colors.warning;
130187
- default: return colors.textDim;
130628
+ case "paused": return colors.textDim;
130188
130629
  }
130189
130630
  }
130190
130631
  function formatElapsed$1(ms) {
@@ -130197,7 +130638,7 @@ function formatElapsed$1(ms) {
130197
130638
  }
130198
130639
  /** Word-wrap to `width`, capped at `maxLines` (last line gets an ellipsis when clipped). */
130199
130640
  function wrap$1(text, width, maxLines) {
130200
- const words = text.replace(/\s+/g, " ").trim().split(" ");
130641
+ const words = text.replaceAll(/\s+/g, " ").trim().split(" ");
130201
130642
  const lines = [];
130202
130643
  let current = "";
130203
130644
  for (const word of words) {
@@ -130319,10 +130760,8 @@ async function readQueueFile(session) {
130319
130760
  let parsed;
130320
130761
  try {
130321
130762
  parsed = JSON.parse(raw);
130322
- } catch {
130323
- const empty = emptyQueueFile();
130324
- await writeQueueFile(session, empty);
130325
- return empty;
130763
+ } catch (error) {
130764
+ throw new KimiError(ErrorCodes.CONFIG_INVALID, `Invalid JSON in goal queue: ${describeError(error)}`);
130326
130765
  }
130327
130766
  if (!isGoalQueueFile(parsed)) {
130328
130767
  const empty = emptyQueueFile();
@@ -130390,10 +130829,14 @@ function timestampAfter(previous) {
130390
130829
  function isErrno(error, code) {
130391
130830
  return isRecord$4(error) && error["code"] === code;
130392
130831
  }
130832
+ function describeError(error) {
130833
+ return error instanceof Error ? error.message : String(error);
130834
+ }
130393
130835
  //#endregion
130394
130836
  //#region src/tui/commands/goal.ts
130395
130837
  const MAX_GOAL_OBJECTIVE_LENGTH = 4e3;
130396
130838
  const RESUME_GOAL_INPUT = "Resume the active goal.";
130839
+ const START_NEXT_GOAL_NOW_MESSAGE = "No active goal. Starting this goal now.";
130397
130840
  const CONTROL_SUBCOMMANDS = new Set([
130398
130841
  "pause",
130399
130842
  "resume",
@@ -130488,14 +130931,34 @@ function parseNextGoalCommand(tokens) {
130488
130931
  };
130489
130932
  }
130490
130933
  async function queueNextGoal(host, parsed) {
130934
+ const session = host.requireSession();
130935
+ let hasCurrentGoal;
130491
130936
  try {
130492
- await appendGoalQueueItem(host.requireSession(), { objective: parsed.objective });
130937
+ const { goal } = await session.getGoal();
130938
+ hasCurrentGoal = goal !== null;
130939
+ } catch (error) {
130940
+ host.showError(`Failed to inspect current goal: ${formatErrorMessage$2(error)}`);
130941
+ return;
130942
+ }
130943
+ if (!hasCurrentGoal && !isBusy(host)) {
130944
+ host.showStatus(START_NEXT_GOAL_NOW_MESSAGE);
130945
+ await createGoal(host, {
130946
+ kind: "create",
130947
+ objective: parsed.objective,
130948
+ replace: false
130949
+ }, `next ${parsed.objective}`);
130950
+ return;
130951
+ }
130952
+ try {
130953
+ await appendGoalQueueItem(session, { objective: parsed.objective });
130493
130954
  } catch (error) {
130494
130955
  host.showError(formatErrorMessage$2(error));
130495
130956
  return;
130496
130957
  }
130497
130958
  host.track("goal_queue_append");
130498
- host.showStatus("Upcoming goal added. It will start after the current goal is complete.");
130959
+ if (!hasCurrentGoal) host.requestQueuedGoalPromotion?.();
130960
+ host.state.transcriptContainer.addChild(new UpcomingGoalAddedMessageComponent(host.state.theme.colors));
130961
+ host.state.ui.requestRender();
130499
130962
  }
130500
130963
  async function showGoalQueueManager(host, selectedGoalId) {
130501
130964
  let snapshot;
@@ -130585,7 +131048,7 @@ async function createGoal(host, parsed, rawArgs, options = {}) {
130585
131048
  host.showError(LLM_NOT_SET_MESSAGE);
130586
131049
  return false;
130587
131050
  }
130588
- if (host.state.appState.permissionMode === "manual") {
131051
+ if (host.state.appState.permissionMode === "manual" || host.state.appState.permissionMode === "yolo") {
130589
131052
  showGoalStartPermissionPrompt(host, parsed, rawArgs ?? parsed.objective, options);
130590
131053
  return false;
130591
131054
  }
@@ -130599,6 +131062,7 @@ function showGoalStartPermissionPrompt(host, parsed, rawArgs, options) {
130599
131062
  };
130600
131063
  host.mountEditorReplacement(new GoalStartPermissionPromptComponent({
130601
131064
  colors: host.state.theme.colors,
131065
+ mode: host.state.appState.permissionMode === "yolo" ? "yolo" : "manual",
130602
131066
  onSelect: (choice) => {
130603
131067
  if (choice === "cancel") {
130604
131068
  cancelStart();
@@ -130611,7 +131075,7 @@ function showGoalStartPermissionPrompt(host, parsed, rawArgs, options) {
130611
131075
  }));
130612
131076
  }
130613
131077
  async function startGoalWithPermission(host, parsed, choice, options) {
130614
- if (choice === "auto" || choice === "yolo") {
131078
+ if (choice !== host.state.appState.permissionMode && (choice === "auto" || choice === "yolo")) {
130615
131079
  if (!await setPermissionForGoal(host, choice)) return;
130616
131080
  }
130617
131081
  await startGoal(host, parsed, options);
@@ -130644,7 +131108,8 @@ async function startGoal(host, parsed, options) {
130644
131108
  host.track("goal_create", { replace: parsed.replace });
130645
131109
  host.state.transcriptContainer.addChild(new GoalSetMessageComponent(host.state.theme.colors));
130646
131110
  host.state.ui.requestRender();
130647
- (options.sendInput ?? host.sendNormalUserInput)(parsed.objective);
131111
+ if (options.sendInput !== void 0) options.sendInput(parsed.objective);
131112
+ else host.sendNormalUserInput(parsed.objective);
130648
131113
  return true;
130649
131114
  }
130650
131115
  async function pauseGoal(host) {
@@ -130711,6 +131176,9 @@ async function showGoalStatus(host) {
130711
131176
  function isStreaming(host) {
130712
131177
  return host.state.appState.streamingPhase !== "idle";
130713
131178
  }
131179
+ function isBusy(host) {
131180
+ return isStreaming(host) || host.state.appState.isCompacting;
131181
+ }
130714
131182
  //#endregion
130715
131183
  //#region src/tui/components/dialogs/custom-registry-import.ts
130716
131184
  /**
@@ -137226,7 +137694,7 @@ var HelpPanelComponent = class extends Container {
137226
137694
  const slashColor = chalk.hex(colors.primary);
137227
137695
  const shortcuts = this.opts.shortcuts ?? DEFAULT_KEYBOARD_SHORTCUTS;
137228
137696
  const kbdWidth = Math.max(8, ...shortcuts.map((s) => s.keys.length));
137229
- const sortedCmds = [...this.opts.commands].toSorted((a, b) => a.name.localeCompare(b.name));
137697
+ const sortedCmds = [...this.opts.commands].toSorted(compareSlashCommandsForDisplay);
137230
137698
  const cmdLabels = sortedCmds.map((c) => {
137231
137699
  const aliases = c.aliases.length > 0 ? ` (${c.aliases.map((a) => "/" + a).join(", ")})` : "";
137232
137700
  return `/${c.name}${aliases}`;
@@ -137265,6 +137733,12 @@ var HelpPanelComponent = class extends Container {
137265
137733
  return lines.map((line) => truncateToWidth(line, width));
137266
137734
  }
137267
137735
  };
137736
+ function compareSlashCommandsForDisplay(a, b) {
137737
+ return getSlashCommandDisplayGroup(a.name) - getSlashCommandDisplayGroup(b.name) || a.name.localeCompare(b.name);
137738
+ }
137739
+ function getSlashCommandDisplayGroup(name) {
137740
+ return name.startsWith("skill:") ? 1 : 0;
137741
+ }
137268
137742
  //#endregion
137269
137743
  //#region src/tui/components/dialogs/question-dialog.ts
137270
137744
  /**
@@ -138039,15 +138513,23 @@ function readCustomRegistrySource(provider) {
138039
138513
  function asManaged(config) {
138040
138514
  return config;
138041
138515
  }
138042
- function collectModelIdsForProvider(config, providerId) {
138516
+ function collectModelIdsForAliases(config, aliasKeys) {
138043
138517
  const ids = /* @__PURE__ */ new Set();
138044
- for (const alias of Object.values(config.models ?? {})) if (alias.provider === providerId && alias.model.length > 0) ids.add(alias.model);
138518
+ for (const aliasKey of aliasKeys) {
138519
+ const alias = config.models?.[aliasKey];
138520
+ if (alias !== void 0 && alias.model.length > 0) ids.add(alias.model);
138521
+ }
138045
138522
  return ids;
138046
138523
  }
138047
- function setsEqual(a, b) {
138048
- if (a.size !== b.size) return false;
138049
- for (const item of a) if (!b.has(item)) return false;
138050
- return true;
138524
+ function providerAliasKeys(config, providerId) {
138525
+ const keys = /* @__PURE__ */ new Set();
138526
+ for (const [alias, model] of Object.entries(config.models ?? {})) if (model.provider === providerId) keys.add(alias);
138527
+ return keys;
138528
+ }
138529
+ function generatedProviderAliasKeys(config, providerId, aliasPrefix) {
138530
+ const keys = /* @__PURE__ */ new Set();
138531
+ for (const [alias, model] of Object.entries(config.models ?? {})) if (model.provider === providerId && alias.startsWith(aliasPrefix)) keys.add(alias);
138532
+ return keys;
138051
138533
  }
138052
138534
  function computeChanges(oldIds, newIds) {
138053
138535
  let added = 0;
@@ -138059,6 +138541,56 @@ function computeChanges(oldIds, newIds) {
138059
138541
  removed
138060
138542
  };
138061
138543
  }
138544
+ function providerModelSnapshot(config, providerId, aliasKeys) {
138545
+ const snapshots = [];
138546
+ for (const alias of aliasKeys) {
138547
+ const model = config.models?.[alias];
138548
+ if (model === void 0 || model.provider !== providerId) continue;
138549
+ snapshots.push({
138550
+ alias,
138551
+ model: {
138552
+ ...model,
138553
+ capabilities: model.capabilities === void 0 ? void 0 : model.capabilities.toSorted()
138554
+ }
138555
+ });
138556
+ }
138557
+ snapshots.sort((a, b) => a.alias.localeCompare(b.alias));
138558
+ return JSON.stringify(snapshots);
138559
+ }
138560
+ function providerModelsEqual(config, nextConfig, providerId, aliasKeys) {
138561
+ return providerModelSnapshot(config, providerId, aliasKeys) === providerModelSnapshot(nextConfig, providerId, aliasKeys);
138562
+ }
138563
+ function providerRefreshAliasKeys(config, nextConfig, providerId, aliasPrefix) {
138564
+ const keys = generatedProviderAliasKeys(config, providerId, aliasPrefix);
138565
+ for (const key of providerAliasKeys(nextConfig, providerId)) keys.add(key);
138566
+ return keys;
138567
+ }
138568
+ function preserveUserProviderAliases(config, providerId, refreshedAliasKeys) {
138569
+ const preserved = {};
138570
+ for (const [alias, model] of Object.entries(config.models ?? {})) {
138571
+ if (model.provider !== providerId || refreshedAliasKeys.has(alias)) continue;
138572
+ preserved[alias] = structuredClone(model);
138573
+ }
138574
+ return preserved;
138575
+ }
138576
+ function restoreProviderAliases(config, aliases) {
138577
+ if (Object.keys(aliases).length === 0) return;
138578
+ config.models = {
138579
+ ...config.models,
138580
+ ...aliases
138581
+ };
138582
+ }
138583
+ function restoreDefaultSelection(config, defaultModel, defaultThinking) {
138584
+ if (defaultModel === void 0 || config.models?.[defaultModel] === void 0) return;
138585
+ config.defaultModel = defaultModel;
138586
+ config.defaultThinking = defaultThinking;
138587
+ }
138588
+ function clampDanglingDefault(config) {
138589
+ if (config.defaultModel !== void 0 && config.models?.[config.defaultModel] === void 0) {
138590
+ config.defaultModel = void 0;
138591
+ config.defaultThinking = void 0;
138592
+ }
138593
+ }
138062
138594
  function pickDefaultModel(config, providerId, models) {
138063
138595
  const firstModel = models[0];
138064
138596
  if (firstModel === void 0) return "";
@@ -138088,25 +138620,27 @@ async function refreshAllProviderModels(host) {
138088
138620
  baseUrl: auth.baseUrl
138089
138621
  });
138090
138622
  if (models.length > 0) {
138091
- const beforeIds = collectModelIdsForProvider(config, KIMI_CODE_PROVIDER_NAME);
138092
- const newIds = new Set(models.map((m) => m.id));
138093
- if (setsEqual(beforeIds, newIds)) unchanged.push(KIMI_CODE_PROVIDER_NAME);
138623
+ const next = structuredClone(config);
138624
+ applyManagedKimiCodeConfig(asManaged(next), {
138625
+ models,
138626
+ baseUrl: auth.baseUrl,
138627
+ oauthKey: auth.oauthRef.key,
138628
+ oauthHost: auth.oauthRef.oauthHost,
138629
+ preserveDefaultModel: true
138630
+ });
138631
+ const refreshedAliasKeys = providerRefreshAliasKeys(config, next, KIMI_CODE_PROVIDER_NAME, `${KIMI_CODE_PLATFORM_ID}/`);
138632
+ restoreProviderAliases(next, preserveUserProviderAliases(config, KIMI_CODE_PROVIDER_NAME, refreshedAliasKeys));
138633
+ restoreDefaultSelection(next, config.defaultModel, config.defaultThinking);
138634
+ clampDanglingDefault(next);
138635
+ if (providerModelsEqual(config, next, "managed:kimi-code", refreshedAliasKeys)) unchanged.push(KIMI_CODE_PROVIDER_NAME);
138094
138636
  else {
138095
- const { added, removed } = computeChanges(beforeIds, newIds);
138096
- config = await host.removeProvider(KIMI_CODE_PROVIDER_NAME);
138097
- clearManagedKimiCodeConfig(asManaged(config));
138098
- applyManagedKimiCodeConfig(asManaged(config), {
138099
- models,
138100
- baseUrl: auth.baseUrl,
138101
- oauthKey: auth.oauthRef.key,
138102
- oauthHost: auth.oauthRef.oauthHost,
138103
- preserveDefaultModel: true
138104
- });
138105
- await host.setConfig({
138106
- providers: config.providers,
138107
- models: config.models,
138108
- defaultModel: config.defaultModel,
138109
- defaultThinking: config.defaultThinking
138637
+ const { added, removed } = computeChanges(collectModelIdsForAliases(config, refreshedAliasKeys), collectModelIdsForAliases(next, refreshedAliasKeys));
138638
+ await host.removeProvider(KIMI_CODE_PROVIDER_NAME);
138639
+ config = await host.setConfig({
138640
+ providers: next.providers,
138641
+ models: next.models,
138642
+ defaultModel: next.defaultModel,
138643
+ defaultThinking: next.defaultThinking
138110
138644
  });
138111
138645
  changed.push({
138112
138646
  providerId: KIMI_CODE_PROVIDER_NAME,
@@ -138134,28 +138668,30 @@ async function refreshAllProviderModels(host) {
138134
138668
  let models = await fetchOpenPlatformModels(platform, apiKey);
138135
138669
  models = filterModelsByPrefix(models, platform);
138136
138670
  if (models.length === 0) continue;
138137
- const beforeIds = collectModelIdsForProvider(config, providerId);
138138
- const newIds = new Set(models.map((m) => m.id));
138139
- if (setsEqual(beforeIds, newIds)) unchanged.push(providerId);
138671
+ const selectedModelId = pickDefaultModel(config, providerId, models);
138672
+ const selectedModel = models.find((m) => m.id === selectedModelId);
138673
+ if (selectedModel === void 0) continue;
138674
+ const next = structuredClone(config);
138675
+ applyOpenPlatformConfig(asManaged(next), {
138676
+ platform,
138677
+ models,
138678
+ selectedModel,
138679
+ thinking: false,
138680
+ apiKey
138681
+ });
138682
+ const refreshedAliasKeys = providerRefreshAliasKeys(config, next, providerId, `${providerId}/`);
138683
+ restoreProviderAliases(next, preserveUserProviderAliases(config, providerId, refreshedAliasKeys));
138684
+ restoreDefaultSelection(next, config.defaultModel, config.defaultThinking);
138685
+ clampDanglingDefault(next);
138686
+ if (providerModelsEqual(config, next, providerId, refreshedAliasKeys)) unchanged.push(providerId);
138140
138687
  else {
138141
- const { added, removed } = computeChanges(beforeIds, newIds);
138142
- const selectedModelId = pickDefaultModel(config, providerId, models);
138143
- const selectedModel = models.find((m) => m.id === selectedModelId);
138144
- if (selectedModel === void 0) continue;
138145
- config = await host.removeProvider(providerId);
138146
- removeOpenPlatformConfig(asManaged(config), providerId);
138147
- applyOpenPlatformConfig(asManaged(config), {
138148
- platform,
138149
- models,
138150
- selectedModel,
138151
- thinking: false,
138152
- apiKey
138153
- });
138154
- await host.setConfig({
138155
- providers: config.providers,
138156
- models: config.models,
138157
- defaultModel: config.defaultModel,
138158
- defaultThinking: config.defaultThinking
138688
+ const { added, removed } = computeChanges(collectModelIdsForAliases(config, refreshedAliasKeys), collectModelIdsForAliases(next, refreshedAliasKeys));
138689
+ await host.removeProvider(providerId);
138690
+ config = await host.setConfig({
138691
+ providers: next.providers,
138692
+ models: next.models,
138693
+ defaultModel: next.defaultModel,
138694
+ defaultThinking: next.defaultThinking
138159
138695
  });
138160
138696
  changed.push({
138161
138697
  providerId,
@@ -138187,20 +138723,18 @@ async function refreshAllProviderModels(host) {
138187
138723
  }
138188
138724
  for (const { source, providerIds } of customSources.values()) try {
138189
138725
  const entries = await fetchCustomRegistry(source);
138190
- let changedAny = false;
138726
+ const next = structuredClone(config);
138727
+ const changedProviders = [];
138191
138728
  for (const providerId of providerIds) {
138192
138729
  const entry = entries[providerId];
138193
138730
  if (entry === void 0) continue;
138194
- const beforeIds = collectModelIdsForProvider(config, providerId);
138195
- const newIds = new Set(Object.values(entry.models).map((m) => m.id));
138196
- if (setsEqual(beforeIds, newIds)) unchanged.push(providerId);
138731
+ applyCustomRegistryProvider(asManaged(next), entry, source);
138732
+ const refreshedAliasKeys = providerRefreshAliasKeys(config, next, providerId, `${providerId}/`);
138733
+ restoreProviderAliases(next, preserveUserProviderAliases(config, providerId, refreshedAliasKeys));
138734
+ if (providerModelsEqual(config, next, providerId, refreshedAliasKeys)) unchanged.push(providerId);
138197
138735
  else {
138198
- const { added, removed } = computeChanges(beforeIds, newIds);
138199
- config = await host.removeProvider(providerId);
138200
- removeCustomRegistryProvider(asManaged(config), providerId);
138201
- applyCustomRegistryProvider(asManaged(config), entry, source);
138202
- changedAny = true;
138203
- changed.push({
138736
+ const { added, removed } = computeChanges(collectModelIdsForAliases(config, refreshedAliasKeys), collectModelIdsForAliases(next, refreshedAliasKeys));
138737
+ changedProviders.push({
138204
138738
  providerId,
138205
138739
  providerName: entry.name || providerId,
138206
138740
  added,
@@ -138208,10 +138742,18 @@ async function refreshAllProviderModels(host) {
138208
138742
  });
138209
138743
  }
138210
138744
  }
138211
- if (changedAny) await host.setConfig({
138212
- providers: config.providers,
138213
- models: config.models
138214
- });
138745
+ if (changedProviders.length > 0) {
138746
+ restoreDefaultSelection(next, config.defaultModel, config.defaultThinking);
138747
+ clampDanglingDefault(next);
138748
+ for (const { providerId } of changedProviders) await host.removeProvider(providerId);
138749
+ config = await host.setConfig({
138750
+ providers: next.providers,
138751
+ models: next.models,
138752
+ defaultModel: next.defaultModel,
138753
+ defaultThinking: next.defaultThinking
138754
+ });
138755
+ for (const change of changedProviders) changed.push(change);
138756
+ }
138215
138757
  } catch (error) {
138216
138758
  for (const providerId of providerIds) failed.push({
138217
138759
  provider: providerId,
@@ -140159,6 +140701,7 @@ var SessionEventHandler = class {
140159
140701
  goalCompletionAwaitingClear = false;
140160
140702
  goalCompletionTurnEnded = false;
140161
140703
  queuedGoalPromotionPending = false;
140704
+ queuedGoalPromotionInFlight = false;
140162
140705
  queuedGoalPromotionTimer;
140163
140706
  resetRuntimeState() {
140164
140707
  this.backgroundAgentMetadata.clear();
@@ -140171,6 +140714,7 @@ var SessionEventHandler = class {
140171
140714
  this.goalCompletionAwaitingClear = false;
140172
140715
  this.goalCompletionTurnEnded = false;
140173
140716
  this.queuedGoalPromotionPending = false;
140717
+ this.queuedGoalPromotionInFlight = false;
140174
140718
  this.clearQueuedGoalPromotionTimer();
140175
140719
  this.stopAllMcpServerStatusSpinners();
140176
140720
  }
@@ -140630,14 +141174,25 @@ var SessionEventHandler = class {
140630
141174
  }
140631
141175
  scheduleQueuedGoalPromotion() {
140632
141176
  if (!this.queuedGoalPromotionPending || !this.goalCompletionTurnEnded) return;
141177
+ if (this.queuedGoalPromotionInFlight) return;
140633
141178
  if (this.queuedGoalPromotionTimer !== void 0) return;
140634
141179
  this.queuedGoalPromotionTimer = setTimeout(() => {
140635
141180
  this.queuedGoalPromotionTimer = void 0;
140636
141181
  if (!this.queuedGoalPromotionPending || !this.goalCompletionTurnEnded) return;
140637
- if (this.host.state.appState.streamingPhase !== "idle" || this.host.state.queuedMessages.length > 0) return;
140638
- this.queuedGoalPromotionPending = false;
140639
- this.goalCompletionTurnEnded = false;
140640
- this.promoteNextQueuedGoal();
141182
+ if (this.queuedGoalPromotionInFlight) return;
141183
+ if (!this.isReadyForQueuedGoalPromotion()) return;
141184
+ this.queuedGoalPromotionInFlight = true;
141185
+ this.promoteNextQueuedGoal().then((complete) => {
141186
+ if (complete) {
141187
+ this.queuedGoalPromotionPending = false;
141188
+ this.goalCompletionTurnEnded = false;
141189
+ return;
141190
+ }
141191
+ this.goalCompletionTurnEnded = false;
141192
+ }).finally(() => {
141193
+ this.queuedGoalPromotionInFlight = false;
141194
+ this.scheduleQueuedGoalPromotion();
141195
+ });
140641
141196
  }, 0);
140642
141197
  }
140643
141198
  clearQueuedGoalPromotionTimer() {
@@ -140645,50 +141200,72 @@ var SessionEventHandler = class {
140645
141200
  clearTimeout(this.queuedGoalPromotionTimer);
140646
141201
  this.queuedGoalPromotionTimer = void 0;
140647
141202
  }
141203
+ requestQueuedGoalPromotion() {
141204
+ this.queuedGoalPromotionPending = true;
141205
+ this.goalCompletionTurnEnded = true;
141206
+ this.scheduleQueuedGoalPromotion();
141207
+ }
141208
+ retryQueuedGoalPromotion() {
141209
+ this.scheduleQueuedGoalPromotion();
141210
+ }
141211
+ isReadyForQueuedGoalPromotion(session) {
141212
+ return (session === void 0 || this.host.session === session) && !this.host.aborted && this.host.state.appState.streamingPhase === "idle" && this.host.state.queuedMessages.length === 0;
141213
+ }
140648
141214
  async promoteNextQueuedGoal() {
140649
141215
  const { host } = this;
140650
141216
  const session = host.session;
140651
- if (session === void 0 || host.aborted) return;
141217
+ if (session === void 0 || host.aborted) return true;
140652
141218
  let queue;
140653
141219
  try {
140654
141220
  queue = await readGoalQueue(session);
140655
141221
  } catch (error) {
140656
141222
  host.showError(`Failed to read upcoming goals: ${formatErrorMessage$2(error)}`);
140657
- return;
141223
+ return false;
140658
141224
  }
140659
- if (host.session !== session || host.aborted) return;
141225
+ if (host.session !== session || host.aborted) return true;
140660
141226
  const next = queue.goals[0];
140661
- if (next === void 0) return;
140662
- await createGoal(host, {
141227
+ if (next === void 0) return true;
141228
+ if (!this.isReadyForQueuedGoalPromotion(session)) return false;
141229
+ return await createGoal(host, {
140663
141230
  kind: "create",
140664
141231
  objective: next.objective,
140665
141232
  replace: false
140666
141233
  }, next.objective, {
140667
141234
  beforeSend: async () => {
140668
- if (host.session !== session || host.aborted) return false;
141235
+ if (!this.isReadyForQueuedGoalPromotion(session)) {
141236
+ await this.cancelStartedQueuedGoal(session);
141237
+ return false;
141238
+ }
140669
141239
  try {
140670
141240
  await removeGoalQueueItem(session, { goalId: next.id });
140671
141241
  } catch (error) {
140672
141242
  host.showError(`Queued goal started, but could not be removed from the queue: ${formatErrorMessage$2(error)}`);
141243
+ await this.cancelStartedQueuedGoal(session);
140673
141244
  return false;
140674
141245
  }
140675
- if (host.session === session && !host.aborted) return true;
140676
- try {
140677
- await restoreGoalQueueItem(session, next);
140678
- } catch (error) {
140679
- host.showError(`Queued goal could not be restored: ${formatErrorMessage$2(error)}`);
140680
- }
140681
- try {
140682
- await session.cancelGoal();
140683
- } catch (error) {
140684
- host.showError(`Queued goal could not be cancelled: ${formatErrorMessage$2(error)}`);
140685
- }
141246
+ if (this.isReadyForQueuedGoalPromotion(session)) return true;
141247
+ await this.restoreAndCancelStartedQueuedGoal(session, next);
140686
141248
  return false;
140687
141249
  },
140688
141250
  sendInput: (objective) => {
140689
141251
  host.sendQueuedMessage(session, { text: objective });
140690
141252
  }
140691
- });
141253
+ }) || host.session !== session || host.aborted;
141254
+ }
141255
+ async restoreAndCancelStartedQueuedGoal(session, goal) {
141256
+ try {
141257
+ await restoreGoalQueueItem(session, goal);
141258
+ } catch (error) {
141259
+ this.host.showError(`Queued goal could not be restored: ${formatErrorMessage$2(error)}`);
141260
+ }
141261
+ await this.cancelStartedQueuedGoal(session);
141262
+ }
141263
+ async cancelStartedQueuedGoal(session) {
141264
+ try {
141265
+ await session.cancelGoal();
141266
+ } catch (error) {
141267
+ this.host.showError(`Queued goal could not be cancelled: ${formatErrorMessage$2(error)}`);
141268
+ }
140692
141269
  }
140693
141270
  async notifyQueuedGoalWaitingOnBlocked() {
140694
141271
  const { host } = this;
@@ -140718,7 +141295,7 @@ var SessionEventHandler = class {
140718
141295
  this.host.showError(OAUTH_LOGIN_REQUIRED_STARTUP_NOTICE);
140719
141296
  return;
140720
141297
  }
140721
- this.host.showError(`[${event.code}] ${event.message}`);
141298
+ this.host.showError(formatErrorPayload(event));
140722
141299
  if (this.host.state.appState.sessionId.length > 0) this.host.showStatus(errorReportHintLine());
140723
141300
  }
140724
141301
  handleSessionWarning(event) {
@@ -143303,8 +143880,13 @@ var FileMentionProvider = class {
143303
143880
  this.inner = new CombinedAutocompleteProvider(slashCommands, workDir, fdPath);
143304
143881
  }
143305
143882
  async getSuggestions(lines, cursorLine, cursorCol, options) {
143306
- const atPrefix = extractAtPrefix((lines[cursorLine] ?? "").slice(0, cursorCol));
143307
- if (atPrefix === null) return this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
143883
+ const currentLine = lines[cursorLine] ?? "";
143884
+ const textBeforeCursor = currentLine.slice(0, cursorCol);
143885
+ const atPrefix = extractAtPrefix(textBeforeCursor);
143886
+ if (atPrefix === null) {
143887
+ if (shouldSuppressSlashArgumentCompletion(textBeforeCursor, currentLine.slice(cursorCol), options.force)) return null;
143888
+ return this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
143889
+ }
143308
143890
  if (this.fdPath !== null) return this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
143309
143891
  const snapshot = this.gitCache.getSnapshot();
143310
143892
  if (snapshot === null || snapshot.files.length === 0) return this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
@@ -143336,6 +143918,12 @@ function extractAtPrefix(text) {
143336
143918
  if (text[tokenStart] !== "@") return null;
143337
143919
  return text.slice(tokenStart);
143338
143920
  }
143921
+ function shouldSuppressSlashArgumentCompletion(textBeforeCursor, textAfterCursor, force) {
143922
+ if (force === true) return false;
143923
+ if (!textBeforeCursor.startsWith("/")) return false;
143924
+ if (!textBeforeCursor.includes(" ")) return false;
143925
+ return textAfterCursor.trimStart().length > 0;
143926
+ }
143339
143927
  /** True when any path segment starts with a dot (e.g. `.github/x.yml`). */
143340
143928
  function containsDotSegment(path) {
143341
143929
  for (const segment of path.split("/")) if (segment.startsWith(".")) return true;
@@ -145027,6 +145615,7 @@ var CustomEditor = class extends Editor {
145027
145615
  };
145028
145616
  /**
145029
145617
  * Return a copy of `line` with the first `/token` coloured using `hex`.
145618
+ * For `/goal next manage`, also colour the command-path tokens.
145030
145619
  * `line` may already contain SGR escapes (cursor inverse, etc.); we
145031
145620
  * locate `/` via visible-index math so ANSI pass-through survives.
145032
145621
  * Returns `undefined` if no token is found.
@@ -145042,13 +145631,48 @@ function highlightFirstSlashToken(line, hex) {
145042
145631
  if (ch === " " || ch === " ") break;
145043
145632
  endVisible++;
145044
145633
  }
145045
- if (visible.slice(slashIdx, endVisible).slice(1).includes("/")) return void 0;
145046
- const rawStart = mapVisibleIdxToRaw(line, slashIdx);
145047
- const rawEnd = mapVisibleIdxToRaw(line, endVisible);
145048
- const before = line.slice(0, rawStart);
145049
- const token = line.slice(rawStart, rawEnd);
145050
- const after = line.slice(rawEnd);
145051
- return before + chalk.hex(hex).bold(token) + after;
145634
+ const visibleToken = visible.slice(slashIdx, endVisible);
145635
+ if (visibleToken.slice(1).includes("/")) return void 0;
145636
+ const ranges = [{
145637
+ start: slashIdx,
145638
+ end: endVisible
145639
+ }];
145640
+ if (visibleToken === "/goal") ranges.push(...goalCommandPathRanges(visible, endVisible));
145641
+ return highlightVisibleRanges(line, ranges, hex);
145642
+ }
145643
+ function goalCommandPathRanges(visible, commandEnd) {
145644
+ const nextRange = readTokenRange(visible, commandEnd);
145645
+ if (nextRange === null || visible.slice(nextRange.start, nextRange.end) !== "next") return [];
145646
+ const ranges = [nextRange];
145647
+ const manageRange = readTokenRange(visible, nextRange.end);
145648
+ if (manageRange !== null && visible.slice(manageRange.start, manageRange.end) === "manage") ranges.push(manageRange);
145649
+ return ranges;
145650
+ }
145651
+ function readTokenRange(visible, start) {
145652
+ let tokenStart = start;
145653
+ while (tokenStart < visible.length && isTokenSpace(visible[tokenStart])) tokenStart++;
145654
+ if (tokenStart >= visible.length) return null;
145655
+ let tokenEnd = tokenStart;
145656
+ while (tokenEnd < visible.length && !isTokenSpace(visible[tokenEnd])) tokenEnd++;
145657
+ return {
145658
+ start: tokenStart,
145659
+ end: tokenEnd
145660
+ };
145661
+ }
145662
+ function isTokenSpace(ch) {
145663
+ return ch === " " || ch === " ";
145664
+ }
145665
+ function highlightVisibleRanges(line, ranges, hex) {
145666
+ let out = "";
145667
+ let rawCursor = 0;
145668
+ for (const range of ranges) {
145669
+ const rawStart = mapVisibleIdxToRaw(line, range.start);
145670
+ const rawEnd = mapVisibleIdxToRaw(line, range.end);
145671
+ out += line.slice(rawCursor, rawStart);
145672
+ out += chalk.hex(hex).bold(line.slice(rawStart, rawEnd));
145673
+ rawCursor = rawEnd;
145674
+ }
145675
+ return out + line.slice(rawCursor);
145052
145676
  }
145053
145677
  /**
145054
145678
  * Overlay a terminal-style `> ` prompt symbol on the first content line.
@@ -145964,6 +146588,9 @@ var KimiTUI = class {
145964
146588
  imageAttachmentIds: item.imageAttachmentIds
145965
146589
  });
145966
146590
  }
146591
+ requestQueuedGoalPromotion() {
146592
+ this.sessionEventHandler.requestQueuedGoalPromotion();
146593
+ }
145967
146594
  sendMessageInternal(session, input, options) {
145968
146595
  const imageAttachmentIds = options?.imageAttachmentIds !== void 0 && options.imageAttachmentIds.length > 0 ? options.imageAttachmentIds : void 0;
145969
146596
  this.appendTranscriptEntry({
@@ -146065,7 +146692,10 @@ var KimiTUI = class {
146065
146692
  if ("planMode" in patch) this.updateEditorBorderHighlight();
146066
146693
  this.state.footer.setState(this.state.appState);
146067
146694
  this.updateActivityPane();
146068
- if (busyChanged) this.updateQueueDisplay();
146695
+ if (busyChanged) {
146696
+ this.updateQueueDisplay();
146697
+ this.sessionEventHandler.retryQueuedGoalPromotion();
146698
+ }
146069
146699
  this.state.ui.requestRender();
146070
146700
  }
146071
146701
  patchLivePane(patch) {
@@ -147355,6 +147985,21 @@ async function showPendingBackgroundInstallNotice(state, currentVersion, stdout,
147355
147985
  await writeUpdateInstallState(nextState).catch(() => {});
147356
147986
  return nextState;
147357
147987
  }
147988
+ /**
147989
+ * `KIMI_CODE_NO_AUTO_UPDATE` (or the legacy `KIMI_CLI_NO_AUTO_UPDATE` alias)
147990
+ * fully disables the update preflight — no check, no background install, no
147991
+ * prompt. Migrated from kimi-cli, where the variable gated all auto-update
147992
+ * behavior. Accepts the usual truthy values (`1`/`true`/`yes`/`on`).
147993
+ */
147994
+ function isAutoUpdateDisabledByEnv(env = process.env) {
147995
+ const truthy = (value) => [
147996
+ "1",
147997
+ "true",
147998
+ "yes",
147999
+ "on"
148000
+ ].includes((value ?? "").trim().toLowerCase());
148001
+ return truthy(env["KIMI_CODE_NO_AUTO_UPDATE"]) || truthy(env["KIMI_CLI_NO_AUTO_UPDATE"]);
148002
+ }
147358
148003
  async function shouldAutoInstallUpdates() {
147359
148004
  try {
147360
148005
  return (await loadTuiConfig()).upgrade.autoInstall;
@@ -147512,6 +148157,7 @@ async function runUpdatePreflight(currentVersion, options = {}) {
147512
148157
  const stderr = options.stderr ?? process.stderr;
147513
148158
  const logger = options.logger ?? log;
147514
148159
  const platform = process.platform;
148160
+ if (isAutoUpdateDisabledByEnv()) return "continue";
147515
148161
  try {
147516
148162
  const isInteractive = options.isTTY ?? (process.stdin.isTTY && process.stdout.isTTY);
147517
148163
  let installState = await readUpdateInstallState().catch(() => emptyUpdateInstallState());