@moonshot-ai/kimi-code 0.14.1 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.mjs +1164 -750
  2. package/package.json +3 -3
package/dist/main.mjs CHANGED
@@ -37,7 +37,7 @@ import { EventEmitter as EventEmitter$1 } from "node:events";
37
37
  import { StringDecoder } from "node:string_decoder";
38
38
  import Mi from "assert";
39
39
  import so from "node:assert";
40
- import { parse as parse$2, stringify } from "smol-toml";
40
+ import { TomlError, parse as parse$2, stringify } from "smol-toml";
41
41
  import "zod/v3";
42
42
  import * as z4mini from "zod/v4-mini";
43
43
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -54366,7 +54366,7 @@ function applyCompletionBudget(args) {
54366
54366
  return args.provider.withMaxCompletionTokens(cap);
54367
54367
  }
54368
54368
  //#endregion
54369
- //#region ../../packages/agent-core/src/agent/compaction/compaction-instruction.md
54369
+ //#region ../../packages/agent-core/src/agent/compaction/compaction-instruction.md?raw
54370
54370
  var compaction_instruction_default = "\n--- This message is a direct task, not part of the above conversation ---\n\nYou are now given a task to compact this conversation context according to specific priorities and output requirements.\n\nOutput text only. DO NOT CALL ANY TOOLS. Calling tools will be rejected and fails the task. You already have all the information you need in the conversation history. You have only one chance.\n\nThe goal of compaction is to keep essential code patterns, technical details, and architectural decisions for continuing development without losing context after the above messages are cleared work.\n\n{{ customInstruction }}\n\n<!-- Compression Priorities (in order) -->\n\n1. **Current Task State**: What is being worked on RIGHT NOW\n2. **Errors & Solutions**: All encountered errors and their resolutions\n3. **Code Evolution**: Final working versions only (remove intermediate attempts)\n4. **System Context**: Project structure, dependencies, environment setup\n5. **Design Decisions**: Architectural choices and their rationale\n6. **TODO Items**: Unfinished tasks and known issues\n\n<!-- Required Output Structure -->\n\n## Current Focus\n\n[What we're working on now]\n\n## Environment\n\n- [Key setup/config points]\n- ...\n\n## Completed Tasks\n\n- [Task]: [Brief outcome]\n- ...\n\n## Active Issues\n\n- [Issue]: [Status/Next steps]\n- ...\n\n## Code State\n\n### [Critical file name]\n\n[Brief description of the file's purpose and current state]\n\n```\n[The latest version of critical code snippets in this file, <20 lines]\n```\n\n### [Critical file name]\n\n- [Useful classes/methods/functions]: [Brief description/usage]\n- ...\n\n<!-- Omit non-critical code, intermediate attempts, and resolved errors -->\n\n## Important Context\n\n- [Any crucial information not covered above]\n- ...\n\n## All User Messages\n\n- [Detailed non tool use user message]\n- ...\n";
54371
54371
  //#endregion
54372
54372
  //#region ../../packages/agent-core/src/agent/compaction/render-messages.ts
@@ -54496,7 +54496,7 @@ function closeObjectNodes(value) {
54496
54496
  for (const child of Object.values(node)) closeObjectNodes(child);
54497
54497
  }
54498
54498
  //#endregion
54499
- //#region ../../packages/agent-core/src/tools/builtin/state/todo-list.md
54499
+ //#region ../../packages/agent-core/src/tools/builtin/state/todo-list.md?raw
54500
54500
  var todo_list_default = "Use this tool to maintain a structured TODO list as you work through a multi-step task. Use it proactively and often when progress tracking helps the current work. This is especially useful in Plan mode, long-running investigations, and implementation tasks with several tool calls.\n\n**When to use:**\n- Multi-step tasks that span several tool calls\n- Tracking investigation progress across a large codebase search\n- Planning a sequence of edits before making them\n- After receiving new multi-step instructions, capture the requirements as todos\n- Before starting a tracked task, mark exactly one item as `in_progress`\n- Immediately after finishing a tracked task, mark it `done`; do not batch completions at the end\n\n**When NOT to use:**\n- Single-shot answers that complete in one or two tool calls\n- Trivial requests where tracking adds no clarity\n- Purely conversational or informational replies\n\n**Avoid churn:**\n- Do not re-call this tool when nothing meaningful has changed since the last call — update the list only after real progress.\n- When unsure of the current state, call query mode first (omit `todos`) to check the list before deciding what to update.\n- If no available tool can move any task forward, tell the user where you are stuck instead of repeatedly re-ordering the same todos.\n\n**How to use:**\n- Call with `todos: [...]` to replace the full list. Statuses: pending / in_progress / done.\n- Call with no arguments to retrieve the current list without changing it.\n- Call with `todos: []` to clear the list.\n- Keep titles short and actionable (e.g. \"Read session-control.ts\", \"Add planMode flag to TurnManager\").\n- Update statuses as you make progress.\n- When work is underway, keep exactly one task `in_progress`.\n- Only mark a task `done` when it is fully accomplished.\n- Never mark a task `done` if tests are failing, implementation is partial, unresolved errors remain, or required files/dependencies could not be found.\n- If you encounter a blocker, keep the blocked task `in_progress` or add a new pending task describing what must be resolved.\n";
54501
54501
  //#endregion
54502
54502
  //#region ../../packages/agent-core/src/tools/builtin/state/todo-list.ts
@@ -54700,7 +54700,13 @@ var FullCompaction = class {
54700
54700
  if (data.source === "manual") this.compactionCountInTurn = 0;
54701
54701
  else this.compactionCountInTurn += 1;
54702
54702
  if (this.compactionCountInTurn > this.strategy.maxCompactionPerTurn) return;
54703
- if (this.agent.records.restoring) return;
54703
+ if (this.agent.records.restoring) {
54704
+ this.agent.replayBuilder.push({
54705
+ type: "compaction",
54706
+ instruction: data.instruction
54707
+ });
54708
+ return;
54709
+ }
54704
54710
  const compactedCount = this.strategy.computeCompactCount(this.agent.context.history, data.source);
54705
54711
  if (compactedCount === 0) throw new KimiError(ErrorCodes.COMPACTION_UNABLE, "No prefix that can be compacted in current history.");
54706
54712
  this.agent.records.logRecord({
@@ -54730,6 +54736,7 @@ var FullCompaction = class {
54730
54736
  this.markCanceled();
54731
54737
  }
54732
54738
  markCanceled() {
54739
+ this.agent.replayBuilder.patchLast("compaction", { result: "cancelled" });
54733
54740
  if (!this.compacting) return;
54734
54741
  this.agent.records.logRecord({ type: "full_compaction.cancel" });
54735
54742
  this.compacting.abortController.abort();
@@ -56644,26 +56651,32 @@ var ContextMemory = class {
56644
56651
  stoppedAtCompaction: stoppedAtBoundary
56645
56652
  } });
56646
56653
  }
56647
- applyCompaction(summary) {
56654
+ applyCompaction(result) {
56648
56655
  this.agent.records.logRecord({
56649
56656
  type: "context.apply_compaction",
56650
- ...summary
56657
+ ...result
56651
56658
  });
56659
+ this.agent.replayBuilder.patchLast("compaction", { result: {
56660
+ summary: result.summary,
56661
+ compactedCount: result.compactedCount,
56662
+ tokensBefore: result.tokensBefore,
56663
+ tokensAfter: result.tokensAfter
56664
+ } });
56652
56665
  this._history = [{
56653
56666
  role: "assistant",
56654
56667
  content: [{
56655
56668
  type: "text",
56656
- text: summary.summary
56669
+ text: result.summary
56657
56670
  }],
56658
56671
  toolCalls: [],
56659
56672
  origin: { kind: "compaction_summary" }
56660
- }, ...this._history.slice(summary.compactedCount)];
56673
+ }, ...this._history.slice(result.compactedCount)];
56661
56674
  this.openSteps.clear();
56662
56675
  this.flushDeferredMessagesIfToolExchangeClosed();
56663
- this._tokenCount = summary.tokensAfter;
56676
+ this._tokenCount = result.tokensAfter;
56664
56677
  this.tokenCountCoveredMessageCount = this._history.length;
56665
56678
  this.agent.microCompaction.reset();
56666
- this.agent.injection.onContextCompacted(summary.compactedCount);
56679
+ this.agent.injection.onContextCompacted(result.compactedCount);
56667
56680
  this.agent.emitStatusUpdated();
56668
56681
  }
56669
56682
  data() {
@@ -62113,6 +62126,12 @@ var ReplayBuilder = class {
62113
62126
  time: this.agent.records.restoring?.time ?? Date.now()
62114
62127
  });
62115
62128
  }
62129
+ patchLast(type, patch) {
62130
+ if (this.agent.records.restoring) {
62131
+ const last = this.records.at(-1);
62132
+ if (last && last.type === type) Object.assign(last, patch);
62133
+ }
62134
+ }
62116
62135
  removeLastMessages(removedMessages) {
62117
62136
  if (removedMessages.size === 0) return;
62118
62137
  for (let i = this.records.length - 1; i >= 0; i--) {
@@ -65596,7 +65615,8 @@ function summarizeSkill(skill) {
65596
65615
  path: skill.path,
65597
65616
  source: skill.source,
65598
65617
  type: skill.metadata.type,
65599
- disableModelInvocation: skill.metadata.disableModelInvocation
65618
+ disableModelInvocation: skill.metadata.disableModelInvocation,
65619
+ isSubSkill: skill.metadata.isSubSkill
65600
65620
  };
65601
65621
  }
65602
65622
  //#endregion
@@ -65797,7 +65817,7 @@ function isRecord$11(value) {
65797
65817
  return typeof value === "object" && value !== null && !Array.isArray(value);
65798
65818
  }
65799
65819
  //#endregion
65800
- //#region ../../packages/agent-core/src/skill/builtin/custom-theme.md
65820
+ //#region ../../packages/agent-core/src/skill/builtin/custom-theme.md?raw
65801
65821
  var custom_theme_default = "---\nname: custom-theme\ndescription: Create or edit a kimi-code custom color theme — a JSON file under the resolved KIMI_CODE_HOME data directory that recolors the TUI. Use when the user wants their own theme, asks for a specific palette or mood, or wants to tweak an existing custom theme's colors.\n---\n\n# Create a kimi-code custom theme (custom-theme)\n\nHelp the user design, write, and apply a custom color theme for the kimi-code TUI. A theme is a single JSON file; the TUI ships with `dark`, `light`, and `auto`, and any file the user adds becomes selectable alongside them.\n\n## Rules of engagement\n\n- **Never write a theme until the user has explicitly clarified what they want.** This skill may only run after the user has confirmed light vs dark, the style or mood, any specific colors they care about, and the intended filename. If any of these are missing, ask before creating files.\n- **Never assume the data directory is `~/.kimi-code`.** Always resolve `$KIMI_CODE_HOME` first with the Bash command below.\n- **Never edit a live theme file in place.** Always create a `.json.new` candidate, validate it, back up the old file, and then `mv` it into place.\n- **Never overwrite an existing theme without reading it first.** Read, back up, then overwrite only after the user confirms.\n\n## Where a theme lives\n\nThe kimi-code runtime resolves the data directory as `KIMI_CODE_HOME` first, falling back to `~/.kimi-code`. Theme files live inside the `themes/` subdirectory of that data directory.\n\nBefore doing anything, resolve the actual data root with Bash so you don't write to the wrong place. Check whether `KIMI_CODE_HOME` is set and fall back to `~/.kimi-code` when it is empty:\n\n```bash\necho \"$KIMI_CODE_HOME\"\necho \"$HOME/.kimi-code\"\n```\n\nUse the first line when it is non-empty; otherwise use the second line. In the rest of this skill, `<KIMI_CODE_HOME>` means that resolved data root — **never assume `~/.kimi-code`**. Theme files live at `<KIMI_CODE_HOME>/themes/<name>.json`. Create the `themes/` directory if it doesn't exist.\n\n## What a theme is\n\n- A theme lives at `<KIMI_CODE_HOME>/themes/<name>.json`.\n- **The filename is the theme name**: `ember.json` shows up in the `/theme` picker as `Custom: ember`.\n- Shape:\n\n ```json\n {\n \"name\": \"ember\",\n \"displayName\": \"Ember\",\n \"colors\": {\n \"primary\": \"#83A598\",\n \"accent\": \"#FE8019\"\n }\n }\n ```\n\n - `name` (required), `displayName` (optional), `base` (optional: `\"dark\"` default, or `\"light\"`), `colors` (each value a 6-digit hex `#RRGGBB`).\n- **Partial themes are fine**: any token you leave out falls back to the **base** palette (`dark` by default; set `\"base\": \"light\"` for a light theme), so you can recolor just a few tokens or all of them.\n\n## Source of truth: the docs token reference\n\nBefore choosing colors, use **FetchURL** to fetch the official custom-theme docs as the authoritative list of tokens and what each controls:\n\n```\nhttps://moonshotai.github.io/kimi-code/en/customization/themes.html\n```\n\nOnly set tokens from this set — unknown keys are silently ignored at load. If FetchURL is unavailable or the fetch fails, fall back to the embedded reference below (it mirrors the same tokens) and tell the user you're working from the built-in list rather than the live docs.\n\n## Color tokens (what each controls)\n\n| Token | Controls |\n| --- | --- |\n| `primary` | The most-used color: links, inline code, the selected item in nearly every dialog, the focused editor border, plan/\"running\" badges, spinners |\n| `accent` | Secondary highlight: approval `▶` prefix, device-code box, image placeholder, BTW / queue panes, registry import |\n| `text` | Body text: dialog bodies, todo titles, footer model label, Markdown headings, assistant/tool message bullets, list bullets |\n| `textStrong` | Emphasized / bold text: input dialogs, status messages |\n| `textDim` | Secondary, dimmed text (the most widely used dim shade): thinking, hints, descriptions, completed todos, Markdown quotes, footer status bar |\n| `textMuted` | Faintest text: counters, scroll info, descriptions, Markdown link URLs, code-block borders |\n| `border` | Pane and editor borders, Markdown horizontal rule |\n| `borderFocus` | Focus / attention border (currently only the approval panel) |\n| `success` | Success state: `✓`, \"enabled\", completed |\n| `warning` | Warning state: auto/yolo badges, stale markers, plan-mode hint |\n| `error` | Error state: error messages, failed tool output |\n| `diffAdded` | Diff added lines |\n| `diffRemoved` | Diff removed lines |\n| `diffAddedStrong` | Diff intra-line changed words, added (bold) |\n| `diffRemovedStrong` | Diff intra-line changed words, removed (bold) |\n| `diffGutter` | Diff line-number gutter |\n| `diffMeta` | Diff meta / hunk headers |\n| `roleUser` | User message bullet and text, skill-activation name (the one role color with its own hue) |\n\n## Workflow\n\n1. **Ask the user what they want first — before choosing any colors.** Clarify, in one short exchange:\n - **Light or dark?** A light theme (dark text on a light background) or a dark theme (light text on a dark background). This sets the whole direction, so settle it first. For a light theme, set `\"base\": \"light\"` so the tokens you leave out inherit the light palette instead of dark.\n - **What style / mood?** e.g. warm vs cool, vivid vs muted, high vs low contrast, a named vibe (\"nord\", \"solarized\", \"sunset\"), or a base to start from (an existing theme, or `dark` / `light`).\n - **Any specific colors?** Whether they have exact hex values to anchor on (a brand color, a preferred `primary`, etc.).\n\n For the discrete choices (light vs dark, a few style options), prefer **AskUserQuestion** if it is available. If you are running in **auto mode** and `AskUserQuestion` is unavailable, ask the same question as a plain-text message with clear numbered or bulleted options, and wait for the user's reply. Don't start picking colors until you at least know light-vs-dark and the rough style.\n\n2. **Resolve the actual theme directory and current theme(s).**\n - Resolve the data root by checking `echo \"$KIMI_CODE_HOME\"`; if empty, use `echo \"$HOME/.kimi-code\"`. Use `<root>/themes` for every subsequent step.\n - If tweaking an existing custom theme, **Read** `<KIMI_CODE_HOME>/themes/<name>.json` first — never overwrite a theme you haven't read.\n - Starting fresh: build a `colors` object from the token table. You can `ls <KIMI_CODE_HOME>/themes/` and Read one of the user's existing themes as a reference for the format.\n\n3. **Pick a starting point and choose colors deliberately.**\n - Every value is a 6-digit hex `#RRGGBB` (not 3-digit, not a named color).\n - Keep contrast usable against the user's terminal background: don't let `text` / `textDim` sit too close to the background, and keep `success` / `warning` / `error` clearly distinguishable from each other.\n - `primary` is the most-seen color (links, selection, focus) — make it readable and distinct from `text`.\n - `roleUser` is the one role color meant to stand on its own — give it a distinct hue.\n\n4. **Create a candidate file; never edit the live theme in place.**\n - Use Bash to create a candidate. If the target theme already exists, copy it verbatim: `cp <name>.json <name>.json.new` (inside `<KIMI_CODE_HOME>/themes/`). If it doesn't exist, use **Write** to create a minimal skeleton named `<name>.json.new`.\n - Use **Edit** on the candidate to change only the intended keys. Keep every existing entry, comment, and formatting intact.\n\n5. **Validate the candidate before overwriting.**\n - Read the candidate with **Read** to visually confirm it is well-formed JSON and that every `colors` value is a full 6-digit hex `#RRGGBB` (not 3-digit, not a named color).\n - Invalid hex values are silently skipped at load (they fall back to the base palette), but fix them so the theme renders as intended.\n\n6. **Back up and overwrite.**\n - Back up the old file first — **always** create a new timestamped backup and never overwrite an existing backup: `cp <name>.json \"<name>.json.$(date +%Y%m%d-%H%M%S).bak\"`.\n - If the target didn't exist, skip the backup.\n - Overwrite with the candidate: `mv <name>.json.new <name>.json`.\n\n7. **Tell the user how to apply it** (next section).\n\n## Applying the theme\n\n- The `/theme` picker re-scans the themes directory every time it opens, so a newly added file shows up **without restarting** — tell the user to run `/theme` and choose `Custom: <name>`.\n- Or set it in `tui.toml`: `theme = \"<name>\"`.\n- **Editing the active theme**: changes to the theme that's *currently in use* are not auto-reloaded. Tell the user to run **`/reload-tui`** (or switch to another theme and back). Re-selecting the **same** theme in `/theme` is a no-op (\"Theme unchanged\").\n\n## Don'ts\n\n- **Don't start creating or editing a theme until the user has clarified light/dark, style/mood, any specific colors, and the filename.** If anything is unclear, ask — don't guess.\n- Don't invent token names — only use the documented set; unknown keys are silently ignored.\n- Don't write 3-digit hex or named colors — use full `#RRGGBB`.\n- Never edit the live theme file in place; work through a candidate and validate before `mv`.\n- Before overwriting an existing theme file, **read it and back it up** so the user can recover.\n- Don't tell the user to restart the app to apply a theme — `/theme` or `/reload-tui` is enough.\n";
65802
65822
  //#endregion
65803
65823
  //#region ../../packages/agent-core/src/skill/builtin/custom-theme.ts
@@ -65819,7 +65839,7 @@ const CUSTOM_THEME_SKILL = {
65819
65839
  }
65820
65840
  };
65821
65841
  //#endregion
65822
- //#region ../../packages/agent-core/src/skill/builtin/import-from-cc-codex.md
65842
+ //#region ../../packages/agent-core/src/skill/builtin/import-from-cc-codex.md?raw
65823
65843
  var import_from_cc_codex_default = "---\nname: import-from-cc-codex\ndescription: Import Claude Code and Codex instructions, skills, and MCP settings into Kimi Code.\ndisable-model-invocation: true\n---\n\n# Import from Claude Code and Codex\n\nThe user invoked `/import-from-cc-codex` (or `/skill:import-from-cc-codex`).\nHelp them migrate selected local Claude Code and Codex assets into Kimi Code.\nThis skill is intentionally conservative: it imports only instructions, skills,\nand MCP server declarations from `.claude` / `.codex` surfaces, with a user\npreview before any write.\n\n## Non-negotiable rules\n\n- Do **not** migrate `.agents` content. Kimi Code already supports `.agents`\n skills and AGENTS files by default.\n- Do **not** migrate Claude custom commands (`.claude/commands/**`). They are\n out of scope for this importer.\n- Do **not** migrate credentials, OAuth tokens, sessions, history, logs, hooks,\n plugins, plugin caches, output styles, or custom agents/subagents.\n- Do **not** run or install anything from the source directories.\n- Do **not** write anything until the user has chosen what to migrate, reviewed\n the final preview, and explicitly confirmed applying it.\n- Only write under Kimi Code targets:\n - User-global: `$KIMI_CODE_HOME` if set, otherwise `~/.kimi-code`.\n - Project instructions/skills: `<project root>/.kimi-code`, where the project\n root is the nearest parent directory containing `.git`; if no `.git` exists,\n use the current working directory.\n - Project-local MCP: `<cwd>/.kimi-code/mcp.json`, because Kimi reads the\n current working directory's Kimi-specific MCP file, not every project-root\n `.kimi-code/mcp.json` from subdirectories.\n- Preserve existing Kimi files. Never overwrite existing skills or replace an\n existing AGENTS.md / mcp.json wholesale.\n\n## Conversation flow\n\n### 1. Ask what to migrate first\n\nBefore reading source files, ask the user which categories to migrate. Use\n`AskUserQuestion` when available; otherwise ask in plain text and stop. Offer a\nmulti-select choice:\n\n- Instructions (`AGENTS.md` / `CLAUDE.md`)\n- Skills\n- MCP settings\n- All of the above\n\nIf the user already gave a preference in the invocation arguments, present it as\nthe default/recommended choice, but still ask for confirmation of the categories.\nIf the user dismisses or refuses the question, stop.\n\n### 2. Scan only the chosen categories\n\nResolve paths explicitly; `~` is the real OS home, and Kimi home follows\n`$KIMI_CODE_HOME` before `~/.kimi-code`.\n\nUser-level sources:\n\n- Claude instructions:\n - `~/.claude/AGENTS.md`\n - `~/.claude/CLAUDE.md`\n- Claude skills:\n - `~/.claude/skills/`\n- Claude MCP candidates:\n - `~/.claude.json` only when MCP is selected. Claude Code stores user-level\n MCP declarations there; do not read it for instruction/skill-only imports.\n- Codex instructions:\n - `~/.codex/AGENTS.md`\n - `~/.codex/CLAUDE.md` if present\n- Codex skills:\n - `~/.codex/skills/`\n- Codex MCP candidates:\n - `~/.codex/config.toml`\n\nProject-level sources, rooted at the project root:\n\n- Claude instructions:\n - `<project root>/.claude/AGENTS.md`\n - `<project root>/.claude/CLAUDE.md`\n- Claude skills:\n - `<project root>/.claude/skills/`\n- Codex instructions:\n - `<project root>/.codex/AGENTS.md`\n - `<project root>/.codex/CLAUDE.md` if present\n- Codex skills:\n - `<project root>/.codex/skills/`\n- Codex MCP candidates:\n - `<project root>/.codex/config.toml`\n\nDo not scan project-root `AGENTS.md`, project-root `CLAUDE.md`, `.agents/**`, or\nproject-root `.mcp.json` in this skill. `AGENTS.md` and `.agents/**` are already\nKimi-readable, and project-root `.mcp.json` is already read by Kimi as a\nClaude-compatible MCP file.\n\n### 3. Build an import plan\n\nCreate a plan with three sections: instructions, skills, and MCP. Include exact\nsource and target paths.\n\n#### Instructions plan\n\nMap user-level instruction sources to:\n\n- `$KIMI_CODE_HOME/AGENTS.md`, or `~/.kimi-code/AGENTS.md` if the env var is not\n set.\n\nMap project-level instruction sources to:\n\n- `<project root>/.kimi-code/AGENTS.md`\n\nAppend imported instruction content as marked blocks. Do not duplicate a block\nthat already exists in the target file.\n\nUse this marker shape:\n\n```md\n<!-- Imported from Claude Code: /absolute/source/path -->\n\n<source content>\n\n<!-- End imported from Claude Code: /absolute/source/path -->\n```\n\nFor Codex, use `Imported from Codex` / `End imported from Codex`.\n\nIf a source file is empty, skip it and report it as skipped. If the target exists\nand cannot be read as UTF-8 text, stop before writing and report the blocker.\n\n#### Skills plan\n\nMap user-level skill sources to:\n\n- `$KIMI_CODE_HOME/skills/`, or `~/.kimi-code/skills/` if the env var is not set.\n\nMap project-level skill sources to:\n\n- `<project root>/.kimi-code/skills/`\n\nRecognize these skill shapes under `.claude/skills/` or `.codex/skills/`:\n\n- Directory bundle: `<skill-name>/SKILL.md`\n- Flat markdown skill: `<skill-name>.md`\n\nCopy the entire directory bundle or flat markdown file. Preserve supporting\nfiles inside bundles. Do not copy hidden directories, `node_modules`, caches, or\nplugin-managed folders.\n\nBefore planning a copy:\n\n- Read a bundle's `SKILL.md` enough to verify that directory skills have\n frontmatter with non-empty `name` and `description`, because Kimi requires\n those fields for directory skills.\n- If the target top-level entry already exists, skip it; do not overwrite.\n- If two source entries would write the same target path, keep the first one in\n this order and report the later one as skipped:\n 1. project Claude\n 2. project Codex\n 3. user Claude\n 4. user Codex\n- Warn when a source skill uses Claude/Codex-specific fields or syntax that Kimi\n may not interpret the same way, such as `allowed-tools`, `disallowed-tools`,\n `context: fork`, `agent`, `hooks`, `paths`, dynamic shell injection with\n ``!`command` ``, or `agents/openai.yaml`. Preserve the file; do not rewrite it\n unless the user explicitly asks.\n\nDo not convert `.claude/commands/*.md`. Commands are out of scope.\n\n#### MCP plan\n\nDo not edit `mcp.json` directly in this import skill. Prepare MCP entries for\nmanual follow-up with `/mcp-config`; that built-in skill is user-invocable only,\nso you must not try to call it through the `Skill` tool.\n\nFor the preview, collect MCP candidates and normalize them into Kimi's MCP shape\nwhen possible:\n\n```json\n{\n \"mcpServers\": {\n \"name\": {\n \"command\": \"...\",\n \"args\": [\"...\"],\n \"env\": { \"KEY\": \"VALUE\" }\n }\n }\n}\n```\n\nClaude user MCP:\n\n- Read `~/.claude.json` only if MCP was selected.\n- Look for a top-level `mcpServers` object.\n- Keep stdio entries with `command`; keep HTTP entries with `url`.\n- Preserve `args`, `env`, `cwd`, `enabled`, `enabledTools`, `disabledTools`,\n `startupTimeoutMs`, `toolTimeoutMs`, `headers`, and `bearerTokenEnvVar` when\n present and valid.\n- Drop unsupported or malformed entries and report why.\n\nCodex MCP:\n\n- Read selected `config.toml` files only if MCP was selected.\n- Look for `[mcp_servers.<name>]` tables.\n- Map Codex fields to Kimi fields:\n - `command` -> `command`\n - `args` -> `args`\n - `env` -> `env`\n - `cwd` -> `cwd`\n - `url` -> `url`\n - `bearer_token_env_var` -> `bearerTokenEnvVar`\n - `enabled` -> `enabled`\n - `enabled_tools` -> `enabledTools`\n - `disabled_tools` -> `disabledTools`\n - `startup_timeout_sec` -> `startupTimeoutMs` in milliseconds\n - `tool_timeout_sec` -> `toolTimeoutMs` in milliseconds\n - `http_headers` -> `headers`\n- Drop unsupported Codex-only fields and report them, especially `required`,\n `default_tools_approval_mode`, `tools.<tool>.approval_mode`,\n `env_vars`, `env_http_headers`, and `experimental_environment`.\n- Do not import project-root `.mcp.json`; Kimi already reads it.\n\nFor each MCP candidate, choose the target scope in the preview:\n\n- User-level source -> user-global MCP target (`$KIMI_CODE_HOME/mcp.json` or\n `~/.kimi-code/mcp.json`).\n- Project-level source -> project-local Kimi MCP target (`<cwd>/.kimi-code/mcp.json`). If `<cwd>` is not the project root, call this out in the preview so the user understands when Kimi will load it.\n\nWarn that stdio MCP entries spawn commands at session start, and the user should\nonly import MCP servers they trust. Warn if an MCP entry contains apparent\nliteral secrets in `env`, `headers`, or token-like fields; prefer env-var\nreferences.\n\nAfter the user confirms applying the final preview, do not write MCP config and\ndo not invoke `mcp-config` programmatically. Instead, finish the non-MCP writes\nand show a copy-pasteable manual follow-up for the user, including:\n\n- the `/mcp-config` command they should run,\n- target scope and target path,\n- the normalized JSON entry or entries to add,\n- collision policy: keep existing Kimi entries on name conflict,\n- the reminder that unrelated entries must be preserved.\n\nMake it clear that MCP import is pending until the user manually runs\n`/mcp-config` with the prepared entries.\n\n### 4. Show the final preview and stop\n\nAfter scanning, show a concise final preview grouped by target file/directory:\n\n- Will append instruction blocks\n- Will copy skill bundles/files\n- Will leave these MCP entries pending for a manual `/mcp-config` follow-up\n- Already present / skipped\n- Warnings and blockers\n\nThen ask for explicit confirmation before writing. Use a clear choice such as:\n\n- Apply import\n- Cancel\n\nIf there are blockers, do not offer apply; explain what must be fixed first.\n\n### 5. Apply only after confirmation\n\nWhen the user confirms:\n\n- Create target directories with private permissions where possible.\n- Append instruction blocks without duplicating existing imported source blocks.\n- Copy skills without overwriting existing target entries.\n- Do not write MCP entries. Show the prepared `/mcp-config` follow-up command\n and mark MCP import as pending user action.\n- Report exactly what changed and what was skipped.\n- Tell the user to start a new session (for example `/new`) or restart Kimi Code\n for newly imported skills, instructions, and MCP servers to be picked up.\n\n## Output style\n\nBe brief but precise. Use absolute paths in previews and summaries. Prefer a\nsmall table or bullet list over long prose. If nothing is found for a selected\ncategory, say so and do not treat it as an error.\n";
65824
65844
  //#endregion
65825
65845
  //#region ../../packages/agent-core/src/skill/builtin/import-from-cc-codex.ts
@@ -65841,7 +65861,7 @@ const IMPORT_FROM_CC_CODEX_SKILL = {
65841
65861
  }
65842
65862
  };
65843
65863
  //#endregion
65844
- //#region ../../packages/agent-core/src/skill/builtin/mcp-config.md
65864
+ //#region ../../packages/agent-core/src/skill/builtin/mcp-config.md?raw
65845
65865
  var mcp_config_default = "---\nname: mcp-config\ndescription: Configure MCP servers and handle MCP OAuth login.\n---\n\n# Interactive MCP server configuration\n\nThe user invoked this skill through `/mcp-config` or `/skill:mcp-config`.\nEither they want to log into an MCP server that asked for OAuth, or they\nwant to edit the `mcp.json` that lists MCP servers. The work is small and\nlocal — handle it on this turn yourself, no agents or planning todos.\n\nPick the flow from the user's message and your tool list:\n\n- An `mcp__<server>__authenticate` tool is in your list, the user says\n \"log in\" / \"auth\" / \"sign in\", they invoke `/mcp-config login\n <server>`, or they quote a `needs-auth` status → **Login**.\n- Add / edit / remove / list of an `mcp.json` entry → **Config edit**.\n- Bare `/mcp-config` with no `authenticate` tool in your list →\n **Config edit**. If there were a pending login, the authenticate tool\n would be in your list.\n\n## Login\n\nEach MCP server in `needs-auth` exposes one `mcp__<server>__authenticate`\ntool. Call it for the server the user means — its own description owns\nthe OAuth UX (printing the URL, blocking on the callback, reconnecting on\nsuccess). Surface its output verbatim, including the authorization URL\nunchanged; the URL contains state and PKCE parameters that break if\nedited.\n\nIf the user named a server that has no authenticate tool, say so in one\nsentence and stop — do **not** fall into config edit. They're trying to\nlog in to a server that isn't currently waiting for login; quietly\nrewriting `mcp.json` would be the wrong fix. If multiple authenticate\ntools exist and the user didn't name one, ask which.\n\n## Config edit\n\nConfig lives in three files; on key collision, later entries in this\nprecedence order override earlier ones.\n\nThe kimi-code runtime resolves the user-global directory as `KIMI_CODE_HOME`\nfirst, falling back to `~/.kimi-code`. Before touching the user-global file,\nresolve the actual directory with Bash so you don't read or write the wrong\none. Check whether `KIMI_CODE_HOME` is set and fall back to `~/.kimi-code`\nwhen it is empty:\n\n```bash\necho \"$KIMI_CODE_HOME\"\necho \"$HOME/.kimi-code\"\n```\n\nUse the first line when it is non-empty; otherwise use the second line. In the\nrest of this skill, `<KIMI_CODE_HOME>` means that resolved data root —\n**never assume `~/.kimi-code`**.\n\n- User-global: `<KIMI_CODE_HOME>/mcp.json`. Use for servers you want\n everywhere.\n- Project-root: `<project root>/.mcp.json`, where project root is found\n by walking up from `<cwd>` to the nearest `.git`. Use for\n Claude-compatible, repo-shared, or cross-agent servers.\n- Project-local: `<cwd>/.kimi-code/mcp.json`. Use for Kimi-specific\n overrides in the current working directory.\n\nMention once that project-root and project-local stdio entries spawn\ncommands at session start, so they should only live in trusted repos.\n\nAll three files wrap their entries the same way:\n\n```json\n{ \"mcpServers\": { \"<name>\": { /* entry */ } } }\n```\n\nA minimal stdio entry needs `command` (+ optional `args`, `env`, `cwd`).\nFor project-root `.mcp.json`, stdio entries run from the project root by\ndefault; relative `cwd` values are resolved against the directory that\ncontains `.mcp.json`.\nA minimal http entry needs `url`; add `bearerTokenEnvVar: \"ENV_NAME\"` for\nservers that authenticate with a static bearer token from the\nenvironment. Servers that use OAuth take no token field — the login flow\nabove handles them. `transport` is inferred from `command` vs `url`, so\nomit it. For less common fields (`enabled`, `startupTimeoutMs`,\n`toolTimeoutMs`, `enabledTools`, `disabledTools`, `headers`) the source of\ntruth is `McpServerStdioConfigSchema` / `McpServerHttpConfigSchema` in\n`packages/agent-core/src/config/schema.ts`.\n\nIf the user only wants to **see** what's configured, read all three files,\nshow a merged view with enough source-path context to inspect or remove a\nserver from the file that actually declared it, and stop — no scope\nprompt, no write.\n\nFor changes, the flow is:\n\n1. **Pick a scope.** Infer it from the user's words when you can\n (global / everywhere / all projects → user-global; root / repo /\n shared / cross-agent / Claude / `.mcp.json` → project-root; cwd /\n current directory / Kimi-specific / `.kimi-code` → project-local). When\n the request is genuinely scope-less, use one `AskUserQuestion` to ask\n user-global vs project-root vs project-local, defaulting to\n user-global. Use plain text for every other question — `AskUserQuestion`\n is a poor fit for free-form input. If the user dismisses the scope\n question, stop; you can't safely guess where they wanted the change.\n2. **Read and announce.** Read the target file (a missing or empty file\n is fine; you'll create `{ \"mcpServers\": {} }`). If JSON parsing fails,\n surface the error verbatim and stop — silently overwriting a broken\n file could destroy work. Then show the user the target path, what's\n currently in it, and the entry you're about to write or delete. This\n is for transparency, not a confirmation gate — the Edit/Write\n permission prompt is the real gate, and your message is what gives\n the user context when that prompt appears. In yolo / afk modes there\n is no prompt, which is those modes' explicit contract.\n3. **Write and tell them how to reload MCP servers.** Preserve unrelated\n entries and the `mcpServers` wrapper. MCP servers load at session\n start, so tell the user to start a new session (for example `/new`) or\n restart `kimi-code` for the change to take effect.\n\n## Secrets\n\nDon't store secrets (tokens, keys, passwords) as literals in\n`mcp.json` — it's a plain config file on disk. http servers should use\n`bearerTokenEnvVar` to reference an env var instead; if a stdio entry\nmust inline one in `env`, warn the user before writing.\n";
65846
65866
  //#endregion
65847
65867
  //#region ../../packages/agent-core/src/skill/builtin/mcp-config.ts
@@ -65863,13 +65883,13 @@ const MCP_CONFIG_SKILL = {
65863
65883
  }
65864
65884
  };
65865
65885
  //#endregion
65866
- //#region ../../packages/agent-core/src/skill/builtin/sub-skill/consolidate/SKILL.md
65867
- var SKILL_default$2 = "---\nname: sub-skill.consolidate\ndescription: Apply an approved sub-skill grouping by moving user-specified skills into a parent bundle, with timestamped backups of every modified directory.\ndisable-model-invocation: true\n---\n\n# Consolidate sub-skills (`sub-skill.consolidate`)\n\nExecute the reorganization by moving user-specified skills into a parent bundle, forming a sub-skill hierarchy.\n\n## When to use\n\n- The user has approved a grouping proposal (typically from `sub-skill.review`) and wants to apply it.\n- Migrating standalone skills into a new or existing parent bundle.\n\n## Process\n\n1. **Confirm the plan.** Restate which skills will move and where, and ask the user to confirm **before** making any file changes.\n2. **Back up every original skill directory.** Before moving anything, create a timestamped backup of each skill directory that will be modified.\n - For a skill at `<root>/<skill-name>/SKILL.md`, back up the entire `<skill-name>` directory:\n ```bash\n cp -r <skill-name> \"<skill-name>.$(date +%Y%m%d-%H%M%S).bak\"\n ```\n - Keep all backups; never overwrite an existing backup file.\n3. **Create or update the parent bundle.**\n - If the parent does not exist, create `<parent-name>/SKILL.md` with `has-sub-skill: true` in the frontmatter.\n - If the parent already exists, ensure its frontmatter includes `has-sub-skill: true`.\n4. **Move child skills into the parent.** Move each child skill's entire directory under the parent bundle.\n - Example: `web-search/` → `web-research/web-search/`\n5. **Verify the result.** List the new directory structure and confirm each moved skill still has a valid `SKILL.md` with required frontmatter (`name` and `description`).\n6. **Report the change.** Summarize what was moved, the new structure, and where backups are located.\n\n## Don'ts\n\n- **Never move skills without backing up first.**\n- **Never overwrite an existing backup** — always use a fresh timestamped suffix.\n- **Don't drop frontmatter or payload files** during the move; the entire directory must be preserved.\n- **Don't create deeply nested hierarchies** (3+ levels) unless the user explicitly requests it.\n";
65886
+ //#region ../../packages/agent-core/src/skill/builtin/sub-skill/consolidate/SKILL.md?raw
65887
+ var SKILL_default$2 = "---\nname: consolidate\ndescription: Apply an approved sub-skill grouping by moving user-specified skills into a parent bundle, with timestamped backups of every modified directory.\ndisable-model-invocation: true\n---\n\n# Consolidate sub-skills (`sub-skill.consolidate`)\n\nExecute the reorganization by moving user-specified skills into a parent bundle, forming a sub-skill hierarchy.\n\n## When to use\n\n- The user has approved a grouping proposal (typically from `sub-skill.review`) and wants to apply it.\n- Migrating standalone skills into a new or existing parent bundle.\n\n## Process\n\n1. **Confirm the plan.** Restate which skills will move and where, and ask the user to confirm **before** making any file changes.\n2. **Back up every original skill directory.** Before moving anything, create a timestamped backup of each skill directory that will be modified.\n - For a skill at `<root>/<skill-name>/SKILL.md`, back up the entire `<skill-name>` directory:\n ```bash\n cp -r <skill-name> \"<skill-name>.$(date +%Y%m%d-%H%M%S).bak\"\n ```\n - Keep all backups; never overwrite an existing backup file.\n3. **Create or update the parent bundle.**\n - If the parent does not exist, create `<parent-name>/SKILL.md` with `has-sub-skill: true` in the frontmatter.\n - If the parent already exists, ensure its frontmatter includes `has-sub-skill: true`.\n4. **Move child skills into the parent.** Move each child skill's entire directory under the parent bundle.\n - Example: `web-search/` → `web-research/web-search/`\n5. **Keep documentation directory alignment.** When moving documentation, references, examples, assets, or other payload directories, align them with the new skill directory layout.\n - Preserve relative links from `SKILL.md` to files such as `references/`, `assets/`, `examples/`, or templates.\n - If a child skill moves from `<root>/<child>/` to `<root>/<parent>/<child>/`, its documentation payload should move with that child unless the approved plan says otherwise.\n - Do not leave documentation in the old location or merge unrelated documentation directories together.\n6. **Verify the result.** List the new directory structure and confirm each moved skill still has a valid `SKILL.md` with required frontmatter (`name` and `description`). Check documentation directory alignment and relative links after the move.\n7. **Report the change.** Summarize what was moved, the new structure, any documentation directories that moved, and where backups are located.\n\n## Don'ts\n\n- **Never move skills without backing up first.**\n- **Never overwrite an existing backup** — always use a fresh timestamped suffix.\n- **Don't drop frontmatter or payload files** during the move; the entire directory must be preserved.\n- **Don't break documentation directory alignment** — references, assets, examples, and templates must stay aligned with the skill directory that uses them.\n- **Don't create deeply nested hierarchies** (3+ levels) unless the user explicitly requests it.\n";
65868
65888
  //#endregion
65869
- //#region ../../packages/agent-core/src/skill/builtin/sub-skill/review/SKILL.md
65870
- var SKILL_default$1 = "---\nname: sub-skill.review\ndescription: Analyze the available skill set and recommend candidate groups that could be consolidated into sub-skill bundles. Read-only — proposes a plan, does not move files.\ndisable-model-invocation: true\n---\n\n# Review sub-skills (`sub-skill.review`)\n\nAnalyze the current skill inventory and identify candidate groups that could be consolidated into sub-skill bundles. This sub-skill is **read-only**: it produces a proposal for the user to review before any file changes are made by `sub-skill.consolidate`.\n\n## When to use\n\n- The user asks to review, reorganize, or audit the skill inventory.\n- There are many loosely-related skills that might benefit from hierarchical grouping.\n- The user wants to evaluate whether a new skill should be a sub-skill of an existing parent.\n\n## Process\n\n1. **List the current inventory.** Use the skill registry (or scan the configured skill roots) to get a full list of skill names, descriptions, and source scopes.\n2. **Categorize by domain.** Group skills by functional domain — e.g. file operations, web tools, collaboration, observability.\n3. **Detect coupling.** Identify skills that are frequently used together or share similar `whenToUse` conditions.\n4. **Flag granularity issues.** Call out skills that are too fine-grained (e.g. one skill per CLI flag) or too broad to land in any single domain.\n5. **Propose sub-skill structures.** For each candidate group, list:\n - The parent skill name and a one-line description.\n - The children that should move under it.\n - Whether the parent needs `has-sub-skill: true` (it does, if children should be discovered).\n6. **Output a summary report.** Present findings as a concise grouped list with rationale, and stop. Do **not** edit any file — that's `sub-skill.consolidate`'s job.\n\n## Criteria for a good sub-skill grouping\n\n- **Shared context.** Children operate within the same domain or workflow.\n- **Composable entry point.** The parent gives a natural top-level handle; children handle specifics.\n- **Shallow nesting.** Prefer 2 levels (parent → child). Avoid 3+ unless strictly necessary.\n- **Backward compatibility.** Existing skill names should ideally remain discoverable.\n\n## Example output format\n\n```\nProposed sub-skill: web-research\n - Parent: web-research (has-sub-skill: true)\n - Children:\n - web-search → move under web-research/search\n - fetch-url → move under web-research/fetch\n - Rationale: Both deal with online information retrieval and are often chained together.\n```\n";
65889
+ //#region ../../packages/agent-core/src/skill/builtin/sub-skill/review/SKILL.md?raw
65890
+ var SKILL_default$1 = "---\nname: review\ndescription: Analyze the available skill set and recommend candidate groups that could be consolidated into sub-skill bundles. Read-only — proposes a plan, does not move files.\ndisable-model-invocation: true\n---\n\n# Review sub-skills (`sub-skill.review`)\n\nAnalyze the current skill inventory and identify candidate groups that could be consolidated into sub-skill bundles. This sub-skill is **read-only**: it produces a proposal for the user to review before any file changes are made by `sub-skill.consolidate`.\n\n## When to use\n\n- The user asks to review, reorganize, or audit the skill inventory.\n- There are many loosely-related skills that might benefit from hierarchical grouping.\n- The user wants to evaluate whether a new skill should be a sub-skill of an existing parent.\n\n## Process\n\n1. **List the current inventory.** Use the skill registry (or scan the configured skill roots) to get a full list of skill names, descriptions, and source scopes.\n2. **Categorize by domain.** Group skills by functional domain — e.g. file operations, web tools, collaboration, observability.\n3. **Detect coupling.** Identify skills that are frequently used together or share similar `whenToUse` conditions.\n4. **Flag granularity issues.** Call out skills that are too fine-grained (e.g. one skill per CLI flag) or too broad to land in any single domain.\n5. **Propose sub-skill structures.** For each candidate group, list:\n - The parent skill name and a one-line description.\n - The children that should move under it.\n - Any documentation, reference, example, asset, or template directories that must move with each child so the final directory layout stays aligned.\n - Whether the parent needs `has-sub-skill: true` (it does, if children should be discovered).\n6. **Output a summary report.** Present findings as a concise grouped list with rationale, and stop. Do **not** edit any file — that's `sub-skill.consolidate`'s job.\n\n## Criteria for a good sub-skill grouping\n\n- **Shared context.** Children operate within the same domain or workflow.\n- **Composable entry point.** The parent gives a natural top-level handle; children handle specifics.\n- **Shallow nesting.** Prefer 2 levels (parent → child). Avoid 3+ unless strictly necessary.\n- **Backward compatibility.** Existing skill names should ideally remain discoverable.\n\n## Example output format\n\n```\nProposed sub-skill: web-research\n - Parent: web-research (has-sub-skill: true)\n - Children:\n - web-search → move under web-research/search\n - fetch-url → move under web-research/fetch\n - Documentation alignment: move each child's references/examples/assets with that child.\n - Rationale: Both deal with online information retrieval and are often chained together.\n```\n";
65871
65891
  //#endregion
65872
- //#region ../../packages/agent-core/src/skill/builtin/sub-skill/SKILL.md
65892
+ //#region ../../packages/agent-core/src/skill/builtin/sub-skill/SKILL.md?raw
65873
65893
  var SKILL_default = "---\nname: sub-skill\ndescription: Discover and reorganize the skill inventory into hierarchical sub-skill bundles. Use when the user asks to review, group, or consolidate skills into a parent bundle.\ndisable-model-invocation: true\nhas-sub-skill: true\n---\n\n# Sub-skill\n\nContainer skill for analyzing the local skill inventory and reorganizing it into hierarchical sub-skill bundles (`has-sub-skill: true` parents with children inside).\n\n## When to use\n\n- The user asks to review, reorganize, or consolidate skills.\n- There are many loosely-related skills that might benefit from hierarchical grouping.\n- The user wants to evaluate whether a new skill should be a sub-skill of an existing one.\n\n## Sub-skills\n\n- **`sub-skill.review`** — Analyze the current inventory and propose candidate sub-skill groupings.\n- **`sub-skill.consolidate`** — Apply an approved grouping by moving skills into a parent bundle, with timestamped backups.\n\nThe usual flow is `sub-skill.review` first (read-only proposal), then `sub-skill.consolidate` after the user approves a plan. Never run consolidate without an explicit go-ahead from the user.\n";
65874
65894
  //#endregion
65875
65895
  //#region ../../packages/agent-core/src/skill/builtin/sub-skill.ts
@@ -65882,6 +65902,7 @@ function makeBuiltin(body, dirName, pseudoPath, extraMetadata = {}) {
65882
65902
  });
65883
65903
  return {
65884
65904
  ...parsed,
65905
+ name: dirName,
65885
65906
  path: pseudoPath,
65886
65907
  dir: pseudoPath,
65887
65908
  metadata: {
@@ -65895,10 +65916,16 @@ const SUB_SKILL_PARENT = makeBuiltin(SKILL_default, "sub-skill", "builtin://sub-
65895
65916
  disableModelInvocation: true,
65896
65917
  "has-sub-skill": true
65897
65918
  });
65898
- const SUB_SKILL_REVIEW = makeBuiltin(SKILL_default$1, "sub-skill.review", "builtin://sub-skill/review", { disableModelInvocation: true });
65899
- const SUB_SKILL_CONSOLIDATE = makeBuiltin(SKILL_default$2, "sub-skill.consolidate", "builtin://sub-skill/consolidate", { disableModelInvocation: true });
65919
+ const SUB_SKILL_REVIEW = makeBuiltin(SKILL_default$1, "sub-skill.review", "builtin://sub-skill/review", {
65920
+ disableModelInvocation: true,
65921
+ isSubSkill: true
65922
+ });
65923
+ const SUB_SKILL_CONSOLIDATE = makeBuiltin(SKILL_default$2, "sub-skill.consolidate", "builtin://sub-skill/consolidate", {
65924
+ disableModelInvocation: true,
65925
+ isSubSkill: true
65926
+ });
65900
65927
  //#endregion
65901
- //#region ../../packages/agent-core/src/skill/builtin/update-config.md
65928
+ //#region ../../packages/agent-core/src/skill/builtin/update-config.md?raw
65902
65929
  var update_config_default = "---\nname: update-config\ndescription: Inspect or edit kimi-code's own config — `config.toml` (model, provider, permission, hooks) and `tui.toml` (theme, editor, notifications, auto-update). Use when the user asks what a setting does or wants to change one.\n---\n\n# Configure kimi-code (update-config)\n\nHelp the user inspect, change, and validate kimi-code's configuration files. The files are **TOML** with **snake_case** keys.\n\n## The two config files\n\nkimi-code has two TOML config files, both under `<KIMI_CODE_HOME>/`, both snake_case, but with different ownership — decide which one the user means before doing anything.\n\nThe runtime resolves the data directory as `KIMI_CODE_HOME` first, falling back to `~/.kimi-code`. Before doing anything, resolve the actual directory with Bash so you don't write to the wrong place. Check whether `KIMI_CODE_HOME` is set and fall back to `~/.kimi-code` when it is empty:\n\n```bash\necho \"$KIMI_CODE_HOME\"\necho \"$HOME/.kimi-code\"\n```\n\nUse the first line when it is non-empty; otherwise use the second line. In the rest of this skill, `<KIMI_CODE_HOME>` means that resolved root — **never assume `~/.kimi-code`**.\n\n- **`config.toml`** — agent / runtime settings: `default_model`, `providers`, `models`, `thinking`, `permission`, `hooks`, `loop_control`, etc.\n- **`tui.toml`** — terminal-UI / client preferences: `theme`, `[editor].command`, `[notifications]`, `[upgrade].auto_install` (auto-update). These can usually also be changed with the interactive commands `/config`, `/theme`, `/editor`, which is easier — prefer pointing the user at those.\n\nThe \"read → copy → Edit → validate → back up → overwrite\" flow below applies to both files; only **which reload command applies** differs (see Capability 4).\n\n## Prerequisite 1: the official docs are the single source of truth\n\nBefore touching any config, use **FetchURL** to fetch the official config docs as the one authoritative reference for fields (key names, types, allowed values, owning section):\n\n```\nhttps://moonshotai.github.io/kimi-code/en/configuration/config-files.html\n```\n\n- Use the **snake_case key names and sections exactly as documented** — don't invent them, don't guess camelCase.\n- If FetchURL is unavailable or the fetch fails, tell the user plainly that you can't reach the online docs, and ask them to paste the relevant section or confirm whether to proceed from what you already know. **Never edit blindly without an authoritative reference.**\n\n## Prerequisite 2: read the target file before any change\n\nBefore any modification, use **Read** on the target config file (decide whether it's `config.toml` or `tui.toml` per the above):\n\n- Location: `<KIMI_CODE_HOME>/config.toml` or `<KIMI_CODE_HOME>/tui.toml`. For other scopes/files, defer to the official docs.\n- A missing or empty file is fine — you'll create a minimal skeleton later.\n- If the file exists but **fails to parse as TOML**, report the error verbatim and **stop** — never overwrite a broken file in place (it could destroy the user's existing config).\n\n---\n\n## Capability 1: explain configuration (read-only, no file changes)\n\nWhen the user asks \"what config is there\", \"what does this setting do\", or \"how do I use it\":\n\n1. Fetch the official docs (Prerequisite 1).\n2. Read the current `config.toml` (Prerequisite 2).\n3. Answer against both: list the relevant sections / keys, what each is for, **current value vs default**, and the allowed-value range; say which file and section each lives in.\n4. Present it as a compact grouped list or table. **Stay read-only — write no files.**\n\n## Capability 2: make changes for the user (copy → Edit → validate → back up → overwrite)\n\nDon't edit the target file in place, and **don't rewrite it from scratch** — instead copy it, Edit the copy, and keep the original out of any broken state the whole time:\n\n1. **Clarify intent**: which key, what value, and which file (`config.toml` or `tui.toml`). Ask in one line if ambiguous; for discrete choices (e.g. scope) AskUserQuestion is fine, but use plain questions for free-form input.\n2. **Read the target file** (Prerequisite 2): Read it to understand the current state and confirm it parses.\n3. **Copy out a candidate (do not create from scratch)**: use **Bash** to copy the target verbatim — `cp config.toml config-new.toml` (same directory, `-new` suffix; for tui.toml, `cp tui.toml tui-new.toml`). **Leave the original untouched for now.**\n - Only when the target doesn't exist (nothing to copy) should you use **Write** to create a minimal skeleton candidate (e.g. just the comment line `# <KIMI_CODE_HOME>/config.toml`).\n4. **Edit the candidate**: use the **Edit** tool on the candidate to **change/add only the target key** — never rewrite the whole file. That way every existing section, entry, comment, and bit of formatting stays exactly as-is; only what should change changes. The candidate is identical to the original, so use the content you read in step 2 to locate the Edit anchor. Check the change against the official docs (key / section / value type / allowed values, snake_case).\n5. **Validate the candidate** (see Capability 3, via `kimi doctor`). **If anything fails, keep Editing the candidate and re-validate, looping until it all passes.**\n6. **Back up and overwrite** (only after validation fully passes):\n - **Back up the old file — always create a new timestamped backup, keep all of them, never overwrite an existing backup.** Copy this exactly with **Bash** (for config.toml): `cp config.toml \"config.toml.$(date +%Y%m%d-%H%M%S).bak\"`; for tui.toml: `cp tui.toml \"tui.toml.$(date +%Y%m%d-%H%M%S).bak\"`. Skip the backup only if the target didn't exist.\n - Overwrite with the candidate: `mv config-new.toml config.toml`.\n - If reload errors after the overwrite, the user can recover from **the most recent timestamped backup**.\n7. Tell the user how to apply it (see Capability 4).\n\n## Capability 3: validate the candidate file (must pass before overwrite)\n\nUse **`kimi doctor`** to validate the candidate you wrote — it doesn't start the TUI and doesn't modify any file; it runs kimi's own parser + schema (syntax and schema together), so it's the authoritative check. Pick the subcommand by which file you changed, and pass the **candidate** path explicitly:\n\n- changed `config.toml` → `kimi doctor config <config-new.toml path>`\n- changed `tui.toml` → `kimi doctor tui <tui-new.toml path>`\n\nWhen a path is passed explicitly the file must exist (your candidate does, so that's fine). **Exit code 0 = pass (valid or skipped); non-zero = a specified file is missing or the config is invalid** — show the output verbatim, fix the candidate, and re-run, looping until it's 0.\n\nThen do two checks `kimi doctor` can't:\n\n1. **Cross-check values against the official docs** (single source of truth): are the key / section / enum values as documented, and snake_case? doctor guarantees \"schema-valid\", but \"valid yet not what the user wanted\" (e.g. a misspelled model alias) needs the docs.\n2. **Completeness**: every existing entry is still present (the candidate fully replaces the target — a dropped line is a deletion).\n\n> To also check whether the currently **active** config is OK overall, run `kimi doctor` with no path (it checks the default `config.toml` + `tui.toml`, showing a missing one as skipped).\n\n## Capability 4: tell the user how to apply changes\n\nOnce local validation passes, tell the user how to make the change take effect — **the reload command depends on which file you changed**:\n\n- changed **`config.toml`** → run **`/reload`** in the TUI (reloads the session and applies `config.toml`; it also reloads `tui.toml`).\n- changed **`tui.toml`** → run **`/reload-tui`** (reloads only `tui.toml`, lighter); `/reload` works too (reloads both).\n- changed both → a single **`/reload`** covers it.\n\nNote: `/reload` is available **only when idle** — if a reply is streaming, press Esc / Ctrl-C to stop first. `kimi doctor` already validated the schema before the overwrite, so reload should apply cleanly; if it still errors, follow the message to fix it or recover from the most recent timestamped backup. If you don't want to reload now, the **next new session** picks it up automatically.\n\n## Don'ts\n\n- **Always back up before overwriting**, with a **timestamped name and all history kept** — don't skip the backup, don't keep only a single `.bak`, don't overwrite an old backup.\n- Don't drop unrelated entries (the candidate fully replaces the target — a dropped line is a deletion).\n- When you can't reach the docs / have no authoritative reference, don't edit by guessing.\n";
65903
65930
  //#endregion
65904
65931
  //#region ../../packages/agent-core/src/skill/builtin/update-config.ts
@@ -65964,7 +65991,7 @@ async function discoverSkills(options) {
65964
65991
  const warn = options.onWarning ?? (() => {});
65965
65992
  const skip = options.onSkippedByPolicy ?? (() => {});
65966
65993
  const byName = /* @__PURE__ */ new Map();
65967
- async function walkSkillDir(dirPath, root, isTopLevel, depth) {
65994
+ async function walkSkillDir(dirPath, root, isTopLevel, depth, subSkillParentName) {
65968
65995
  if (depth > MAX_SKILL_SCAN_DEPTH) return;
65969
65996
  let entries;
65970
65997
  try {
@@ -65981,7 +66008,7 @@ async function discoverSkills(options) {
65981
66008
  if (entry === "node_modules" || entry.startsWith(".")) continue;
65982
66009
  if (await isDir(entryPath)) subdirs.push(entry);
65983
66010
  }
65984
- const allowedSubSkillBundles = /* @__PURE__ */ new Set();
66011
+ const allowedSubSkillBundles = /* @__PURE__ */ new Map();
65985
66012
  for (const entry of directorySkills) {
65986
66013
  const skill = await parseAndRegister({
65987
66014
  parse,
@@ -65991,9 +66018,10 @@ async function discoverSkills(options) {
65991
66018
  root,
65992
66019
  onDiscoveredSkill: options.onDiscoveredSkill,
65993
66020
  warn,
65994
- skip
66021
+ skip,
66022
+ subSkillParentName
65995
66023
  });
65996
- if (skill !== void 0 && hasSubSkillEnabled(skill)) allowedSubSkillBundles.add(entry);
66024
+ if (skill !== void 0 && hasSubSkillEnabled(skill)) allowedSubSkillBundles.set(entry, skill.name);
65997
66025
  }
65998
66026
  if (isTopLevel) {
65999
66027
  if (root.plugin !== void 0) {
@@ -66033,7 +66061,8 @@ async function discoverSkills(options) {
66033
66061
  }
66034
66062
  for (const entry of subdirs) {
66035
66063
  if (directorySkills.has(entry) && !allowedSubSkillBundles.has(entry)) continue;
66036
- await walkSkillDir(path.join(dirPath, entry), root, false, depth + 1);
66064
+ const allowedSubSkillParentName = allowedSubSkillBundles.get(entry);
66065
+ await walkSkillDir(path.join(dirPath, entry), root, false, depth + 1, allowedSubSkillParentName ?? subSkillParentName);
66037
66066
  }
66038
66067
  }
66039
66068
  for (const root of options.roots) await walkSkillDir(root.path, root, true, 0);
@@ -66097,11 +66126,20 @@ async function pushProvidedRoot(out, root, isDir, realpath) {
66097
66126
  }
66098
66127
  async function parseAndRegister(input) {
66099
66128
  try {
66100
- const skill = await input.parse({
66129
+ const parsed = await input.parse({
66101
66130
  skillMdPath: input.skillMdPath,
66102
66131
  skillDirName: input.skillDirName,
66103
66132
  source: input.root.source
66104
66133
  });
66134
+ const subSkillParentName = input.subSkillParentName;
66135
+ const skill = subSkillParentName !== void 0 ? {
66136
+ ...parsed,
66137
+ name: qualifySubSkillName(subSkillParentName, parsed.name),
66138
+ metadata: {
66139
+ ...parsed.metadata,
66140
+ isSubSkill: true
66141
+ }
66142
+ } : parsed;
66105
66143
  const discovered = input.root.plugin === void 0 ? skill : {
66106
66144
  ...skill,
66107
66145
  plugin: input.root.plugin
@@ -66109,7 +66147,7 @@ async function parseAndRegister(input) {
66109
66147
  input.onDiscoveredSkill?.(discovered);
66110
66148
  const key = normalizeSkillName(discovered.name);
66111
66149
  if (!input.byName.has(key)) input.byName.set(key, discovered);
66112
- return skill;
66150
+ return discovered;
66113
66151
  } catch (error) {
66114
66152
  if (error instanceof UnsupportedSkillTypeError) input.skip({
66115
66153
  path: input.skillMdPath,
@@ -66121,6 +66159,10 @@ async function parseAndRegister(input) {
66121
66159
  return;
66122
66160
  }
66123
66161
  }
66162
+ function qualifySubSkillName(parentName, skillName) {
66163
+ if (skillName === parentName || skillName.startsWith(`${parentName}.`)) return skillName;
66164
+ return `${parentName}.${skillName}`;
66165
+ }
66124
66166
  function hasSubSkillEnabled(skill) {
66125
66167
  const nested = skill.metadata["metadata"];
66126
66168
  const nestedFlag = typeof nested === "object" && nested !== null ? nested["has-sub-skill"] === true || nested["hasSubSkill"] === true : false;
@@ -66249,7 +66291,7 @@ var SkillRegistry = class {
66249
66291
  }
66250
66292
  getModelSkillListing() {
66251
66293
  const lines = ["DISREGARD any earlier skill listings. Current available skills:"];
66252
- const listing = renderGroupedSkills(this.listInvocableSkills(), formatModelSkill);
66294
+ const listing = renderGroupedSkills(this.listInvocableSkills().filter((skill) => skill.metadata.isSubSkill !== true), formatModelSkill);
66253
66295
  if (listing.length > 0) lines.push(listing);
66254
66296
  return lines.length === 1 ? "" : lines.join("\n");
66255
66297
  }
@@ -66392,10 +66434,10 @@ var SkillManager = class {
66392
66434
  }
66393
66435
  };
66394
66436
  //#endregion
66395
- //#region ../../packages/agent-core/src/agent/swarm/enter-reminder.md
66437
+ //#region ../../packages/agent-core/src/agent/swarm/enter-reminder.md?raw
66396
66438
  var enter_reminder_default = "## Swarm Mode\n\nYou are now in \"agent swarm\" mode. The user may send tasks that require a large number of parallel subagents.\n\n## Workflow\n\nYou do not need to use TodoList to record this workflow.\n\n1. First, you may need to do a small amount of exploratory work before deciding how to divide the task across subagents. You may not need subagents during this exploratory phase.\n\n2. After exploring, if you are convinced no subagent is needed to complete the task, tell the user why and wait for further instructions; otherwise, continue with the appropriate delegation.\n\n3. Once you have enough context, do not handle the main work yourself. Use AgentSwarm with a `prompt_template` containing the `{{item}}` placeholder and an `items` array for the requested or appropriate number of subagents, partitioning the problem so each item gives one subagent a distinct part of the work. Pass `subagent_type` when the whole swarm should use a non-default subagent profile.\n\n## Coordination\n\n- Give each subagent a distinct scope of work.\n- Avoid duplicating work across subagents.\n- Avoid assigning conflicting changes or responsibilities to different subagents.\n- Remember that subagents have your full capabilities. Do not overload their prompts with excessive detail; only describe the necessary background and each subagent's specific task.\n- Unless the user explicitly specifies a lower limit, do not try to conserve the number of agents. AgentSwarm supports up to 128 subagents and queues launches automatically, so decompose work as finely as possible while keeping subagent responsibilities non-conflicting; combine tasks only when they are genuinely inseparable. If the subagents only need to read, inspect, or report back without making changes, their scopes may overlap slightly.\n";
66397
66439
  //#endregion
66398
- //#region ../../packages/agent-core/src/agent/swarm/exit-reminder.md
66440
+ //#region ../../packages/agent-core/src/agent/swarm/exit-reminder.md?raw
66399
66441
  var exit_reminder_default = "## Swarm Mode Ended\n\nSwarm Mode has ended. You are no longer required to follow the Swarm Mode workflow.\n\nThe user's next request is likely to be a regular request that does not need AgentSwarm. If the request still benefits from parallel subagents, you may call the AgentSwarm tool, but decide from the new request itself rather than the ended Swarm Mode workflow.\n";
66400
66442
  //#endregion
66401
66443
  //#region ../../packages/agent-core/src/agent/swarm/index.ts
@@ -80068,16 +80110,16 @@ function normalizeSourcePath(path) {
80068
80110
  return normalize(path.replaceAll("\\", "/")).replace(/^\.\//, "");
80069
80111
  }
80070
80112
  //#endregion
80071
- //#region ../../packages/agent-core/src/profile/default/agent.yaml
80113
+ //#region ../../packages/agent-core/src/profile/default/agent.yaml?raw
80072
80114
  var agent_default$1 = "name: agent\ndescription: Default Kimi Code agent\n\nsystemPromptPath: ./system.md\npromptVars:\n roleAdditional: ''\n\ntools:\n - Read\n - Write\n - Edit\n - Grep\n - Glob\n - Bash\n - TaskList\n - TaskOutput\n - TaskStop\n - CronCreate\n - CronList\n - CronDelete\n - ReadMediaFile\n - TodoList\n - Skill\n - WebSearch\n - Agent\n - AgentSwarm\n - FetchURL\n - AskUserQuestion\n - EnterPlanMode\n - ExitPlanMode\n - CreateGoal\n - GetGoal\n - SetGoalBudget\n - UpdateGoal\n - mcp__*\n\nsubagents:\n coder:\n description: Good at general software engineering tasks.\n explore:\n description: Fast codebase exploration with prompt-enforced read-only behavior.\n plan:\n description: Read-only implementation planning and architecture design.\n";
80073
80115
  //#endregion
80074
- //#region ../../packages/agent-core/src/profile/default/coder.yaml
80116
+ //#region ../../packages/agent-core/src/profile/default/coder.yaml?raw
80075
80117
  var coder_default = "extends: agent\nname: coder\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\nwhenToUse: |\n Use this agent for non-trivial software engineering work that may require reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - Write\n - Edit\n - WebSearch\n - FetchURL\n - mcp__*\n";
80076
80118
  //#endregion
80077
- //#region ../../packages/agent-core/src/profile/default/explore.yaml
80119
+ //#region ../../packages/agent-core/src/profile/default/explore.yaml?raw
80078
80120
  var explore_default = "extends: agent\nname: explore\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n You are a codebase exploration specialist. Your role is EXCLUSIVELY to search, read, and analyze existing code and resources. You do NOT have access to file editing tools.\n\n Your strengths:\n - Rapidly finding files using glob patterns\n - Searching code and text with powerful regex patterns\n - Reading and analyzing file contents\n - Running read-only shell commands (git log, git diff, ls, find, etc.)\n\n Guidelines:\n - Use Glob for broad file pattern matching. Patterns MUST contain a literal anchor (extension or subdirectory); pure wildcards like `*` or `**/*` are rejected by the tool.\n - Use Grep for searching file contents with regex\n - Use Read when you know the specific file path\n - Use Bash ONLY for read-only operations (ls, git status, git log, git diff, find)\n - NEVER use Bash for any file creation or modification commands\n - Adapt your search depth based on the thoroughness level specified by the caller\n - Wherever possible, spawn multiple parallel tool calls for grepping and reading files to maximize speed\n\n If the prompt includes a <git-context> block, use it to orient yourself about the repository state before starting your investigation.\n\n You are meant to be a fast agent. Complete the search request efficiently and report your findings clearly in a structured format.\nwhenToUse: |\n Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (e.g. \"src/**/*.yaml\"), search code for keywords (e.g. \"database connection\"), or answer questions about the codebase (e.g. \"how does the auth module work?\"). When calling this agent, specify the desired thoroughness level: \"quick\" for basic searches, \"medium\" for moderate exploration, or \"thorough\" for comprehensive analysis across multiple locations and naming conventions. Use this agent for any read-only exploration that will clearly require more than 3 search queries. Prefer launching multiple explore agents concurrently when investigating independent questions.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - WebSearch\n - FetchURL\n";
80079
80121
  //#endregion
80080
- //#region ../../packages/agent-core/src/profile/default/init.md
80122
+ //#region ../../packages/agent-core/src/profile/default/init.md?raw
80081
80123
  var init_default = "You are a software engineering expert with many years of programming experience. Please explore the current project directory to understand the project's architecture and main details.\n\nTask requirements:\n1. Analyze the project structure and identify key configuration files (such as pyproject.toml, package.json, Cargo.toml, etc.).\n2. Understand the project's technology stack, build process and runtime architecture.\n3. Identify how the code is organized and main module divisions.\n4. Discover project-specific development conventions, testing strategies, and deployment processes.\n\nAfter the exploration, you should do a thorough summary of your findings and overwrite it into `AGENTS.md` file in the project root. You need to refer to what is already in the file when you do so.\n\nFor your information, `AGENTS.md` is a file intended to be read by AI coding agents. Expect the reader of this file know nothing about the project.\n\nYou should compose this file according to the actual project content. Do not make any assumptions or generalizations. Ensure the information is accurate and useful. You must use the natural language that is mainly used in the project's comments and documentation.\n\nPopular sections that people usually write in `AGENTS.md` are:\n\n- Project overview\n- Build and test commands\n- Code style guidelines\n- Testing instructions\n- Security considerations\n";
80082
80124
  //#endregion
80083
80125
  //#region ../../packages/agent-core/src/profile/default.ts
@@ -80208,7 +80250,7 @@ function formatPlainObject(record) {
80208
80250
  return Object.entries(record).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => `${fieldName(key)}: ${formatValue(value)}`).join("\n");
80209
80251
  }
80210
80252
  //#endregion
80211
- //#region ../../packages/agent-core/src/tools/background/task-list.md
80253
+ //#region ../../packages/agent-core/src/tools/background/task-list.md?raw
80212
80254
  var task_list_default = "List background tasks and their current status.\n\nUse this tool to discover which background tasks exist and where each one\nstands. It is the entry point for inspecting background work: it returns a\ntask ID, status, command, description, and PID for every task it reports,\nplus the exit code and stop reason for tasks that have already finished.\n\nGuidelines:\n\n- After a context compaction, or whenever you are unsure which background\n tasks are running or what their task IDs are, call this tool to\n re-enumerate them instead of guessing a task ID.\n- Prefer the default `active_only=true`, which lists only non-terminal tasks.\n Pass `active_only=false` only when you specifically need to see tasks that\n have already finished. With `active_only=false` the result may also include\n `lost` tasks — tasks left over from a previous process that can no longer be\n inspected or controlled; treat them as already terminated.\n- `limit` caps how many tasks are returned. It accepts a value between 1 and\n 100 and defaults to 20 when omitted.\n- This tool only lists tasks; it does not return their output. Use it first\n to locate the task ID you need, then call `TaskOutput` with that ID to read\n the task's output and details.\n- This tool is read-only and does not change any state, so it is always safe\n to call, including in plan mode.\n";
80213
80255
  //#endregion
80214
80256
  //#region ../../packages/agent-core/src/tools/background/task-list.ts
@@ -80249,7 +80291,7 @@ var TaskListTool = class {
80249
80291
  }
80250
80292
  };
80251
80293
  //#endregion
80252
- //#region ../../packages/agent-core/src/tools/background/task-output.md
80294
+ //#region ../../packages/agent-core/src/tools/background/task-output.md?raw
80253
80295
  var task_output_default = "Retrieve output from a running or completed background task.\n\nUse this after `Bash(run_in_background=true)` or `Agent(run_in_background=true)` when you need to inspect progress or explicitly wait for completion.\n\nGuidelines:\n- Prefer relying on automatic completion notifications. Use this tool only when you need task output before the automatic notification arrives.\n- By default this tool is non-blocking and returns a current status/output snapshot.\n- Use block=true only when you intentionally want to wait for completion or timeout.\n- This tool returns structured task metadata, a fixed-size output preview, and an output_path for the full log.\n- For a terminal task, the metadata also explains why it ended: `status: timed_out` when a task was aborted by its deadline, and `stop_reason` when the task was explicitly stopped. `terminal_reason` is a categorical label for the same event — its value is `timed_out` or `stopped` — and is emitted alongside the matching status / `stop_reason` field. A task that ended on its own emits neither `stop_reason` nor `terminal_reason`.\n- The full, never-truncated log is always available at output_path; use the `Read` tool with that path to page through it, whether or not the preview was truncated.\n- This tool works with the generic background task system and should remain the primary read path for future task types, not just bash.\n";
80254
80296
  //#endregion
80255
80297
  //#region ../../packages/agent-core/src/tools/background/task-output.ts
@@ -80344,7 +80386,7 @@ var TaskOutputTool = class {
80344
80386
  }
80345
80387
  };
80346
80388
  //#endregion
80347
- //#region ../../packages/agent-core/src/tools/background/task-stop.md
80389
+ //#region ../../packages/agent-core/src/tools/background/task-stop.md?raw
80348
80390
  var task_stop_default = "Stop a running background task.\n\nOnly use this when a task must genuinely be cancelled — for a task that is\nfinishing normally, wait for its completion notification or inspect it with\n`TaskOutput` instead of stopping it.\n\nGuidelines:\n- This is a general-purpose stop capability for any background task. It is not\n a bash-specific kill.\n- Stopping a task is destructive: it may leave partial side effects behind.\n Use it with care.\n- If the task has already finished, this tool simply returns its current\n status.\n";
80349
80391
  //#endregion
80350
80392
  //#region ../../packages/agent-core/src/tools/background/task-stop.ts
@@ -80418,7 +80460,7 @@ function pad(n) {
80418
80460
  return String(n).padStart(2, "0");
80419
80461
  }
80420
80462
  //#endregion
80421
- //#region ../../packages/agent-core/src/tools/cron/cron-create.md
80463
+ //#region ../../packages/agent-core/src/tools/cron/cron-create.md?raw
80422
80464
  var cron_create_default = "Schedule a prompt to be enqueued at a future time. Use for both recurring schedules and one-shot reminders.\n\nUses standard 5-field cron in the user's local timezone: minute hour day-of-month month day-of-week. `0 9 * * *` means 9am local — no timezone conversion needed.\n\n## One-shot tasks (recurring: false)\n\nFor \"remind me at X\" or \"at <time>, do Y\" requests — fire once then auto-delete.\nPin minute/hour/day-of-month/month to specific values:\n \"remind me at 2:30pm today to check the deploy\" → cron: \"30 14 <today_dom> <today_month> *\", recurring: false\n \"tomorrow morning, run the smoke test\" → cron: \"57 8 <tomorrow_dom> <tomorrow_month> *\", recurring: false\n\n## Recurring jobs (recurring: true, the default)\n\nFor \"every N minutes\" / \"every hour\" / \"weekdays at 9am\" requests:\n \"*/5 * * * *\" (every 5 min), \"0 * * * *\" (hourly), \"0 9 * * 1-5\" (weekdays at 9am local)\n\n## Avoid the :00 and :30 minute marks when the task allows it\n\nEvery user who asks for \"9am\" gets `0 9`, and every user who asks for \"hourly\" gets `0 *` — which means requests from across the planet land on the API at the same instant. When the user's request is approximate, pick a minute that is NOT 0 or 30:\n \"every morning around 9\" → \"57 8 * * *\" or \"3 9 * * *\" (not \"0 9 * * *\")\n \"hourly\" → \"7 * * * *\" (not \"0 * * * *\")\n \"in an hour or so, remind me to...\" → pick whatever minute you land on, don't round\n\nOnly use minute 0 or 30 when the user names that exact time and clearly means it (\"at 9:00 sharp\", \"at half past\", coordinating with a meeting). When in doubt, nudge a few minutes early or late — the user will not notice, and the fleet will.\n\n## Coalesce semantics\n\nIf the scheduler slept past multiple ideal fire times (laptop closed, long-running turn, etc.), only **one** fire is delivered when it wakes up. The origin carries `coalescedCount` showing how many ideal fires were collapsed into this single delivery. You should treat `coalescedCount > 1` as \"I missed some checks; only the latest state matters\" rather than running the prompt that many times.\n\n## Cron-fire envelope\n\nWhen a cron task fires, the prompt you scheduled is re-injected wrapped in an XML envelope that exposes the fire context:\n\n```\n<cron-fire jobId=\"...\" cron=\"...\" recurring=\"true|false\" coalescedCount=\"N\" stale=\"true|false\">\n<prompt>\nyour original prompt text, verbatim\n</prompt>\n</cron-fire>\n```\n\nThe envelope is parseable. Use `coalescedCount > 1` to know multiple ideal fires were collapsed into a single delivery (treat as \"only the latest state matters\"), and `stale=\"true\"` as a cue that the task is past its 7-day threshold.\n\n## 7-day stale behavior\n\nRecurring tasks that have been alive for more than 7 days fire one\nfinal time with `stale: true` on the envelope, and the system then\nauto-deletes the task. The flag is the model's notice that this is\nthe last delivery. If the schedule is still wanted, call `CronCreate`\nagain with the same `cron` and `prompt` — that resets `createdAt` and\nstarts a fresh 7-day window. One-shot tasks are never marked stale.\n\nBench / acceptance runs can set `KIMI_CRON_NO_STALE=1` to disable the\njudgment entirely.\n\n## Jitter behavior\n\nAnti-herd jitter is applied deterministically per task id:\n - Recurring: ideal fire time is shifted **forward** by an offset ≤ min(10% of the cron period, 15 minutes). A `*/5 * * * *` task can drift up to 30s; a `0 9 * * *` task can drift up to 15 minutes.\n - One-shot: only when the ideal fire lands on `:00` or `:30` of the hour, the fire is pulled **earlier** by ≤ 90 seconds. Other minutes pass through unchanged.\n\nBench / acceptance tests can set `KIMI_CRON_NO_JITTER=1` to disable jitter entirely.\n\n## One-shot vs recurring — when to pick which\n\nUse `recurring: false` for \"remind me at X\" style requests, single deadlines, \"in N minutes do Y\", and any task that should not repeat. Use `recurring: true` for periodic polling (CI status, build watchers, scheduled reports), workday rituals, and anything the user explicitly described as recurring.\n\n## Session lifetime\n\nCron tasks live in the current kimi CLI session. When you exit, they\nare persisted under the session homedir; the next `kimi resume` of the\nsame session reloads them and the scheduler resumes from each task's\n`createdAt`. Fire times that fell during the offline window are\ncollapsed into a single delivery via `coalescedCount` (and recurring\ntasks past their 7-day window arrive with `stale: true` as their final\ndelivery).\n\nTasks do **not** carry over into a brand-new session — they are scoped\nto the resumed session id, not to the working directory.\n\n## Returned fields\n\n`id` (8-hex), `humanSchedule` (English summary), `recurring`,\n`nextFireAt` (local ISO timestamp with numeric offset, or null). `id` is needed by `CronDelete`.\n\n## Tell the user how to cancel or modify\n\nAfter successfully creating a task, proactively tell the user how they can cancel or modify it later. Users have no direct `/cron` command or self-service UI to manage reminders themselves; they must ask the model to make changes (e.g. \"cancel my 9am reminder\" or \"change my daily check to 10am\"). Include the task `id` in your message so the user can reference it.\n";
80423
80465
  /**
80424
80466
  * Hard ceiling on `prompt` byte length (UTF-8). The zod `.max(...)`
@@ -80540,7 +80582,7 @@ function formatOutput(o) {
80540
80582
  ].join("\n");
80541
80583
  }
80542
80584
  //#endregion
80543
- //#region ../../packages/agent-core/src/tools/cron/cron-delete.md
80585
+ //#region ../../packages/agent-core/src/tools/cron/cron-delete.md?raw
80544
80586
  var cron_delete_default = "Cancel a scheduled cron job by id.\n\nUse this tool to remove a cron task previously scheduled with\n`CronCreate`. The `id` is the 8-hex value returned by `CronCreate`, or\nshown in the `id:` column of `CronList` — quote it verbatim, no\nprefix.\n\nBehaviour by task kind:\n\n- **Recurring task** (`recurring: true`): stops all future fires\n immediately. The scheduler picks up the deletion on its next tick.\n- **One-shot task** (`recurring: false`): cancels the pending fire if\n it has not happened yet. One-shots that have already fired\n auto-delete themselves, so calling `CronDelete` on a fired one-shot\n returns \"no cron job with id ...\".\n\nNot-found is reported as an error (not a silent no-op) so you can\ncorrect yourself — typically by calling `CronList` to see which ids\nare actually live, rather than re-trying with the same stale id.\n\nRefresh pattern (use when you want a stale recurring schedule to\ncontinue):\n\nStale recurring tasks are auto-deleted by the system after their final\nfire — there is nothing for `CronDelete` to remove at that point. To\nkeep the schedule running, just call `CronCreate` with the same `cron`\nand `prompt`. Use `CronList`'s `prompt` field to recall the original\ntext after a context compaction.\n\n`CronDelete` remains the right call when you want to cancel a task\nthat is still live (recurring not yet stale, or a one-shot still\npending).\n\nGuidelines:\n\n- Users have no direct `/cron` command or self-service UI to delete\n tasks themselves; they must ask the model to cancel a reminder.\n When deleting on behalf of a user, confirm the action and report\n the result plainly.\n- Cron deletion is irreversible — there is no undo. If you delete the\n wrong task, you must re-create it with `CronCreate`.\n- If the model is unsure which id is current (e.g. after a context\n compaction), call `CronList` first rather than guessing.\n";
80545
80587
  //#endregion
80546
80588
  //#region ../../packages/agent-core/src/tools/cron/cron-delete.ts
@@ -80621,7 +80663,7 @@ var CronDeleteTool = class {
80621
80663
  }
80622
80664
  };
80623
80665
  //#endregion
80624
- //#region ../../packages/agent-core/src/tools/cron/cron-list.md
80666
+ //#region ../../packages/agent-core/src/tools/cron/cron-list.md?raw
80625
80667
  var cron_list_default = "List all cron jobs currently scheduled in this session.\n\nUse this tool to see every pending cron task — both recurring jobs and\none-shot reminders — that you (or the user) have scheduled with\n`CronCreate`. The output is the entry point for inspecting scheduled\nwork: it returns a stable id, the original cron expression, a human\nrendering, the next post-jitter fire time, the recurring flag, the\ntask's age in days, and a stale indicator.\n\nEach record carries:\n\n- `id` — the 8-hex task id. Pass this to `CronDelete` to remove the\n task, or quote it in user-facing messages when asking for\n confirmation.\n- `cron` — the verbatim 5-field cron expression as scheduled.\n- `humanSchedule` — plain-English rendering (e.g. `every 5 minutes`).\n- `prompt` — the scheduled prompt text, JSON-encoded so embedded\n newlines stay on one line. Truncated to 200 UTF-8 bytes with\n `…(truncated)` if longer. Use this to recall what a task is for\n after a context compaction, and as the source for the\n `CronCreate` refresh ritual.\n- `nextFireAt` — local ISO timestamp with an explicit numeric offset\n for the next fire **after jitter has been applied**. The actual fire\n may land slightly before or after a round `:00` / `:30` minute mark\n due to herd-avoidance jitter; this is the value the scheduler will\n compare against, so it reflects what will really happen. `null` if\n the expression has no fire in the next 5 years (should not happen\n for tasks created through `CronCreate`, which validates).\n- `recurring` — `true` for cadenced jobs, `false` for one-shots.\n- `ageDays` — `(now - createdAt) / day`, two decimal places. Useful\n when deciding whether a long-running cron is still relevant.\n- `stale` — `true` when a recurring task is older than 7 days. The\n system **auto-deletes the task after this fire** to bound session\n lifetime; the `stale: true` flag is the model's notice that this is\n the final delivery. To resume the same schedule, call `CronCreate`\n again with the original `cron` and `prompt` (the `prompt` row above\n carries it for exactly this purpose). One-shots are never marked\n stale — they fire at most once by construction.\n `KIMI_CRON_NO_STALE=1` disables the judgment entirely (bench /\n acceptance runs).\n\nGuidelines:\n\n- This tool is read-only and never mutates state, so it is always\n safe to call (including in plan mode).\n- Users cannot directly manage cron tasks themselves; if they want to\n cancel or modify a schedule, route the request through the model\n (i.e. call `CronDelete` or `CronCreate` on their behalf).\n- The empty case returns `cron_jobs: 0\\nNo cron jobs scheduled.`. Cron\n tasks survive a `kimi resume` of the same session but do not bleed\n into new sessions.\n- After a context compaction, or whenever you are unsure which cron\n jobs are live, call this tool to re-enumerate them rather than\n guessing ids from earlier in the conversation.\n- Records are separated by a line containing just `---`, in the\n insertion order they were scheduled.\n";
80626
80668
  //#endregion
80627
80669
  //#region ../../packages/agent-core/src/tools/cron/cron-list.ts
@@ -81277,7 +81319,7 @@ var SubagentBatch = class {
81277
81319
  }
81278
81320
  };
81279
81321
  //#endregion
81280
- //#region ../../packages/agent-core/src/session/summary-continuation.md
81322
+ //#region ../../packages/agent-core/src/session/summary-continuation.md?raw
81281
81323
  var summary_continuation_default = "Your previous response was too brief. Please provide a more comprehensive summary that includes:\n\n1. Specific technical details and implementations\n2. Detailed findings and analysis\n3. All important information that the parent agent should know";
81282
81324
  //#endregion
81283
81325
  //#region ../../packages/agent-core/src/session/subagent-host.ts
@@ -81611,13 +81653,13 @@ function shouldSuppressQueuedAttemptFailureEvent(options, error) {
81611
81653
  return isAbortError$3(error) || options.signal.aborted;
81612
81654
  }
81613
81655
  //#endregion
81614
- //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-background-disabled.md
81656
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-background-disabled.md?raw
81615
81657
  var agent_background_disabled_default = "Background agent execution is disabled for this agent. Do not set `run_in_background=true`.";
81616
81658
  //#endregion
81617
- //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-background-enabled.md
81659
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-background-enabled.md?raw
81618
81660
  var agent_background_enabled_default = "When `run_in_background=true`, the subagent runs detached from this turn. The completion arrives in a later turn as a synthetic user-role message containing its result — you do not need to poll, sleep, or check on its progress. Continue with other work or respond to the user. Never fabricate or predict what the result will say.\n";
81619
81661
  //#endregion
81620
- //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent.md
81662
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent.md?raw
81621
81663
  var agent_default = "Launch a subagent to handle a task. The subagent runs as a same-process loop instance with its own context and wire file.\n\nWriting the prompt:\n- The subagent starts with zero context — it has not seen this conversation. Brief it like a colleague who just walked into the room: state the goal, list what you already know, hand over the specifics.\n- Lookups (read this file, run that test): put the exact path or command in the prompt. The subagent should not have to search for things you already know.\n- Investigations (figure out X, find why Y): give the question, not prescribed steps — fixed steps become dead weight when the premise is wrong.\n- Do not delegate understanding. If the task hinges on a file path or line number, find it yourself first and write it into the prompt.\n\nUsage notes:\n- When the task continues earlier work a subagent already did, prefer resuming that agent (pass its `resume` id) over spawning a fresh instance — the resumed agent keeps its prior context.\n- A subagent's result is only visible to you, not to the user. When the user needs to see what a subagent produced, summarize the relevant parts yourself in your own reply.\n- Subagents use a fixed 30-minute timeout. If one times out, resume the same agent instead of starting over.\n\nWhen NOT to use Agent: skip delegation for trivial work you can do directly — reading a file whose path you already know, searching a small known set of files, or any task that takes only a step or two. Delegation has a context-handoff cost; it pays off only when the task is substantial enough to outweigh it.\n\nOnce a subagent is running, leave that scope to it: do not redo its searches or reads in parallel, and do not abandon it midway and finish the job manually. Both undo the context savings the delegation was meant to buy.\n";
81622
81664
  //#endregion
81623
81665
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent.ts
@@ -81838,7 +81880,7 @@ function buildSubagentDescriptions(subagents) {
81838
81880
  }).join("\n");
81839
81881
  }
81840
81882
  //#endregion
81841
- //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-swarm.md
81883
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-swarm.md?raw
81842
81884
  var agent_swarm_default = "Launch multiple subagents from one prompt template, existing agent resumes, or both.\n\nUse AgentSwarm when many subagents should run the same kind of task over different inputs. The placeholder is exactly `{{item}}`. For example, with `prompt_template` set to `Review {{item}} for likely regressions.` and `items` set to `[\"src/a.ts\", \"src/b.ts\"]`, AgentSwarm launches two new subagents with those two concrete prompts.\n\nUse `resume_agent_ids` to continue subagents that already exist from earlier work, such as ones that failed or timed out: map each agent id to the prompt for that resumed subagent (usually `continue` if no extra information is needed). You may combine `resume_agent_ids` with `items` in the same call to resume existing subagents and launch new ones. Do not duplicate resumed work in `items`.\n\nUse enough subagents to keep the work focused and parallel. AgentSwarm supports up to 128 subagents, and launches are queued automatically, so it is safe to split large tasks into many clear, independent items.\n\nIf `AgentSwarm` is called, that call must be the only tool call in the response.\n";
81843
81885
  //#endregion
81844
81886
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-swarm.ts
@@ -81999,7 +82041,7 @@ function escapeXmlAttribute(value) {
81999
82041
  return value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
82000
82042
  }
82001
82043
  //#endregion
82002
- //#region ../../packages/agent-core/src/tools/builtin/collaboration/ask-user.md
82044
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/ask-user.md?raw
82003
82045
  var ask_user_default = "Use this tool when you need to ask the user questions with structured options during execution. This allows you to:\n1. Collect user preferences or requirements before proceeding\n2. Resolve ambiguous or underspecified instructions\n3. Let the user decide between implementation approaches as you work\n4. Present concrete options when multiple valid directions exist\n\n**When NOT to use:**\n- When you can infer the answer from context — be decisive and proceed\n- Trivial decisions that don't materially affect the outcome\n\nOverusing this tool interrupts the user's flow. Only use it when the user's input genuinely changes your next action.\n\n**Usage notes:**\n- Users always have an \"Other\" option for custom input — don't create one yourself\n- Use multi_select to allow multiple answers to be selected for a question\n- Keep option labels concise (1-5 words), use descriptions for trade-offs and details\n- Each question should have 2-4 meaningful, distinct options\n- You can ask 1-4 questions at a time; group related questions to minimize interruptions\n- If you recommend a specific option, list it first and append \"(Recommended)\" to its label\n";
82004
82046
  //#endregion
82005
82047
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/ask-user.ts
@@ -82161,7 +82203,7 @@ function isQuestionResponse(result) {
82161
82203
  return typeof answers === "object" && answers !== null && !Array.isArray(answers);
82162
82204
  }
82163
82205
  //#endregion
82164
- //#region ../../packages/agent-core/src/tools/builtin/collaboration/skill-tool.md
82206
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/skill-tool.md?raw
82165
82207
  var skill_tool_default = "Invoke a registered skill from the current skill listing. BLOCKING REQUIREMENT: when a skill from the listing matches the user's request, you MUST call this tool (not free-form text). Do NOT call the same skill repeatedly inside one turn — recursive depth is capped at {{ MAX_SKILL_QUERY_DEPTH }}.";
82166
82208
  var NestedSkillTooDeepError = class extends Error {
82167
82209
  skillName;
@@ -82289,7 +82331,7 @@ function makeCarriageReturnsVisible(text) {
82289
82331
  return text.replaceAll("\r", "\\r");
82290
82332
  }
82291
82333
  //#endregion
82292
- //#region ../../packages/agent-core/src/tools/builtin/file/edit.md
82334
+ //#region ../../packages/agent-core/src/tools/builtin/file/edit.md?raw
82293
82335
  var edit_default = "Perform exact replacements in existing files.\n\n- Edit is mandatory for every incremental change, especially small edits. DO NOT use Write or Bash `sed`.\n- Read the target file before every Edit. DO NOT call Edit from memory, stale context, or a guessed `old_string`.\n- Take `old_string` and `new_string` from the Read output view.\n- Drop the line-number prefix and tab; match only file content.\n- `old_string` must be unique unless `replace_all` is set.\n- If `old_string` is ambiguous, add surrounding context. Use `replace_all` only when every occurrence should change.\n- Multiple Edit calls may run in one response only when they do not target the same file.\n- DO NOT issue consecutive Edit calls on the same file. A previous Edit can invalidate a later Edit's `old_string`, causing `old_string not found`. Read the file again before the next Edit.\n- A write lock serializes same-file edits in response order, but serialization does not make stale `old_string` valid.\n- For pure CRLF files, Read shows LF; use LF in `old_string` and `new_string`, and Edit writes CRLF back.\n- For mixed endings or lone carriage returns, Read shows carriage returns as \\r; include actual \\r escapes in those positions.\n";
82294
82336
  //#endregion
82295
82337
  //#region ../../packages/agent-core/src/tools/builtin/file/edit.ts
@@ -82392,7 +82434,7 @@ var EditTool = class {
82392
82434
  }
82393
82435
  };
82394
82436
  //#endregion
82395
- //#region ../../packages/agent-core/src/tools/builtin/file/glob.md
82437
+ //#region ../../packages/agent-core/src/tools/builtin/file/glob.md?raw
82396
82438
  var glob_default = "Find files (and optionally directories) by glob pattern, sorted by modification time (most recent first).\n\nGood patterns:\n- `*.ts` — files in the current directory matching an extension\n- `src/**/*.ts` — recursive walk with a subdirectory anchor and extension\n- `**/*.py` — recursive walk from the search root for an extension\n- `*.{ts,tsx}` — brace expansion is supported; expanded into `*.ts` and `*.tsx` before walking\n- `{src,test}/**/*.ts` — cartesian brace expansion is supported too\n\nResults are capped at the first 100 matching paths (walk order, not global modification-time order). If a search would return more, a truncation marker is appended with the count of matches seen so far. Refine the pattern (extension, subdirectory) when 100 is not enough, or call again with a narrower anchor.\n\nLarge-directory caveat — avoid recursing into dependency / build output even with an anchor:\n- `node_modules/**/*.js`, `.venv/**/*.py`, `__pycache__/**`, `target/**` all match technically but\n typically produce thousands of results that truncate at the match cap and waste the caller context.\n Prefer specific subpaths like `node_modules/react/src/**/*.js`.\n";
82397
82439
  //#endregion
82398
82440
  //#region ../../packages/agent-core/src/tools/builtin/file/glob.ts
@@ -87119,7 +87161,7 @@ var ToolResultBuilder = class {
87119
87161
  }
87120
87162
  };
87121
87163
  //#endregion
87122
- //#region ../../packages/agent-core/src/tools/builtin/file/grep.md
87164
+ //#region ../../packages/agent-core/src/tools/builtin/file/grep.md?raw
87123
87165
  var grep_default = "Search file contents using regular expressions (powered by ripgrep).\n\nUse Grep when the task is to find unknown content or unknown file locations. Do not use shell `grep` or `rg` directly; this tool applies workspace path policy, output limits, and sensitive-file filtering.\nALWAYS use Grep tool instead of running `grep` or `rg` from a shell — direct shell calls bypass workspace policy, output limits, and sensitive-file filtering.\nIf you already know a concrete file path and need to inspect its contents, use Read directly instead.\n\nWrite patterns in ripgrep regex syntax, which differs from POSIX `grep` syntax. For example, braces are special, so escape them as `\\{` to match a literal `{`.\n\nHidden files (dotfiles such as `.gitlab-ci.yml` or `.eslintrc.json`) are searched by default. To also search files excluded by `.gitignore` (such as `node_modules` or build outputs), set `include_ignored` to `true`. Sensitive files (such as `.env`) are always skipped for safety, even when `include_ignored` is `true`.\n";
87124
87166
  //#endregion
87125
87167
  //#region ../../packages/agent-core/src/tools/builtin/file/grep.ts
@@ -88107,7 +88149,7 @@ function detectFileType(path, header) {
88107
88149
  };
88108
88150
  }
88109
88151
  //#endregion
88110
- //#region ../../packages/agent-core/src/tools/builtin/file/read.md
88152
+ //#region ../../packages/agent-core/src/tools/builtin/file/read.md?raw
88111
88153
  var read_default = "Read a text file from the local filesystem.\n\nIf the user provides a concrete file path to a text file, call Read directly. Do not `Glob`, `ls`, or otherwise pre-check known text file paths; missing or invalid file paths return errors you can handle. Do not use Read for directories; use `ls` via Bash for a known directory, or Glob when you need files/directories matching a pattern. Use `Grep` only when the task is to search for unknown content or locations.\n\nWhen you need several files, prefer to read them in parallel: emit multiple `Read` calls in a single response instead of reading one file per turn.\n\n- Relative paths resolve against the working directory; a path outside the working directory must be absolute.\n- Returns up to {{ MAX_LINES }} lines or {{ MAX_BYTES_KB }} KB per call, whichever comes first; lines longer than {{ MAX_LINE_LENGTH }} chars are truncated mid-line.\n- Page larger files with `line_offset` (1-based start line) and `n_lines`. Omit `n_lines` to read up to the {{ MAX_LINES }}-line cap.\n- Sensitive files (`.env` files, credential stores, SSH keys, and similar secrets) are refused to protect secrets; do not attempt to read them.\n- Only UTF-8 text files can be read. Non-UTF-8 encodings, binary files, and files containing NUL bytes are refused; use `ReadMediaFile` for images or video, and Bash or an MCP tool for other binary formats.\n- Negative line_offset reads from the end of the file (for example, -100 reads the last 100 lines); the absolute value cannot exceed {{ MAX_LINES }}.\n- Output format: `<line-number>\\t<content>` per line.\n- A `<system>...</system>` status block is appended after the file content; it summarizes how much was read (line and byte counts, truncation, line-ending notes) and is not part of the file itself.\n- Pure CRLF files are displayed with LF line endings; `Edit` matches this output and preserves CRLF when writing back.\n- Mixed or lone carriage-return line endings are shown as `\\r` and require exact `Edit.old_string` escapes.\n- After a successful `Edit`/`Write`, do not re-read solely to prove the write landed. When the task depends on an exact file, API, or output shape, inspect the final external contract before finishing.\n";
88112
88154
  //#endregion
88113
88155
  //#region ../../packages/agent-core/src/tools/builtin/file/read.ts
@@ -88414,7 +88456,7 @@ var ReadTool = class {
88414
88456
  }
88415
88457
  };
88416
88458
  //#endregion
88417
- //#region ../../packages/agent-core/src/tools/builtin/file/read-media.md
88459
+ //#region ../../packages/agent-core/src/tools/builtin/file/read-media.md?raw
88418
88460
  var read_media_default = "Read media content from a file.\n\n**Tips:**\n- Make sure you follow the description of each tool parameter.\n- A `<system>` tag is given before the file content; it summarizes the mime type, byte size and, for images, the original pixel dimensions. When outputting coordinates, give relative coordinates first and compute absolute coordinates from the original image size. After generating or editing media via commands or scripts, read the result back before continuing.\n- The system will notify you when there is anything wrong when reading the file.\n- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible.\n- This tool can only read image or video files. To read text files, use the Read tool. To list directories, use `ls` via Bash for a known directory, or Glob for pattern search.\n- If the file doesn't exist or path is invalid, an error will be returned.\n- The maximum size that can be read is {{ MAX_MEDIA_MEGABYTES }}MB. An error will be returned if the file is larger than this limit.\n- The media content will be returned in a form that you can directly view and understand.\n\n**Capabilities**";
88419
88461
  //#endregion
88420
88462
  //#region ../../packages/agent-core/src/tools/builtin/file/read-media.ts
@@ -88576,7 +88618,7 @@ var ReadMediaFileTool = class {
88576
88618
  }
88577
88619
  };
88578
88620
  //#endregion
88579
- //#region ../../packages/agent-core/src/tools/builtin/file/write.md
88621
+ //#region ../../packages/agent-core/src/tools/builtin/file/write.md?raw
88580
88622
  var write_default = "Create, append to, or replace a file entirely.\n\n- The parent directory must already exist.\n- Mode defaults to overwrite; append adds content at EOF without adding a newline.\n- Write is NOT ALLOWED for incremental changes to existing files, including trivial, one-line, quick, or cosmetic edits. Use Edit instead.\n- Use Write only when the file does not exist, you intend a complete replacement, or the new contents have little continuity with the old contents.\n- Read before overwriting an existing file.\n- Write ignores the Read/Edit line-number view. NEVER include line prefixes.\n- Write outputs content literally, including supplied line endings: \\n stays LF, \\r\\n stays CRLF.\n- For new content too large for one call, overwrite the first chunk, then append subsequent chunks. Never chunk Write to modify an existing file.\n";
88581
88623
  //#endregion
88582
88624
  //#region ../../packages/agent-core/src/tools/builtin/file/write.ts
@@ -88672,7 +88714,7 @@ var WriteTool = class {
88672
88714
  }
88673
88715
  };
88674
88716
  //#endregion
88675
- //#region ../../packages/agent-core/src/tools/builtin/goal/create-goal.md
88717
+ //#region ../../packages/agent-core/src/tools/builtin/goal/create-goal.md?raw
88676
88718
  var create_goal_default = "Create a durable, structured goal that the runtime will pursue across multiple turns.\n\nCall `CreateGoal` only when:\n\n- the user explicitly asks you to start a goal or work autonomously toward an outcome, or\n- a host goal-intake prompt asks you to create one.\n\nDo NOT create a goal for greetings, ordinary questions, or vague requests that lack a\nverifiable completion condition. A goal needs a checkable end state.\n\nWhen the request is vague, ask the user for the missing completion criterion before creating\nthe goal. If the user clearly insists after you warn them that the wording is vague or risky,\nrespect that and create the goal.\n\nInclude a `completionCriterion` when the user provides one, or when it can be stated without\ninventing new requirements. Keep `objective` concise; reference long task descriptions by file\npath rather than pasting them.\n\nUse `replace: true` only when the user explicitly wants to abandon the current goal and start a\nnew one.\n";
88677
88719
  //#endregion
88678
88720
  //#region ../../packages/agent-core/src/tools/builtin/goal/create-goal.ts
@@ -88706,7 +88748,7 @@ var CreateGoalTool = class {
88706
88748
  }
88707
88749
  };
88708
88750
  //#endregion
88709
- //#region ../../packages/agent-core/src/tools/builtin/goal/get-goal.md
88751
+ //#region ../../packages/agent-core/src/tools/builtin/goal/get-goal.md?raw
88710
88752
  var get_goal_default = "Read the current goal: its objective, completion criterion, status, budgets (turns, tokens,\ntime, and how much remains), the latest self-report, and the latest evaluator verdict.\n\nUse `GetGoal` before deciding whether to continue working, report completion, report a blocker,\nor respect a pause. It returns `{ \"goal\": null }` when there is no current goal.\n";
88711
88753
  //#endregion
88712
88754
  //#region ../../packages/agent-core/src/tools/builtin/goal/get-goal.ts
@@ -88732,7 +88774,7 @@ var GetGoalTool = class {
88732
88774
  }
88733
88775
  };
88734
88776
  //#endregion
88735
- //#region ../../packages/agent-core/src/tools/builtin/goal/set-goal-budget.md
88777
+ //#region ../../packages/agent-core/src/tools/builtin/goal/set-goal-budget.md?raw
88736
88778
  var set_goal_budget_default = "Set a hard budget limit for the current goal.\n\nUse this only when the user clearly gives a runtime limit, such as:\n\n- \"stop after 20 turns\"\n- \"use no more than 500k tokens\"\n- \"finish within 30 minutes\"\n\nDo not invent limits. Do not call this for vague wording such as \"spend some time\" or\n\"try to be quick\".\n\nIf the user gives a compound time, convert it to one supported unit before calling this tool.\nFor example, \"2 hours and 3 minutes\" can be set as `value: 123, unit: \"minutes\"`.\n\nIf the requested budget is not reasonable, do not set it. Tell the user that the requested\nbudget is not reasonable. Examples include a time budget that is too short to act on, such as\n1 millisecond, or too long for an interactive goal run, such as 1 year.\n\nSupported units:\n\n- `turns`\n- `tokens`\n- `milliseconds`\n- `seconds`\n- `minutes`\n- `hours`\n";
88737
88779
  //#endregion
88738
88780
  //#region ../../packages/agent-core/src/tools/builtin/goal/set-goal-budget.ts
@@ -89920,7 +89962,7 @@ function formatTokens$3(tokens) {
89920
89962
  return `${(tokens / 1e6).toFixed(1)}M`;
89921
89963
  }
89922
89964
  //#endregion
89923
- //#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.md
89965
+ //#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.md?raw
89924
89966
  var update_goal_default = "Set the status of the current goal. This is how you resume, end, or yield an autonomous goal.\n\n- `active` — resume a paused or blocked goal when the user explicitly asks you to work on that goal.\n- `complete` — the objective is satisfied and any stated validation has passed. The goal ends and a completion summary is recorded.\n- `blocked` — an external condition or required user input prevents progress, or the objective cannot be completed as stated. The goal stops but can be resumed later.\n- `paused` — set the goal aside for now (e.g. to hand control back to the user). It can be resumed later.\n\nIf the goal is active and you do not call this, the goal keeps running: after your turn ends you will be prompted to continue. Call `complete` only when all required work is done, any stated validation has passed, and there is no useful next action. Do not call `complete` after only producing a plan, summary, first pass, or partial result. If you call `blocked`, you will be prompted to explain the blocker in your next message. This tool only records the status.\n";
89925
89967
  //#endregion
89926
89968
  //#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.ts
@@ -89981,7 +90023,7 @@ var UpdateGoalTool = class {
89981
90023
  }
89982
90024
  };
89983
90025
  //#endregion
89984
- //#region ../../packages/agent-core/src/tools/builtin/planning/enter-plan-mode.md
90026
+ //#region ../../packages/agent-core/src/tools/builtin/planning/enter-plan-mode.md?raw
89985
90027
  var enter_plan_mode_default = "Use this tool proactively when you're about to start a non-trivial implementation task.\nGetting user sign-off on your approach via ExitPlanMode before writing code prevents wasted effort.\n\nUse it when ANY of these conditions apply:\n\n1. New Feature Implementation - e.g. \"Add a caching layer to the API\"\n2. Multiple Valid Approaches - e.g. \"Optimize database queries\" (indexing vs rewrite vs caching)\n3. Code Modifications - e.g. \"Refactor auth module to support OAuth\"\n4. Architectural Decisions - e.g. \"Add WebSocket support\"\n5. Multi-File Changes - involves more than 2-3 files\n6. Unclear Requirements - need exploration to understand scope\n7. User Preferences Matter - if user input would materially change the implementation approach, use EnterPlanMode to structure the decision\n\nPermission mode notes:\n- EnterPlanMode enters plan mode automatically without an approval prompt in all permission modes.\n- In yolo and manual modes, ExitPlanMode still presents the plan to the user for approval.\n- In auto permission mode, do not use AskUserQuestion; make the best decision from available context.\n- In auto permission mode, ExitPlanMode exits plan mode without asking the user.\n- Use EnterPlanMode only when planning itself adds value.\n\nWhen NOT to use:\n- Single-line or few-line fixes (typos, obvious bugs, small tweaks)\n- User gave very specific, detailed instructions\n- Pure research/exploration tasks\n\n## What Happens in Plan Mode\nIn plan mode, you will:\n1. Identify 2-3 key questions about the codebase that are critical to your plan. If you are not confident about the codebase structure or relevant code paths, use `Agent(subagent_type=\"explore\")` to investigate these questions first - this is strongly recommended for non-trivial tasks.\n2. Explore the codebase using Glob, Grep, Read, and other read-only tools for any remaining quick lookups. Use Bash only when needed; Bash follows the normal permission mode and rules.\n3. Design an implementation approach based on your findings\n4. Write your plan to the current plan file with Write or Edit\n5. Present your plan to the user via ExitPlanMode for approval\n";
89986
90028
  //#endregion
89987
90029
  //#region ../../packages/agent-core/src/tools/builtin/planning/enter-plan-mode.ts
@@ -90043,7 +90085,7 @@ function enteredPlanModeMessage(planPath) {
90043
90085
  ].join("\n");
90044
90086
  }
90045
90087
  //#endregion
90046
- //#region ../../packages/agent-core/src/tools/builtin/planning/exit-plan-mode.md
90088
+ //#region ../../packages/agent-core/src/tools/builtin/planning/exit-plan-mode.md?raw
90047
90089
  var exit_plan_mode_default = "Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval.\n\n## How This Tool Works\n- You should have already written your plan to the plan file specified in the plan mode reminder.\n- This tool does NOT take the plan content as a parameter - it reads the plan from the file you wrote.\n- The user will see the contents of your plan file when they review it. In auto permission mode, the tool reads the file and exits plan mode without asking the user.\n\n## When to Use\nOnly use this tool for tasks that require planning implementation steps. For research tasks (searching files, reading code, understanding the codebase), do NOT use this tool.\n\n## Multiple Approaches\nIf your plan contains multiple alternative approaches:\n- Pass them via the `options` parameter so the user can choose which approach to execute.\n- Each option should have a concise label and a brief description of trade-offs.\n- If you recommend one option, append \"(Recommended)\" to its label.\n- In yolo and manual modes, the user will see all options alongside Reject and Revise choices.\n- Provide up to 3 options; the host adds the standard rejection and revision controls. When the plan offers a real choice, 2-3 distinct approaches work best.\n- Passing a single option is allowed and is equivalent to a plain plan approval (no approach choice is surfaced to the user).\n- Do NOT use \"Reject\", \"Reject and Exit\", \"Revise\", or \"Approve\" as option labels - these are reserved by the system.\n\n## Before Using\n- In auto permission mode, do NOT use AskUserQuestion; make the best decision from available context.\n- In auto permission mode, this tool exits plan mode without asking the user.\n- In yolo and manual modes, this tool still presents the plan to the user for approval.\n- If auto permission mode is not active and you have unresolved questions, use AskUserQuestion first.\n- If auto permission mode is not active and you have multiple approaches and haven't narrowed down yet, consider using AskUserQuestion first to let the user choose, then write a plan for the chosen approach only.\n- Once your plan is finalized, use THIS tool to request approval.\n- Do NOT use AskUserQuestion to ask \"Is this plan OK?\" or \"Should I proceed?\" - that is exactly what ExitPlanMode does.\n- If rejected, revise based on feedback and call ExitPlanMode again.\n";
90048
90090
  //#endregion
90049
90091
  //#region ../../packages/agent-core/src/tools/builtin/planning/exit-plan-mode.ts
@@ -90168,7 +90210,7 @@ function formatPlanForOutput(plan, path) {
90168
90210
  return `Plan mode deactivated. All tools are now available.\n${path !== void 0 ? `Plan saved to: ${path}\n\n` : ""}## Approved Plan:\n${plan}`;
90169
90211
  }
90170
90212
  //#endregion
90171
- //#region ../../packages/agent-core/src/tools/builtin/shell/bash.md
90213
+ //#region ../../packages/agent-core/src/tools/builtin/shell/bash.md?raw
90172
90214
  var bash_default = "Execute a `{{ SHELL_NAME }}` command. Use this for shell semantics — pipes, env, processes, git, package managers, build/test runners, anything genuinely interactive or multi-step.\n\n**Translate these to a dedicated tool instead:**\n- `cat` / `head` / `tail` (known path) → `Read`\n- `sed` / `awk` (in-place edit) → `Edit`\n- `echo > file` / `cat <<EOF` → `Write`\n- `find` / recursive `ls` to locate files by name pattern → `Glob` (plain `ls <known-directory>` is fine for listing a directory)\n- `grep` / `rg` (search file contents) → `Grep`\n- `echo` / `printf` (talk to the user) → just output text directly\n\nThe dedicated tools render in the per-tool permission UI and keep raw stdout out of the conversation; that is why they are worth reaching for whenever one fits.\n\n**Output:**\nThe stdout and stderr will be combined and returned as a string. The output may be truncated if it is too long. If the command failed, the output will end with a `Command failed with exit code: N` line stating the non-zero exit code.\n\nIf `run_in_background=true`, the command will be started as a background task and this tool will return a task ID instead of waiting for command completion. When doing that, you must provide a short `description`. Background commands default to a {{ DEFAULT_BACKGROUND_TIMEOUT_S }}s timeout and `timeout` is capped at {{ MAX_BACKGROUND_TIMEOUT_S }}s; set `disable_timeout=true` only when the task should run without a timeout. You will be automatically notified when the task completes. Use `TaskOutput` for a non-blocking status/output snapshot, and only set `block=true` when you explicitly want to wait for completion. Use `TaskStop` only if the task must be cancelled. If a human user wants to inspect background tasks themselves, point them to the `/tasks` command, which opens an interactive panel; it has no subcommands.\n\n**Guidelines for safety and security:**\n- Each shell tool call will be executed in a fresh shell environment. The shell variables, current working directory changes, and the shell history is not preserved between calls.\n- The tool call will return after the command is finished. You shall not use this tool to execute an interactive command or a command that may run forever. For possibly long-running foreground commands, set the `timeout` argument in seconds. Foreground commands default to {{ DEFAULT_TIMEOUT_S }}s and allow up to {{ MAX_TIMEOUT_S }}s.\n- Avoid using `..` to access files or directories outside of the working directory.\n- Avoid modifying files outside of the working directory unless explicitly instructed to do so.\n- Never run commands that require superuser privileges unless explicitly instructed to do so.\n\n**Guidelines for efficiency:**\n- For multiple related commands, use `&&` to chain them in a single call, e.g. `cd /path && ls -la`\n- Use `;` to run commands sequentially regardless of success/failure\n- Use `||` for conditional execution (run second command only if first fails)\n- Use pipe operations (`|`) and redirections (`>`, `>>`) to chain input and output between commands\n- Always quote file paths containing spaces with double quotes (e.g., cd \"/path with spaces/\")\n- Compose multi-step logic in a single call with `if` / `case` / `for` / `while` control flows.\n- Prefer `run_in_background=true` for long-running builds, tests, watchers, or servers when you need the conversation to continue before the command finishes.\n\n**Commands available:**\nThe following common command categories are usually available. Availability still depends on the host, so when in doubt run `which <command>` first to confirm a command exists before relying on it.\n- Navigation and inspection: `ls`, `pwd`, `cd`, `stat`, `file`, `du`, `df`, `tree`\n- File and directory management: `cp`, `mv`, `rm`, `mkdir`, `touch`, `ln`, `chmod`, `chown`\n- Text and data processing: `wc`, `sort`, `uniq`, `cut`, `tr`, `diff`, `xargs`\n- Archives and compression: `tar`, `gzip`, `gunzip`, `zip`, `unzip`\n- Networking and transfer: `curl`, `wget`, `ping`, `ssh`, `scp`\n- Version control: `git`\n- Process and system: `ps`, `kill`, `top`, `env`, `date`, `uname`, `whoami`\n- Language and package toolchains: `node`, `npm`, `pnpm`, `yarn`, `python`, `pip` (use whichever the project actually relies on)\n";
90173
90215
  //#endregion
90174
90216
  //#region ../../packages/agent-core/src/tools/builtin/shell/bash.ts
@@ -90257,7 +90299,7 @@ var BashTool = class {
90257
90299
  },
90258
90300
  approvalRule: literalRulePattern(this.name, args.command),
90259
90301
  matchesRule: (ruleArgs) => matchesGlobRuleSubject(ruleArgs, args.command),
90260
- execute: ({ signal }) => this.execution(args, signal)
90302
+ execute: ({ signal, onUpdate }) => this.execution(args, signal, onUpdate)
90261
90303
  };
90262
90304
  }
90263
90305
  spawn(effectiveCwd, command) {
@@ -90279,7 +90321,7 @@ var BashTool = class {
90279
90321
  };
90280
90322
  return this.kaos.execWithEnv(shellArgs, mergedEnv);
90281
90323
  }
90282
- async execution(args, signal) {
90324
+ async execution(args, signal, onUpdate) {
90283
90325
  if (signal.aborted) return {
90284
90326
  isError: true,
90285
90327
  output: "Aborted before command started"
@@ -90346,7 +90388,7 @@ var BashTool = class {
90346
90388
  try {
90347
90389
  const builder = new ToolResultBuilder();
90348
90390
  const isTerminating = () => timedOut || aborted || killed;
90349
- const [, exitCode] = await Promise.all([Promise.all([readStreamIntoBuilder(proc.stdout, builder, isTerminating), readStreamIntoBuilder(proc.stderr, builder, isTerminating)]), proc.wait()]);
90391
+ const [, exitCode] = await Promise.all([Promise.all([readStreamIntoBuilder(proc.stdout, builder, "stdout", onUpdate, isTerminating), readStreamIntoBuilder(proc.stderr, builder, "stderr", onUpdate, isTerminating)]), proc.wait()]);
90350
90392
  if (timedOut) {
90351
90393
  const timeoutLabel = timeoutMs % 1e3 === 0 ? `${String(timeoutMs / 1e3)}s` : `${String(timeoutMs)}ms`;
90352
90394
  return builder.error(`Command killed by timeout (${timeoutLabel})`, { brief: `Killed by timeout (${timeoutLabel})` });
@@ -90419,17 +90461,27 @@ human_shell_hint: Tell the human to run /tasks to open the interactive backgroun
90419
90461
  return builder.ok("Background task started", { brief: `Started ${taskId}` });
90420
90462
  }
90421
90463
  };
90422
- async function readStreamIntoBuilder(stream, builder, suppressPrematureClose) {
90464
+ async function readStreamIntoBuilder(stream, builder, kind, onUpdate, suppressPrematureClose) {
90423
90465
  const decoder = new StringDecoder("utf8");
90424
90466
  try {
90425
90467
  for await (const chunk of stream) {
90426
90468
  const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
90427
- builder.write(decoder.write(buf));
90469
+ const text = decoder.write(buf);
90470
+ if (text.length > 0) onUpdate?.({
90471
+ kind,
90472
+ text
90473
+ });
90474
+ builder.write(text);
90428
90475
  }
90429
90476
  } catch (error) {
90430
90477
  if (!isPrematureCloseError(error) || suppressPrematureClose?.() !== true) throw error;
90431
90478
  }
90432
- builder.write(decoder.end());
90479
+ const trailing = decoder.end();
90480
+ if (trailing.length > 0) onUpdate?.({
90481
+ kind,
90482
+ text: trailing
90483
+ });
90484
+ builder.write(trailing);
90433
90485
  }
90434
90486
  function isPrematureCloseError(error) {
90435
90487
  return error instanceof Error && error.code === "ERR_STREAM_PREMATURE_CLOSE";
@@ -90452,7 +90504,7 @@ function rewriteWindowsNullRedirect(command) {
90452
90504
  return command.replace(WINDOWS_NUL_REDIRECT, "$1/dev/null");
90453
90505
  }
90454
90506
  //#endregion
90455
- //#region ../../packages/agent-core/src/tools/builtin/web/fetch-url.md
90507
+ //#region ../../packages/agent-core/src/tools/builtin/web/fetch-url.md?raw
90456
90508
  var fetch_url_default = "Fetch content from a URL. Returns the main text content extracted from the page. Use this when you need to read a specific web page.\n\nOnly public `http`/`https` URLs are supported. Requests to private, loopback, or link-local addresses are refused, and responses larger than 10 MiB are rejected.\n";
90457
90509
  //#endregion
90458
90510
  //#region ../../packages/agent-core/src/tools/builtin/web/fetch-url.ts
@@ -90525,7 +90577,7 @@ var FetchURLTool = class {
90525
90577
  }
90526
90578
  };
90527
90579
  //#endregion
90528
- //#region ../../packages/agent-core/src/tools/builtin/web/web-search.md
90580
+ //#region ../../packages/agent-core/src/tools/builtin/web/web-search.md?raw
90529
90581
  var web_search_default = "Search the web for information. Use this when you need up-to-date information from the internet.\n\nEach result includes its title, URL, snippet, and—when available—a publication date. When `include_content` is enabled, the full page content—when available—is appended after the snippet.\n";
90530
90582
  //#endregion
90531
90583
  //#region ../../packages/agent-core/src/tools/builtin/web/web-search.ts
@@ -91958,13 +92010,119 @@ function readConfigFile(filePath) {
91958
92010
  return parseConfigString(readFileSync(filePath, "utf-8"), filePath);
91959
92011
  }
91960
92012
  /**
91961
- * Load the config for runtime consumption: the on-disk config plus any model
91962
- * synthesized from `KIMI_MODEL_*` environment variables. Use this everywhere a
91963
- * value is assigned to the live runtime config; use the raw `readConfigFile`
91964
- * for write-back paths so the synthesized model is never persisted.
92013
+ * Strict read for write paths (read-merge-write must never use a salvaged
92014
+ * config as its base, or the rewrite would drop the user's broken-but-fixable
92015
+ * sections). Re-throws validation failures with a short actionable message —
92016
+ * UIs surface it directly — instead of the raw validation details.
92017
+ */
92018
+ function readConfigFileForUpdate(filePath) {
92019
+ try {
92020
+ return readConfigFile(filePath);
92021
+ } catch (error) {
92022
+ if (error instanceof KimiError && error.code === ErrorCodes.CONFIG_INVALID) throw new KimiError(ErrorCodes.CONFIG_INVALID, `Cannot change settings while ${filePath} is invalid — fix it first (run \`kimi doctor\` for details).`, { cause: error });
92023
+ throw error;
92024
+ }
92025
+ }
92026
+ /**
92027
+ * Lenient variant of `loadRuntimeConfig` that never throws: schema errors
92028
+ * drop only the offending sections (whole entry for `providers`/`models`,
92029
+ * whole top-level section otherwise) and a bad KIMI_MODEL_* env overlay is
92030
+ * skipped, each reported as a warning. A file that cannot be used at all
92031
+ * additionally sets `fileError` so startup can fail fast while mid-run
92032
+ * reloads degrade. Runtime read paths use this; write paths must keep using
92033
+ * the strict readers so a broken file is never silently rewritten.
92034
+ */
92035
+ function loadRuntimeConfigSafe(filePath, env = process.env) {
92036
+ const fileWarnings = [];
92037
+ let fileError;
92038
+ let config = getDefaultConfig();
92039
+ let text;
92040
+ try {
92041
+ text = existsSync(filePath) ? readFileSync(filePath, "utf-8") : void 0;
92042
+ } catch (error) {
92043
+ fileError = new KimiError(ErrorCodes.CONFIG_INVALID, `Failed to read ${filePath}: ${describeUnknownError(error)}`, { cause: error });
92044
+ fileWarnings.push(`Failed to read ${filePath}: ${describeUnknownError(error)}.`);
92045
+ }
92046
+ if (text !== void 0 && text.trim().length > 0) {
92047
+ let data;
92048
+ try {
92049
+ data = parse$2(text);
92050
+ } catch (error) {
92051
+ fileError = new KimiError(ErrorCodes.CONFIG_INVALID, `Invalid TOML in ${filePath}: ${describeUnknownError(error)}`, { cause: error });
92052
+ fileWarnings.push(`Invalid TOML in ${filePath}: ${describeTomlSyntaxError(error)}.`);
92053
+ }
92054
+ if (data !== void 0) {
92055
+ const raw = cloneRecord(data);
92056
+ const transformed = transformTomlData(data);
92057
+ transformed["raw"] = raw;
92058
+ const salvaged = salvageConfigData(transformed);
92059
+ if (salvaged.config === void 0) {
92060
+ fileError = new KimiError(ErrorCodes.CONFIG_INVALID, `Invalid configuration in ${filePath}: ${formatConfigValidationError(salvaged.error)}`, { cause: salvaged.error });
92061
+ fileWarnings.push(`Invalid configuration in ${filePath}: ${formatConfigValidationError(salvaged.error)}.`);
92062
+ } else {
92063
+ config = salvaged.config;
92064
+ if (salvaged.dropped.length > 0) fileWarnings.push(`Ignored invalid config in ${filePath}: ${salvaged.dropped.join(", ")}. Run \`kimi doctor\` for details.`);
92065
+ }
92066
+ }
92067
+ }
92068
+ const envWarnings = [];
92069
+ try {
92070
+ config = applyEnvModelConfig(config, env);
92071
+ } catch (error) {
92072
+ envWarnings.push(`Ignoring KIMI_MODEL_* environment overrides: ${describeUnknownError(error)}`);
92073
+ }
92074
+ return {
92075
+ config,
92076
+ fileWarnings,
92077
+ envWarnings,
92078
+ fileError
92079
+ };
92080
+ }
92081
+ /** Sections keyed by user-chosen names where single entries can be dropped. */
92082
+ const ENTRY_KEYED_SECTIONS = new Set(["providers", "models"]);
92083
+ function salvageConfigData(transformed) {
92084
+ const dropped = [];
92085
+ for (;;) {
92086
+ const result = KimiConfigSchema.safeParse(transformed);
92087
+ if (result.success) return {
92088
+ config: result.data,
92089
+ dropped
92090
+ };
92091
+ let deletedAny = false;
92092
+ for (const issue of result.error.issues) {
92093
+ const [section, entry] = issue.path;
92094
+ if (typeof section !== "string" || !(section in transformed)) continue;
92095
+ const sectionValue = transformed[section];
92096
+ if (ENTRY_KEYED_SECTIONS.has(section) && typeof entry === "string" && isPlainObject$1(sectionValue)) {
92097
+ if (entry in sectionValue) {
92098
+ delete sectionValue[entry];
92099
+ dropped.push(`${camelToSnake$2(section)}.${entry}`);
92100
+ deletedAny = true;
92101
+ }
92102
+ continue;
92103
+ }
92104
+ delete transformed[section];
92105
+ dropped.push(camelToSnake$2(section));
92106
+ deletedAny = true;
92107
+ }
92108
+ if (!deletedAny) return {
92109
+ config: void 0,
92110
+ dropped,
92111
+ error: result.error
92112
+ };
92113
+ }
92114
+ }
92115
+ function describeUnknownError(error) {
92116
+ return error instanceof Error ? error.message : String(error);
92117
+ }
92118
+ /**
92119
+ * One-line summary of a smol-toml parse error: first message line plus the
92120
+ * line/column location, without the multi-line code-frame block.
91965
92121
  */
91966
- function loadRuntimeConfig(filePath, env = process.env) {
91967
- return applyEnvModelConfig(readConfigFile(filePath), env);
92122
+ function describeTomlSyntaxError(error) {
92123
+ const firstLine = describeUnknownError(error).split("\n", 1)[0] ?? "";
92124
+ if (error instanceof TomlError) return `${firstLine} (line ${error.line}, column ${error.column})`;
92125
+ return firstLine;
91968
92126
  }
91969
92127
  function parseConfigString(tomlText, filePath = "config.toml") {
91970
92128
  if (tomlText.trim().length === 0) return getDefaultConfig();
@@ -138802,6 +138960,7 @@ var KimiCore = class {
138802
138960
  kaos;
138803
138961
  runtime;
138804
138962
  config;
138963
+ configWarnings = [];
138805
138964
  runtimeOverride;
138806
138965
  userHomeDir;
138807
138966
  kimiRequestHeaders;
@@ -138829,7 +138988,11 @@ var KimiCore = class {
138829
138988
  this.telemetry = options.telemetry ?? noopTelemetryClient;
138830
138989
  this.appVersion = options.appVersion;
138831
138990
  ensureKimiHome(this.homeDir);
138832
- this.config = loadRuntimeConfig(this.configPath);
138991
+ const loaded = loadRuntimeConfigSafe(this.configPath);
138992
+ if (loaded.fileError !== void 0) throw loaded.fileError;
138993
+ this.config = loaded.config;
138994
+ this.configWarnings = [...loaded.fileWarnings, ...loaded.envWarnings];
138995
+ if (this.configWarnings.length > 0) log.warn("config load degraded", { warnings: this.configWarnings });
138833
138996
  this.experimentalFlags = new FlagResolver(process.env, FLAG_DEFINITIONS, this.config.experimental);
138834
138997
  this.sessionStore = new SessionStore(this.homeDir);
138835
138998
  this.plugins = new PluginManager({ kimiHomeDir: this.homeDir });
@@ -139038,16 +139201,19 @@ var KimiCore = class {
139038
139201
  });
139039
139202
  }
139040
139203
  async getKimiConfig(input) {
139041
- if (input?.reload) this.setRuntimeConfig(loadRuntimeConfig(this.configPath));
139204
+ if (input?.reload) this.reloadRuntimeConfig();
139042
139205
  return this.config;
139043
139206
  }
139207
+ async getConfigDiagnostics(_input) {
139208
+ return { warnings: this.configWarnings };
139209
+ }
139044
139210
  async setKimiConfig(input) {
139045
- const config = mergeConfigPatch(readConfigFile(this.configPath), input);
139211
+ const config = mergeConfigPatch(this.readConfigForWrite(), input);
139046
139212
  await writeConfigFile(this.configPath, config);
139047
- return this.setRuntimeConfig(loadRuntimeConfig(this.configPath));
139213
+ return this.reloadRuntimeConfig();
139048
139214
  }
139049
139215
  async removeKimiProvider(input) {
139050
- const config = readConfigFile(this.configPath);
139216
+ const config = this.readConfigForWrite();
139051
139217
  delete config.providers[input.providerId];
139052
139218
  let removedDefault = false;
139053
139219
  const existingModels = config.models ?? {};
@@ -139059,7 +139225,7 @@ var KimiCore = class {
139059
139225
  if (removedDefault) config.defaultModel = void 0;
139060
139226
  if (config.defaultProvider === input.providerId) config.defaultProvider = void 0;
139061
139227
  await writeConfigFile(this.configPath, config);
139062
- return this.setRuntimeConfig(loadRuntimeConfig(this.configPath));
139228
+ return this.reloadRuntimeConfig();
139063
139229
  }
139064
139230
  prompt({ sessionId, ...payload }) {
139065
139231
  return this.sessionApi(sessionId).prompt(payload);
@@ -139319,7 +139485,24 @@ var KimiCore = class {
139319
139485
  return new SessionAPIImpl(session);
139320
139486
  }
139321
139487
  reloadProviderManager() {
139322
- return this.setRuntimeConfig(loadRuntimeConfig(this.configPath));
139488
+ return this.reloadRuntimeConfig();
139489
+ }
139490
+ readConfigForWrite() {
139491
+ return readConfigFileForUpdate(this.configPath);
139492
+ }
139493
+ reloadRuntimeConfig() {
139494
+ const loaded = loadRuntimeConfigSafe(this.configPath);
139495
+ if (loaded.fileWarnings.length > 0) {
139496
+ this.configWarnings = [
139497
+ ...loaded.fileWarnings,
139498
+ ...loaded.envWarnings,
139499
+ "config.toml has errors; keeping the previously loaded configuration."
139500
+ ];
139501
+ log.warn("config reload degraded; keeping previous config", { warnings: loaded.fileWarnings });
139502
+ return this.config;
139503
+ }
139504
+ this.configWarnings = loaded.envWarnings;
139505
+ return this.setRuntimeConfig(loaded.config);
139323
139506
  }
139324
139507
  setRuntimeConfig(config) {
139325
139508
  this.config = config;
@@ -139982,6 +140165,10 @@ var KimiHarness = class {
139982
140165
  async getConfig(options = {}) {
139983
140166
  return this.rpc.getConfig(options);
139984
140167
  }
140168
+ /** Warnings from the most recent config.toml load; empty when the config is fully valid. */
140169
+ async getConfigDiagnostics() {
140170
+ return this.rpc.getConfigDiagnostics();
140171
+ }
139985
140172
  async getExperimentalFeatures() {
139986
140173
  return this.rpc.getExperimentalFeatures();
139987
140174
  }
@@ -143239,7 +143426,7 @@ var KimiAuthFacade = class {
143239
143426
  onRefresh: options.onRefresh,
143240
143427
  configAdapter: {
143241
143428
  configPath: options.configPath,
143242
- read: () => readConfigFile(options.configPath),
143429
+ read: () => readConfigFileForUpdate(options.configPath),
143243
143430
  write: async (config) => {
143244
143431
  await writeConfigFile(options.configPath, config);
143245
143432
  },
@@ -143314,7 +143501,7 @@ var KimiAuthFacade = class {
143314
143501
  };
143315
143502
  resolveManagedAuth(providerName) {
143316
143503
  const name = providerName ?? "managed:kimi-code";
143317
- const provider = readConfigFile(this.options.configPath).providers[name];
143504
+ const provider = loadRuntimeConfigSafe(this.options.configPath).config.providers[name];
143318
143505
  return {
143319
143506
  oauthRef: provider?.oauth,
143320
143507
  baseUrl: provider?.baseUrl
@@ -143403,6 +143590,9 @@ var SDKRpcClientBase = class {
143403
143590
  async getConfig(input) {
143404
143591
  return (await this.getRpc()).getKimiConfig(input ?? {});
143405
143592
  }
143593
+ async getConfigDiagnostics() {
143594
+ return (await this.getRpc()).getConfigDiagnostics({});
143595
+ }
143406
143596
  async getExperimentalFeatures() {
143407
143597
  return (await this.getRpc()).getExperimentalFeatures({});
143408
143598
  }
@@ -154211,7 +154401,7 @@ function buildSkillSlashCommands(skills) {
154211
154401
  const commandMap = /* @__PURE__ */ new Map();
154212
154402
  return {
154213
154403
  commands: [...skills].toSorted(compareSkillSlashCommands).filter(isUserActivatableSkill).map((skill) => {
154214
- const commandName = skill.source === "builtin" ? skill.name : `skill:${skill.name}`;
154404
+ const commandName = skill.source === "builtin" || skill.isSubSkill === true ? skill.name : `skill:${skill.name}`;
154215
154405
  commandMap.set(commandName, skill.name);
154216
154406
  return {
154217
154407
  name: commandName,
@@ -154951,8 +155141,9 @@ function registerLoginCommand(parent) {
154951
155141
  *
154952
155142
  * `add` writes the same `source = { kind: 'apiJson', url, apiKey }` blob the
154953
155143
  * TUI does; the next launch's `refreshAllProviderModels`
154954
- * (apps/kimi-code/src/tui/utils/refresh-providers.ts) groups by `{url, apiKey}`
154955
- * and re-fetches the model list, so periodic refresh is automatic.
155144
+ * (apps/kimi-code/src/tui/utils/refresh-providers.ts) groups by URL, retries
155145
+ * available API-key candidates, and re-fetches the model list, so periodic
155146
+ * refresh is automatic.
154956
155147
  */
154957
155148
  async function handleProviderAdd$1(deps, url, opts) {
154958
155149
  const apiKey = resolveApiKey(opts.apiKey, deps.env);
@@ -155176,29 +155367,42 @@ async function loadCatalogOrExit(deps, url) {
155176
155367
  }
155177
155368
  function registerProviderCommand(parent, deps) {
155178
155369
  const provider = parent.command("provider").description("Manage LLM providers non-interactively.");
155370
+ const runAction = async (resolved, run) => {
155371
+ try {
155372
+ await run();
155373
+ } catch (error) {
155374
+ resolved.stderr.write(`${errorMessage(error)}\n`);
155375
+ resolved.exit(1);
155376
+ }
155377
+ };
155179
155378
  provider.command("add <url>").description("Import every provider listed in a custom registry (api.json).").option("--api-key <key>", "Registry API key. Falls back to KIMI_REGISTRY_API_KEY.").action(async (url, options) => {
155180
- await handleProviderAdd$1(resolveDeps(deps), url, { apiKey: options.apiKey });
155379
+ const resolved = resolveDeps(deps);
155380
+ await runAction(resolved, () => handleProviderAdd$1(resolved, url, { apiKey: options.apiKey }));
155181
155381
  });
155182
155382
  provider.command("remove <providerId>").description("Remove a provider and every model alias that referenced it.").action(async (providerId) => {
155183
- await handleProviderRemove(resolveDeps(deps), providerId);
155383
+ const resolved = resolveDeps(deps);
155384
+ await runAction(resolved, () => handleProviderRemove(resolved, providerId));
155184
155385
  });
155185
155386
  provider.command("list").description("Show configured providers and their model counts.").option("--json", "Emit the raw providers/models config as JSON.", false).action(async (options) => {
155186
- await handleProviderList(resolveDeps(deps), { json: options.json === true });
155387
+ const resolved = resolveDeps(deps);
155388
+ await runAction(resolved, () => handleProviderList(resolved, { json: options.json === true }));
155187
155389
  });
155188
155390
  const catalog = provider.command("catalog").description("Discover and import providers from the public models.dev catalog.");
155189
155391
  catalog.command("list [providerId]").description("List providers in the catalog, or models when a providerId is given.").option("--filter <substring>", "Case-insensitive id/name substring filter.").option("--url <url>", `Override catalog URL. Defaults to ${DEFAULT_CATALOG_URL}.`).option("--json", "Emit the matching catalog slice as JSON.", false).action(async (providerId, options) => {
155190
- await handleCatalogList(resolveDeps(deps), providerId, {
155392
+ const resolved = resolveDeps(deps);
155393
+ await runAction(resolved, () => handleCatalogList(resolved, providerId, {
155191
155394
  json: options.json === true,
155192
155395
  ...options.filter === void 0 ? {} : { filter: options.filter },
155193
155396
  ...options.url === void 0 ? {} : { url: options.url }
155194
- });
155397
+ }));
155195
155398
  });
155196
155399
  catalog.command("add <providerId>").description("Import a known provider from the catalog by id.").option("--api-key <key>", "API key for the provider. Falls back to KIMI_REGISTRY_API_KEY.").option("--default-model <modelId>", "Mark the imported model as default_model after import.").option("--url <url>", `Override catalog URL. Defaults to ${DEFAULT_CATALOG_URL}.`).action(async (providerId, options) => {
155197
- await handleCatalogAdd(resolveDeps(deps), providerId, {
155400
+ const resolved = resolveDeps(deps);
155401
+ await runAction(resolved, () => handleCatalogAdd(resolved, providerId, {
155198
155402
  ...options.apiKey === void 0 ? {} : { apiKey: options.apiKey },
155199
155403
  ...options.defaultModel === void 0 ? {} : { defaultModel: options.defaultModel },
155200
155404
  ...options.url === void 0 ? {} : { url: options.url }
155201
- });
155405
+ }));
155202
155406
  });
155203
155407
  }
155204
155408
  function resolveDeps(overrides = {}) {
@@ -155292,9 +155496,6 @@ function validateOptions(opts) {
155292
155496
  if (promptMode && opts.session === "") throw new OptionConflictError("Cannot use --session without an id in prompt mode.");
155293
155497
  if (opts.continue && opts.session !== void 0) throw new OptionConflictError("Cannot combine --continue, --session.");
155294
155498
  if (opts.yolo && opts.auto) throw new OptionConflictError("Cannot combine --yolo with --auto.");
155295
- if (!promptMode && (opts.continue || opts.session !== void 0) && opts.yolo) throw new OptionConflictError("Cannot combine --yolo with --continue or --session.");
155296
- if (!promptMode && (opts.continue || opts.session !== void 0) && opts.auto) throw new OptionConflictError("Cannot combine --auto with --continue or --session.");
155297
- if (!promptMode && (opts.continue || opts.session !== void 0) && opts.plan) throw new OptionConflictError("Cannot combine --plan with --continue or --session.");
155298
155499
  return {
155299
155500
  options: opts,
155300
155501
  uiMode: promptMode ? "print" : "shell"
@@ -160039,10 +160240,14 @@ function buildProviderManagerOptions(host) {
160039
160240
  providers: host.state.appState.availableProviders,
160040
160241
  activeProviderId,
160041
160242
  onAdd: () => {
160042
- handleProviderAdd(host);
160243
+ handleProviderAdd(host).catch((error) => {
160244
+ host.showError(`Add provider failed: ${formatErrorMessage$2(error)}`);
160245
+ });
160043
160246
  },
160044
160247
  onDeleteSource: (providerIds) => {
160045
- handleProviderManagerDeleteSource(host, providerIds);
160248
+ handleProviderManagerDeleteSource(host, providerIds).catch((error) => {
160249
+ host.showError(`Remove provider failed: ${formatErrorMessage$2(error)}`);
160250
+ });
160046
160251
  },
160047
160252
  onClose: () => {
160048
160253
  host.restoreEditor();
@@ -160192,7 +160397,9 @@ async function handleCatalogProviderAdd(host) {
160192
160397
  initialTabId: providerId,
160193
160398
  onSelect: ({ alias, thinking }) => {
160194
160399
  host.restoreEditor();
160195
- setDefaultModel(host, alias, thinking);
160400
+ setDefaultModel(host, alias, thinking).catch((error) => {
160401
+ host.showError(`Set default model failed: ${formatErrorMessage$2(error)}`);
160402
+ });
160196
160403
  },
160197
160404
  onCancel: () => {
160198
160405
  host.restoreEditor();
@@ -160220,8 +160427,8 @@ async function handleCustomRegistryAddViaDialog(host) {
160220
160427
  let entries;
160221
160428
  try {
160222
160429
  entries = await fetchCustomRegistry(source);
160223
- } catch (err) {
160224
- host.showError(`Failed to import registry: ${formatErrorMessage$2(err)}`);
160430
+ } catch (error) {
160431
+ host.showError(`Failed to import registry: ${formatErrorMessage$2(error)}`);
160225
160432
  return false;
160226
160433
  }
160227
160434
  const addedProviderIds = Object.values(entries).map((entry) => entry.id);
@@ -160233,8 +160440,8 @@ async function handleCustomRegistryAddViaDialog(host) {
160233
160440
  models: config.models
160234
160441
  });
160235
160442
  await host.authFlow.refreshConfigAfterLogin();
160236
- } catch (err) {
160237
- host.showError(`Failed to apply registry: ${formatErrorMessage$2(err)}`);
160443
+ } catch (error) {
160444
+ host.showError(`Failed to apply registry: ${formatErrorMessage$2(error)}`);
160238
160445
  return false;
160239
160446
  }
160240
160447
  const count = addedProviderIds.length;
@@ -160254,7 +160461,9 @@ async function handleCustomRegistryAddViaDialog(host) {
160254
160461
  initialTabId: firstNewProvider,
160255
160462
  onSelect: ({ alias, thinking }) => {
160256
160463
  host.restoreEditor();
160257
- setDefaultModel(host, alias, thinking);
160464
+ setDefaultModel(host, alias, thinking).catch((error) => {
160465
+ host.showError(`Set default model failed: ${formatErrorMessage$2(error)}`);
160466
+ });
160258
160467
  },
160259
160468
  onCancel: () => {
160260
160469
  host.restoreEditor();
@@ -164546,11 +164755,13 @@ var TruncatedOutputComponent = class {
164546
164755
  maxLines;
164547
164756
  indent;
164548
164757
  expandHint;
164758
+ tail;
164549
164759
  constructor(output, options) {
164550
164760
  this.expanded = options.expanded;
164551
164761
  this.maxLines = options.maxLines ?? PREVIEW_LINES;
164552
164762
  this.indent = options.indent ?? DEFAULT_INDENT;
164553
164763
  this.expandHint = options.expandHint ?? true;
164764
+ this.tail = options.tail ?? false;
164554
164765
  const cleaned = trimTrailingEmptyLines(output.split("\n")).join("\n");
164555
164766
  this.textComponent = new Text(options.isError ? currentTheme.fg("error", cleaned) : currentTheme.dim(cleaned), this.indent, 0);
164556
164767
  }
@@ -164560,8 +164771,12 @@ var TruncatedOutputComponent = class {
164560
164771
  render(width) {
164561
164772
  const contentLines = this.textComponent.render(width);
164562
164773
  if (this.expanded || contentLines.length <= this.maxLines) return contentLines;
164563
- const shown = contentLines.slice(0, this.maxLines);
164564
164774
  const remaining = contentLines.length - this.maxLines;
164775
+ if (this.tail) {
164776
+ const shown = contentLines.slice(contentLines.length - this.maxLines);
164777
+ return [" ".repeat(this.indent) + currentTheme.dim(`... (${String(remaining)} earlier lines)`), ...shown];
164778
+ }
164779
+ const shown = contentLines.slice(0, this.maxLines);
164565
164780
  const hint = this.expandHint ? `... (${String(remaining)} more lines, ctrl+o to expand)` : `... (${String(remaining)} more lines)`;
164566
164781
  return [...shown, " ".repeat(this.indent) + currentTheme.dim(hint)];
164567
164782
  }
@@ -164579,7 +164794,7 @@ var ShellExecutionComponent = class extends Container {
164579
164794
  constructor(options) {
164580
164795
  super();
164581
164796
  if (options.showCommand === true) this.addCommandPreview(options.command ?? "", options.commandPreviewLines);
164582
- if (options.result !== void 0) this.addResultPreview(options.result, options.expanded ?? false, options.resultPreviewLines ?? PREVIEW_LINES);
164797
+ if (options.result !== void 0) this.addResultPreview(options.result, options.expanded ?? false, options.resultPreviewLines ?? PREVIEW_LINES, options.tailOutput ?? false, options.expandHint ?? true);
164583
164798
  }
164584
164799
  addCommandPreview(command, previewLines) {
164585
164800
  if (command.length === 0) return;
@@ -164590,12 +164805,14 @@ var ShellExecutionComponent = class extends Container {
164590
164805
  this.addChild(new Text(currentTheme.dim(prefix + line), 2, 0));
164591
164806
  }
164592
164807
  }
164593
- addResultPreview(result, expanded, previewLines) {
164808
+ addResultPreview(result, expanded, previewLines, tailOutput, expandHint) {
164594
164809
  if (!result.output) return;
164595
164810
  this.addChild(new TruncatedOutputComponent(result.output, {
164596
164811
  expanded,
164597
164812
  isError: result.is_error ?? false,
164598
- maxLines: previewLines
164813
+ maxLines: previewLines,
164814
+ tail: tailOutput,
164815
+ expandHint
164599
164816
  }));
164600
164817
  }
164601
164818
  };
@@ -165045,6 +165262,7 @@ const STREAMING_PROGRESS_INTERVAL_MS = 1e3;
165045
165262
  const SUBAGENT_ELAPSED_INTERVAL_MS = 1e3;
165046
165263
  const PROGRESS_URL_RE = /https?:\/\/\S+/g;
165047
165264
  const ABORTED_MARK = "⊘";
165265
+ const MAX_LIVE_OUTPUT_CHARS = 5e4;
165048
165266
  function backgroundFailureMessage(status) {
165049
165267
  switch (status) {
165050
165268
  case "lost": return "Background agent lost (session restarted before completion)";
@@ -165360,6 +165578,7 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165360
165578
  subagentEndedAtMs;
165361
165579
  progressLines = [];
165362
165580
  static MAX_PROGRESS_LINES = 24;
165581
+ liveOutput = "";
165363
165582
  /**
165364
165583
  * Registered by a group container (`AgentGroupComponent` or
165365
165584
  * `ReadGroupComponent`) when this component is borrowed as a hidden state
@@ -165382,6 +165601,7 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165382
165601
  this.buildCallPreview();
165383
165602
  this.callPreviewEndIndex = this.children.length;
165384
165603
  this.buildProgressBlock();
165604
+ this.buildLiveOutputBlock();
165385
165605
  this.buildContent();
165386
165606
  this.buildSubagentBlock();
165387
165607
  this.syncStreamingProgressTimer();
@@ -165400,6 +165620,7 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165400
165620
  setResult(result) {
165401
165621
  this.result = result;
165402
165622
  this.progressLines = [];
165623
+ this.liveOutput = "";
165403
165624
  this.finalizeSubagentElapsedIfNeeded();
165404
165625
  this.syncStreamingProgressTimer();
165405
165626
  this.syncSubagentElapsedTimer();
@@ -165430,6 +165651,14 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165430
165651
  this.notifySnapshotChange();
165431
165652
  this.ui?.requestRender();
165432
165653
  }
165654
+ appendLiveOutput(text) {
165655
+ if (this.result !== void 0 || text.length === 0) return;
165656
+ this.liveOutput += text;
165657
+ if (this.liveOutput.length > MAX_LIVE_OUTPUT_CHARS) this.liveOutput = `[...truncated]\n${this.liveOutput.slice(this.liveOutput.length - MAX_LIVE_OUTPUT_CHARS)}`;
165658
+ this.rebuildContent();
165659
+ this.notifySnapshotChange();
165660
+ this.ui?.requestRender();
165661
+ }
165433
165662
  dispose() {
165434
165663
  this.stopStreamingProgressTimer();
165435
165664
  this.stopSubagentElapsedTimer();
@@ -165800,6 +166029,20 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165800
166029
  this.notifySnapshotChange();
165801
166030
  this.ui?.requestRender();
165802
166031
  }
166032
+ appendSubToolLiveOutput(id, text) {
166033
+ if (text.length === 0) return;
166034
+ const activity = this.subToolActivities.get(id);
166035
+ const ongoing = this.ongoingSubCalls.get(id);
166036
+ if (activity === void 0 && ongoing === void 0) return;
166037
+ const name = activity?.name ?? ongoing?.name ?? "Tool";
166038
+ const args = activity?.args ?? ongoing?.args ?? {};
166039
+ let output = (activity?.output ?? "") + text;
166040
+ if (output.length > MAX_LIVE_OUTPUT_CHARS) output = `[...truncated]\n${output.slice(output.length - MAX_LIVE_OUTPUT_CHARS)}`;
166041
+ this.upsertSubToolActivity(id, name, args, activity?.phase ?? "ongoing", output);
166042
+ this.rebuildContent();
166043
+ this.notifySnapshotChange();
166044
+ this.ui?.requestRender();
166045
+ }
165803
166046
  finishSubToolCall(result) {
165804
166047
  const ongoing = this.ongoingSubCalls.get(result.tool_call_id);
165805
166048
  if (ongoing === void 0) return;
@@ -165874,6 +166117,7 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165874
166117
  rebuildContent() {
165875
166118
  while (this.children.length > this.callPreviewEndIndex) this.children.pop();
165876
166119
  this.buildProgressBlock();
166120
+ this.buildLiveOutputBlock();
165877
166121
  this.buildContent();
165878
166122
  this.buildSubagentBlock();
165879
166123
  }
@@ -165882,6 +166126,7 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165882
166126
  this.buildCallPreview();
165883
166127
  this.callPreviewEndIndex = this.children.length;
165884
166128
  this.buildProgressBlock();
166129
+ this.buildLiveOutputBlock();
165885
166130
  this.buildContent();
165886
166131
  this.buildSubagentBlock();
165887
166132
  }
@@ -165911,6 +166156,21 @@ var ToolCallComponent = class ToolCallComponent extends Container {
165911
166156
  this.addChild(new Text(styled, 2, 0));
165912
166157
  }
165913
166158
  }
166159
+ buildLiveOutputBlock() {
166160
+ if (this.result !== void 0) return;
166161
+ if (this.liveOutput.length === 0) return;
166162
+ this.addChild(new ShellExecutionComponent({
166163
+ result: {
166164
+ tool_call_id: this.toolCall.id,
166165
+ output: this.liveOutput,
166166
+ is_error: false
166167
+ },
166168
+ expanded: this.expanded,
166169
+ resultPreviewLines: 3,
166170
+ tailOutput: true,
166171
+ expandHint: false
166172
+ }));
166173
+ }
165914
166174
  buildSubagentBlock() {
165915
166175
  if (this.subagentAgentId === void 0 && this.ongoingSubCalls.size === 0 && this.finishedSubCalls.length === 0 && this.subagentText.length === 0 && this.subagentPhase === void 0 && this.backgroundTaskTerminalPhase === void 0) return;
165916
166176
  if (this.isSingleSubagentView()) {
@@ -166060,7 +166320,6 @@ var ToolCallComponent = class ToolCallComponent extends Container {
166060
166320
  if (outputLine !== void 0) this.addChild(new PrefixedWrappedLine(` ${currentTheme.fg("text", "└")} `, " ", currentTheme.fg("text", outputLine)));
166061
166321
  }
166062
166322
  addSubToolOutputPreview(activity) {
166063
- if (activity.phase === "ongoing") return;
166064
166323
  const output = activity.output;
166065
166324
  if (output === void 0 || output.trim().length === 0) return;
166066
166325
  if (activity.name !== "Bash" && !isGenericToolResult(activity.name)) return;
@@ -166069,7 +166328,8 @@ var ToolCallComponent = class ToolCallComponent extends Container {
166069
166328
  expandHint: false,
166070
166329
  isError: activity.phase === "failed",
166071
166330
  maxLines: 3,
166072
- indent: SUBAGENT_SUBTOOL_OUTPUT_INDENT
166331
+ indent: SUBAGENT_SUBTOOL_OUTPUT_INDENT,
166332
+ tail: activity.phase === "ongoing"
166073
166333
  }));
166074
166334
  }
166075
166335
  getRecentSubToolActivities() {
@@ -166975,6 +167235,7 @@ async function runPrompt(opts, version, io = {}) {
166975
167235
  try {
166976
167236
  await harness.ensureConfigFile();
166977
167237
  const config = await harness.getConfig();
167238
+ for (const warning of (await harness.getConfigDiagnostics()).warnings) stderr.write(`Warning: ${warning}\n`);
166978
167239
  const { session, resumed, restorePermission, telemetryModel, goalModel } = await resolvePromptSession(harness, opts, workDir, config.defaultModel, stderr, (restorePermission) => {
166979
167240
  restorePromptSessionPermission = restorePermission;
166980
167241
  });
@@ -167688,6 +167949,144 @@ function findBinaryRecursively(rootDir, binaryName) {
167688
167949
  return null;
167689
167950
  }
167690
167951
  //#endregion
167952
+ //#region src/tui/banner/banner-provider.ts
167953
+ function normalizeTag(value) {
167954
+ if (typeof value !== "string") return null;
167955
+ const trimmed = value.trim();
167956
+ return trimmed.length > 0 ? trimmed : null;
167957
+ }
167958
+ function normalizeText(value) {
167959
+ if (typeof value !== "string") return null;
167960
+ const trimmed = value.trim();
167961
+ return trimmed.length > 0 ? trimmed : null;
167962
+ }
167963
+ function normalizeUtcDate(value) {
167964
+ if (value.endsWith("Z")) return value;
167965
+ if (/[+-]\d{2}:\d{2}$/.test(value)) return value;
167966
+ return `${value}Z`;
167967
+ }
167968
+ function parseDate(value) {
167969
+ if (typeof value !== "string" || value.length === 0) return null;
167970
+ const normalized = normalizeUtcDate(value);
167971
+ const date = new Date(normalized);
167972
+ return Number.isNaN(date.getTime()) ? null : date;
167973
+ }
167974
+ function isWithinWindow(start, end, now) {
167975
+ if (start !== null && now < start) return false;
167976
+ if (end !== null && now > end) return false;
167977
+ return true;
167978
+ }
167979
+ function meetsMinVersion(minVersion, clientVersion) {
167980
+ if (minVersion === void 0 || minVersion === null) return true;
167981
+ if (typeof minVersion !== "string" || minVersion.length === 0) return true;
167982
+ const min = valid(minVersion);
167983
+ const current = valid(clientVersion);
167984
+ if (min === null || current === null) return false;
167985
+ return gte(current, min);
167986
+ }
167987
+ function pickActiveBanner(json, clientVersion, now) {
167988
+ if (json.banner_enabled !== true) return null;
167989
+ if (!meetsMinVersion(json.banner_min_version, clientVersion)) return null;
167990
+ if (!isWithinWindow(parseDate(json.banner_start_time), parseDate(json.banner_end_time), now)) return null;
167991
+ const mainText = normalizeText(json.banner_maintext);
167992
+ if (mainText === null) return null;
167993
+ return {
167994
+ tag: normalizeTag(json.banner_title),
167995
+ mainText,
167996
+ subText: normalizeText(json.banner_subtext)
167997
+ };
167998
+ }
167999
+ function pickFallbackBanner(json, clientVersion, now, random) {
168000
+ if (json.banner_fallback_enabled !== true) return null;
168001
+ const list = Array.isArray(json.banner_fallback_list) ? json.banner_fallback_list : [];
168002
+ const candidates = [];
168003
+ for (const raw of list) {
168004
+ if (typeof raw !== "object" || raw === null) continue;
168005
+ const item = raw;
168006
+ if (item.enabled !== true) continue;
168007
+ if (!meetsMinVersion(item.banner_min_version, clientVersion)) continue;
168008
+ const mainText = normalizeText(item.banner_maintext);
168009
+ if (mainText === null) continue;
168010
+ candidates.push({
168011
+ tag: normalizeTag(item.banner_title),
168012
+ mainText,
168013
+ subText: normalizeText(item.banner_subtext)
168014
+ });
168015
+ }
168016
+ if (candidates.length === 0) return null;
168017
+ return candidates[Math.floor(random() * candidates.length)];
168018
+ }
168019
+ function selectBannerState(json, clientVersion, now, random) {
168020
+ const typed = typeof json === "object" && json !== null ? json : {};
168021
+ return pickActiveBanner(typed, clientVersion, now) ?? pickFallbackBanner(typed, clientVersion, now, random);
168022
+ }
168023
+ var BannerProvider = class {
168024
+ clientVersion;
168025
+ url;
168026
+ constructor(clientVersion, url = KIMI_CODE_TIPS_BANNER_URL) {
168027
+ this.clientVersion = clientVersion;
168028
+ this.url = url;
168029
+ }
168030
+ async load(fetchImpl = fetch) {
168031
+ try {
168032
+ const controller = new AbortController();
168033
+ const timeout = setTimeout(() => {
168034
+ controller.abort();
168035
+ }, 3e3);
168036
+ const response = await fetchImpl(this.url, { signal: controller.signal });
168037
+ clearTimeout(timeout);
168038
+ if (!response.ok) return null;
168039
+ return selectBannerState(await response.json(), this.clientVersion, /* @__PURE__ */ new Date(), Math.random);
168040
+ } catch {
168041
+ return null;
168042
+ }
168043
+ }
168044
+ };
168045
+ //#endregion
168046
+ //#region src/tui/components/chrome/banner.ts
168047
+ const PREFIX_STAR = "✦";
168048
+ const PADDING = " ";
168049
+ var BannerComponent = class {
168050
+ state;
168051
+ constructor(state) {
168052
+ this.state = state;
168053
+ }
168054
+ invalidate() {}
168055
+ render(width) {
168056
+ const main = (s) => currentTheme.boldFg("textStrong", s);
168057
+ const dim = (s) => currentTheme.fg("textDim", s);
168058
+ if (width < 1) return [""];
168059
+ const tagText = this.state.tag ?? "";
168060
+ const tagLabel = tagText.length > 0 ? `${PREFIX_STAR} ${tagText}` : "";
168061
+ const tagStyled = tagLabel.length > 0 ? currentTheme.boldFg("primary", tagLabel) : "";
168062
+ const tagDisplay = tagStyled.length > 0 ? tagStyled + PADDING : "";
168063
+ const tagWidth = visibleWidth(tagDisplay);
168064
+ const showTag = tagWidth > 0 && tagWidth < width;
168065
+ const bodyIndent = showTag ? " ".repeat(tagWidth) : "";
168066
+ const descIndent = showTag ? " ".repeat(visibleWidth(PREFIX_STAR + PADDING)) : "";
168067
+ const bodyContentWidth = width - (showTag ? tagWidth : 0);
168068
+ const descContentWidth = width - (showTag ? visibleWidth(PREFIX_STAR + PADDING) : 0);
168069
+ if (bodyContentWidth <= 0) return [""];
168070
+ const mainSegments = this.state.mainText.split("\n");
168071
+ const subSegments = this.state.subText ? this.state.subText.split("\n") : [];
168072
+ const result = [];
168073
+ for (let i = 0; i < mainSegments.length; i++) {
168074
+ const wrapped = wrapTextWithAnsi(mainSegments[i], bodyContentWidth);
168075
+ for (let j = 0; j < wrapped.length; j++) {
168076
+ const boldLine = main(wrapped[j]);
168077
+ if (i === 0 && j === 0 && showTag) result.push(tagDisplay + boldLine);
168078
+ else result.push(bodyIndent + boldLine);
168079
+ }
168080
+ }
168081
+ for (const sub of subSegments) {
168082
+ const wrapped = wrapTextWithAnsi(sub, descContentWidth <= 0 ? bodyContentWidth : descContentWidth);
168083
+ for (const line of wrapped) result.push(descIndent + dim(line));
168084
+ }
168085
+ result.push("");
168086
+ return result;
168087
+ }
168088
+ };
168089
+ //#endregion
167691
168090
  //#region src/tui/components/chrome/device-code-box.ts
167692
168091
  var DeviceCodeBoxComponent = class {
167693
168092
  params;
@@ -169136,6 +169535,447 @@ var SessionPickerComponent = class extends Container {
169136
169535
  }
169137
169536
  };
169138
169537
  //#endregion
169538
+ //#region src/tui/components/editor/file-mention-provider.ts
169539
+ const PATH_DELIMITERS = new Set([
169540
+ " ",
169541
+ " ",
169542
+ "\"",
169543
+ "'",
169544
+ "="
169545
+ ]);
169546
+ const MAX_FALLBACK_SCAN = 2e3;
169547
+ const MAX_FALLBACK_SUGGESTIONS = 50;
169548
+ /**
169549
+ * Kimi wrapper around pi-tui's combined autocomplete provider.
169550
+ *
169551
+ * File / folder mention behavior uses pi-tui's fd-backed provider when fd is
169552
+ * available. While managed fd is downloading (or when it is unavailable), a
169553
+ * small filesystem fallback keeps basic `@` file and folder completion usable.
169554
+ * Ordinary path completion is still handled by pi-tui's readdir-backed path
169555
+ * completer. This wrapper also keeps Kimi-specific slash-command guards.
169556
+ */
169557
+ var FileMentionProvider = class {
169558
+ slashCommands;
169559
+ workDir;
169560
+ fdPath;
169561
+ inner;
169562
+ constructor(slashCommands, workDir, fdPath) {
169563
+ this.slashCommands = slashCommands;
169564
+ this.workDir = workDir;
169565
+ this.fdPath = fdPath;
169566
+ const expanded = [];
169567
+ for (const cmd of slashCommands) {
169568
+ expanded.push(cmd);
169569
+ for (const alias of cmd.aliases ?? []) expanded.push({
169570
+ ...cmd,
169571
+ name: alias
169572
+ });
169573
+ }
169574
+ this.inner = new CombinedAutocompleteProvider(expanded, workDir, fdPath);
169575
+ }
169576
+ async getSuggestions(lines, cursorLine, cursorCol, options) {
169577
+ const currentLine = lines[cursorLine] ?? "";
169578
+ const textBeforeCursor = currentLine.slice(0, cursorCol);
169579
+ if (shouldSuppressLeadingWhitespaceSlashPath(textBeforeCursor, options.force)) return null;
169580
+ if (shouldSuppressSlashArgumentCompletion(textBeforeCursor, currentLine.slice(cursorCol), options.force)) return null;
169581
+ const atPrefix = extractAtPrefix(textBeforeCursor);
169582
+ if (atPrefix !== null) {
169583
+ if (this.fdPath === null) return getFsMentionSuggestions(this.workDir, atPrefix, options.signal);
169584
+ try {
169585
+ return await this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
169586
+ } catch {
169587
+ return getFsMentionSuggestions(this.workDir, atPrefix, options.signal);
169588
+ }
169589
+ }
169590
+ if (!options.force && textBeforeCursor.startsWith("/")) {
169591
+ if (textBeforeCursor.indexOf(" ") === -1) {
169592
+ const tokens = textBeforeCursor.slice(1).trim().split(/\s+/).filter((t) => t.length > 0);
169593
+ const matches = [];
169594
+ for (const cmd of this.slashCommands) {
169595
+ const nameScore = scoreTokens(tokens, cmd.name);
169596
+ if (nameScore !== null) {
169597
+ matches.push({
169598
+ cmd,
169599
+ score: nameScore,
169600
+ viaAlias: false,
169601
+ label: cmd.name
169602
+ });
169603
+ continue;
169604
+ }
169605
+ const aliases = cmd.aliases ?? [];
169606
+ let bestAliasScore = null;
169607
+ for (const alias of aliases) {
169608
+ const aliasScore = scoreTokens(tokens, alias);
169609
+ if (aliasScore !== null && (bestAliasScore === null || aliasScore < bestAliasScore)) bestAliasScore = aliasScore;
169610
+ }
169611
+ if (bestAliasScore !== null) matches.push({
169612
+ cmd,
169613
+ score: bestAliasScore,
169614
+ viaAlias: true,
169615
+ label: `${cmd.name} (${aliases.join(", ")})`
169616
+ });
169617
+ }
169618
+ matches.sort((a, b) => a.score - b.score || Number(a.viaAlias) - Number(b.viaAlias));
169619
+ if (matches.length === 0) return null;
169620
+ return {
169621
+ items: matches.map((m) => ({
169622
+ value: m.cmd.name,
169623
+ label: m.label,
169624
+ description: formatSlashCommandDescription(m.cmd)
169625
+ })),
169626
+ prefix: textBeforeCursor
169627
+ };
169628
+ }
169629
+ }
169630
+ try {
169631
+ return await this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
169632
+ } catch {
169633
+ return null;
169634
+ }
169635
+ }
169636
+ applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
169637
+ return this.inner.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
169638
+ }
169639
+ };
169640
+ function extractAtPrefix(text) {
169641
+ let tokenStart = 0;
169642
+ for (let i = text.length - 1; i >= 0; i -= 1) if (PATH_DELIMITERS.has(text[i] ?? "")) {
169643
+ tokenStart = i + 1;
169644
+ break;
169645
+ }
169646
+ if (text[tokenStart] !== "@") return null;
169647
+ return text.slice(tokenStart);
169648
+ }
169649
+ function getFsMentionSuggestions(workDir, atPrefix, signal) {
169650
+ if (signal.aborted) return null;
169651
+ const query = atPrefix.slice(1);
169652
+ const candidates = collectFsMentionCandidates(workDir, signal);
169653
+ if (candidates.length === 0 || signal.aborted) return null;
169654
+ const ranked = rankFsMentionCandidates(candidates, query).slice(0, MAX_FALLBACK_SUGGESTIONS);
169655
+ if (ranked.length === 0) return null;
169656
+ return {
169657
+ prefix: atPrefix,
169658
+ items: ranked.map(toMentionItem)
169659
+ };
169660
+ }
169661
+ function collectFsMentionCandidates(workDir, signal) {
169662
+ const result = [];
169663
+ const stack = [""];
169664
+ while (stack.length > 0 && result.length < MAX_FALLBACK_SCAN) {
169665
+ if (signal.aborted) break;
169666
+ const relativeDir = stack.pop() ?? "";
169667
+ const absoluteDir = relativeDir.length === 0 ? workDir : join$1(workDir, relativeDir);
169668
+ let entries;
169669
+ try {
169670
+ entries = readdirSync(absoluteDir, { withFileTypes: true });
169671
+ } catch {
169672
+ continue;
169673
+ }
169674
+ for (const entry of entries) {
169675
+ if (signal.aborted || result.length >= MAX_FALLBACK_SCAN) break;
169676
+ if (entry.name === ".git") continue;
169677
+ const relativePath = normalizePath(relativeDir.length === 0 ? entry.name : join$1(relativeDir, entry.name));
169678
+ const isSymlink = entry.isSymbolicLink();
169679
+ let isDirectory = entry.isDirectory();
169680
+ if (!isDirectory && isSymlink) try {
169681
+ isDirectory = statSync(join$1(workDir, relativePath)).isDirectory();
169682
+ } catch {}
169683
+ result.push({
169684
+ path: relativePath,
169685
+ isDirectory
169686
+ });
169687
+ if (isDirectory && !isSymlink) stack.push(relativePath);
169688
+ }
169689
+ }
169690
+ return result;
169691
+ }
169692
+ function rankFsMentionCandidates(candidates, query) {
169693
+ const lowerQuery = query.toLowerCase();
169694
+ const scored = [];
169695
+ for (const candidate of candidates) {
169696
+ const score = scoreCandidate(candidate, lowerQuery);
169697
+ if (score > 0) scored.push({
169698
+ candidate,
169699
+ score
169700
+ });
169701
+ }
169702
+ scored.sort((a, b) => {
169703
+ if (a.score !== b.score) return b.score - a.score;
169704
+ if (a.candidate.isDirectory !== b.candidate.isDirectory) return a.candidate.isDirectory ? -1 : 1;
169705
+ return a.candidate.path.localeCompare(b.candidate.path);
169706
+ });
169707
+ return scored.map((entry) => entry.candidate);
169708
+ }
169709
+ function scoreCandidate(candidate, lowerQuery) {
169710
+ if (lowerQuery.length === 0) {
169711
+ const depthPenalty = candidate.path.split("/").length - 1;
169712
+ return (candidate.isDirectory ? 120 : 100) - depthPenalty;
169713
+ }
169714
+ const lowerPath = candidate.path.toLowerCase();
169715
+ const lowerBase = basename$1(candidate.path).toLowerCase();
169716
+ let score = 0;
169717
+ if (lowerBase === lowerQuery) score = 100;
169718
+ else if (lowerBase.startsWith(lowerQuery)) score = 80;
169719
+ else if (lowerBase.includes(lowerQuery)) score = 50;
169720
+ else if (lowerPath.includes(lowerQuery)) score = 30;
169721
+ if (candidate.isDirectory && score > 0) score += 10;
169722
+ return score;
169723
+ }
169724
+ function toMentionItem(candidate) {
169725
+ const valuePath = candidate.isDirectory ? `${candidate.path}/` : candidate.path;
169726
+ return {
169727
+ value: valuePath.includes(" ") ? `@"${valuePath}"` : `@${valuePath}`,
169728
+ label: `${basename$1(candidate.path)}${candidate.isDirectory ? "/" : ""}`,
169729
+ description: valuePath
169730
+ };
169731
+ }
169732
+ function normalizePath(path) {
169733
+ return path.replaceAll("\\", "/");
169734
+ }
169735
+ function shouldSuppressLeadingWhitespaceSlashPath(textBeforeCursor, force) {
169736
+ if (force === true) return false;
169737
+ if (textBeforeCursor.startsWith("/")) return false;
169738
+ return textBeforeCursor.trimStart().startsWith("/");
169739
+ }
169740
+ function shouldSuppressSlashArgumentCompletion(textBeforeCursor, textAfterCursor, force) {
169741
+ if (force === true) return false;
169742
+ if (!textBeforeCursor.startsWith("/")) return false;
169743
+ if (!textBeforeCursor.includes(" ")) return false;
169744
+ return textAfterCursor.trimStart().length > 0;
169745
+ }
169746
+ /**
169747
+ * All tokens must fuzzy-match `text`; returns the summed score, or null when
169748
+ * any token misses. An empty token list matches everything with score 0.
169749
+ * Mirrors pi-tui fuzzyFilter's token semantics — keep in sync if it changes.
169750
+ */
169751
+ function scoreTokens(tokens, text) {
169752
+ let score = 0;
169753
+ for (const token of tokens) {
169754
+ const m = fuzzyMatch(token, text);
169755
+ if (!m.matches) return null;
169756
+ score += m.score;
169757
+ }
169758
+ return score;
169759
+ }
169760
+ /**
169761
+ * Mirrors CombinedAutocompleteProvider's description rendering so the
169762
+ * intercepted name completion keeps showing the argument hint.
169763
+ */
169764
+ function formatSlashCommandDescription(cmd) {
169765
+ const desc = cmd.description ?? "";
169766
+ return (cmd.argumentHint ? desc ? `${cmd.argumentHint} — ${desc}` : cmd.argumentHint : desc) || void 0;
169767
+ }
169768
+ //#endregion
169769
+ //#region src/tui/components/messages/goal-markers.ts
169770
+ /**
169771
+ * Low-profile transcript markers for the autonomous goal loop.
169772
+ *
169773
+ * Lifecycle changes (paused / resumed / cancelled) and `no_progress` verdicts
169774
+ * render as a single dim line — `◦ Goal paused` — that expands (ctrl+o, shared
169775
+ * with tool output) to show the reason when there is one. Terminal outcomes use
169776
+ * the richer completion card (the `/goal` box), not this marker.
169777
+ */
169778
+ const HEAD_INDENT = " ";
169779
+ const DETAIL_INDENT = " ";
169780
+ var GoalMarkerComponent = class {
169781
+ headline;
169782
+ detail;
169783
+ accentToken;
169784
+ expanded = false;
169785
+ marker;
169786
+ textToken;
169787
+ expandable;
169788
+ indent;
169789
+ leadingBlank;
169790
+ constructor(headline, detail, accentToken, options = {}) {
169791
+ this.headline = headline;
169792
+ this.detail = detail;
169793
+ this.accentToken = accentToken;
169794
+ this.marker = options.marker ?? "◦";
169795
+ this.textToken = options.textToken ?? "textDim";
169796
+ this.expandable = options.expandable ?? true;
169797
+ this.indent = options.indent ?? HEAD_INDENT;
169798
+ this.leadingBlank = options.leadingBlank ?? false;
169799
+ }
169800
+ invalidate() {}
169801
+ setExpanded(expanded) {
169802
+ this.expanded = expanded;
169803
+ }
169804
+ render(width) {
169805
+ const dot = currentTheme.fg(this.accentToken, this.marker);
169806
+ const head = currentTheme.fg(this.textToken, this.headline);
169807
+ if (!(this.detail !== void 0 && this.detail.length > 0)) return this.clampToWidth([`${this.indent}${dot} ${head}`], width);
169808
+ if (!this.expandable) return this.clampToWidth([`${this.indent}${dot} ${head}`], width);
169809
+ if (!this.expanded) return this.clampToWidth([`${this.indent}${dot} ${head} ${currentTheme.fg("textMuted", "(ctrl+o)")}`], width);
169810
+ const out = [`${this.indent}${dot} ${head}`];
169811
+ const wrapWidth = Math.max(20, width - 4);
169812
+ for (const line of wrap(this.detail, wrapWidth)) out.push(DETAIL_INDENT + currentTheme.fg("textDim", line));
169813
+ return this.clampToWidth(out, width);
169814
+ }
169815
+ clampToWidth(lines, width) {
169816
+ const withBlank = this.withLeadingBlank(lines);
169817
+ if (width <= 0) return withBlank.map(() => "");
169818
+ return withBlank.map((line) => truncateToWidth(line, width));
169819
+ }
169820
+ withLeadingBlank(lines) {
169821
+ return this.leadingBlank ? ["", ...lines] : lines;
169822
+ }
169823
+ };
169824
+ /**
169825
+ * Builds a marker for a lifecycle change (paused / resumed / blocked), or `null`
169826
+ * when the change should be silent (a `completion` change posts its own message,
169827
+ * not a marker). `expanded` seeds the initial ctrl+o state.
169828
+ */
169829
+ function buildGoalMarker(change, expanded, actor) {
169830
+ const spec = markerSpec(change, actor);
169831
+ if (spec === null) return null;
169832
+ const marker = new GoalMarkerComponent(spec.headline, spec.detail ?? change.reason, spec.accentToken, spec.options);
169833
+ marker.setExpanded(expanded);
169834
+ return marker;
169835
+ }
169836
+ function markerSpec(change, actor) {
169837
+ if (change.kind === "lifecycle") switch (change.status) {
169838
+ case "paused": return prominentMarker(pausedHeadline(change.reason, actor), "warning");
169839
+ case "active": return prominentMarker(resumedHeadline(actor), "primary");
169840
+ case "blocked": return {
169841
+ headline: "Goal blocked",
169842
+ accentToken: "warning"
169843
+ };
169844
+ default: return null;
169845
+ }
169846
+ return null;
169847
+ }
169848
+ function prominentMarker(headline, accentToken) {
169849
+ return {
169850
+ headline,
169851
+ accentToken,
169852
+ detail: void 0,
169853
+ options: {
169854
+ marker: STATUS_BULLET.trimEnd(),
169855
+ textToken: accentToken,
169856
+ expandable: false,
169857
+ indent: "",
169858
+ leadingBlank: true
169859
+ }
169860
+ };
169861
+ }
169862
+ function pausedHeadline(reason, actor) {
169863
+ if (reason === "Paused after interruption") return "Goal paused due to user's interruption";
169864
+ if (actor === "user") return "Goal paused by the user.";
169865
+ if (reason?.startsWith("Paused ") === true) return `Goal ${lowercaseFirst(reason)}`;
169866
+ if (reason !== void 0 && reason.length > 0) return `Goal paused: ${reason}`;
169867
+ if (actor === "model") return "Goal paused by the agent.";
169868
+ return "Goal paused";
169869
+ }
169870
+ function resumedHeadline(actor) {
169871
+ if (actor === "user") return "Goal resumed by the user.";
169872
+ if (actor === "model") return "Goal resumed by the agent.";
169873
+ return "Goal resumed";
169874
+ }
169875
+ function lowercaseFirst(text) {
169876
+ return text.length === 0 ? text : `${text[0].toLowerCase()}${text.slice(1)}`;
169877
+ }
169878
+ function wrap(text, width) {
169879
+ const words = text.replaceAll(/\s+/g, " ").trim().split(" ");
169880
+ const lines = [];
169881
+ let current = "";
169882
+ for (const word of words) {
169883
+ const candidate = current.length === 0 ? word : `${current} ${word}`;
169884
+ if (candidate.length > width && current.length > 0) {
169885
+ lines.push(current);
169886
+ current = word;
169887
+ } else current = candidate;
169888
+ }
169889
+ if (current.length > 0) lines.push(current);
169890
+ return lines.length > 0 ? lines : [""];
169891
+ }
169892
+ //#endregion
169893
+ //#region src/tui/components/messages/status-message.ts
169894
+ var StatusMessageComponent = class extends Container {
169895
+ textComponent;
169896
+ content;
169897
+ color;
169898
+ constructor(content, color) {
169899
+ super();
169900
+ this.content = content;
169901
+ this.color = color;
169902
+ const text = color === void 0 ? currentTheme.fg("textDim", content) : currentTheme.fg(color, content);
169903
+ this.textComponent = new Text(` ${text}`, 0, 0);
169904
+ this.addChild(this.textComponent);
169905
+ }
169906
+ invalidate() {
169907
+ const text = this.color === void 0 ? currentTheme.fg("textDim", this.content) : currentTheme.fg(this.color, this.content);
169908
+ this.textComponent.setText(` ${text}`);
169909
+ super.invalidate();
169910
+ }
169911
+ };
169912
+ var NoticeMessageComponent = class extends Container {
169913
+ titleText;
169914
+ detailText;
169915
+ title;
169916
+ detail;
169917
+ constructor(title, detail) {
169918
+ super();
169919
+ this.title = title;
169920
+ this.detail = detail;
169921
+ this.addChild(new Spacer(1));
169922
+ this.titleText = new Text(` ${currentTheme.fg("textStrong", title)}`, 0, 0);
169923
+ this.addChild(this.titleText);
169924
+ if (detail !== void 0 && detail.length > 0) {
169925
+ this.detailText = new Text(` ${currentTheme.fg("textDim", detail)}`, 0, 0);
169926
+ this.addChild(this.detailText);
169927
+ }
169928
+ }
169929
+ invalidate() {
169930
+ this.titleText.setText(` ${currentTheme.fg("textStrong", this.title)}`);
169931
+ if (this.detailText !== void 0 && this.detail !== void 0) this.detailText.setText(` ${currentTheme.fg("textDim", this.detail)}`);
169932
+ super.invalidate();
169933
+ }
169934
+ };
169935
+ //#endregion
169936
+ //#region src/tui/components/panes/activity-pane.ts
169937
+ var ActivityPaneComponent = class extends Container {
169938
+ constructor(options) {
169939
+ super();
169940
+ if (options.mode === "waiting" || options.mode === "tool") {
169941
+ if (options.spinner !== void 0) {
169942
+ this.addChild(new Spacer(1));
169943
+ this.addChild(options.spinner);
169944
+ }
169945
+ return;
169946
+ }
169947
+ if (options.mode === "composing" && options.spinner !== void 0) {
169948
+ this.addChild(new Spacer(1));
169949
+ this.addChild(options.spinner);
169950
+ }
169951
+ }
169952
+ };
169953
+ //#endregion
169954
+ //#region src/tui/components/panes/queue-pane.ts
169955
+ const ELLIPSIS$3 = "…";
169956
+ var QueuePaneComponent = class extends Container {
169957
+ messages;
169958
+ hint;
169959
+ constructor(options) {
169960
+ super();
169961
+ this.messages = options.messages;
169962
+ if (options.messages.length > 0) this.hint = options.isCompacting && !options.isStreaming ? " ↑ to edit · will send after compaction" : !options.canSteerImmediately ? " ↑ to edit · will send after current task" : " ↑ to edit · ctrl-s to steer immediately";
169963
+ }
169964
+ render(width) {
169965
+ const accent = (text) => currentTheme.fg("accent", text);
169966
+ const dim = (text) => currentTheme.fg("textDim", text);
169967
+ const lines = [];
169968
+ for (const item of this.messages) {
169969
+ const singleLine = item.text.replaceAll(/\s+/g, " ").trim();
169970
+ const prefix = ` ❯ `;
169971
+ const truncated = truncateToWidth(singleLine, Math.max(1, width - visibleWidth(prefix)), ELLIPSIS$3);
169972
+ lines.push(accent(prefix + truncated));
169973
+ }
169974
+ if (this.hint !== void 0) lines.push(dim(truncateToWidth(this.hint, width, ELLIPSIS$3)));
169975
+ return lines;
169976
+ }
169977
+ };
169978
+ //#endregion
169139
169979
  //#region src/tui/utils/refresh-providers.ts
169140
169980
  function readCustomRegistrySource(provider) {
169141
169981
  const source = provider.source;
@@ -169152,6 +169992,26 @@ function readCustomRegistrySource(provider) {
169152
169992
  apiKey
169153
169993
  };
169154
169994
  }
169995
+ function customRegistrySourceKey(source) {
169996
+ return JSON.stringify([source.url]);
169997
+ }
169998
+ function customRegistrySourceCredentialKey(source) {
169999
+ return JSON.stringify([source.url, source.apiKey]);
170000
+ }
170001
+ async function fetchCustomRegistryFromSources(sources) {
170002
+ let lastError;
170003
+ for (const source of sources) try {
170004
+ return {
170005
+ entries: await fetchCustomRegistry(source),
170006
+ source
170007
+ };
170008
+ } catch (error) {
170009
+ lastError = error;
170010
+ }
170011
+ if (lastError instanceof Error) throw lastError;
170012
+ if (typeof lastError === "string") throw new Error(lastError);
170013
+ throw new Error("No custom registry sources configured.");
170014
+ }
169155
170015
  function asManaged(config) {
169156
170016
  return config;
169157
170017
  }
@@ -169202,6 +170062,12 @@ function providerModelSnapshot(config, providerId, aliasKeys) {
169202
170062
  function providerModelsEqual(config, nextConfig, providerId, aliasKeys) {
169203
170063
  return providerModelSnapshot(config, providerId, aliasKeys) === providerModelSnapshot(nextConfig, providerId, aliasKeys);
169204
170064
  }
170065
+ function providerConfigSnapshot(config, providerId) {
170066
+ return JSON.stringify(config.providers[providerId] ?? null);
170067
+ }
170068
+ function providerConfigEqual(config, nextConfig, providerId) {
170069
+ return providerConfigSnapshot(config, providerId) === providerConfigSnapshot(nextConfig, providerId);
170070
+ }
169205
170071
  function providerRefreshAliasKeys(config, nextConfig, providerId, aliasPrefix) {
169206
170072
  const keys = generatedProviderAliasKeys(config, providerId, aliasPrefix);
169207
170073
  for (const key of providerAliasKeys(nextConfig, providerId)) keys.add(key);
@@ -169233,6 +170099,9 @@ function clampDanglingDefault(config) {
169233
170099
  config.defaultThinking = void 0;
169234
170100
  }
169235
170101
  }
170102
+ function clearDefaultThinkingWhenDefaultRemoved(config, previousDefaultModel) {
170103
+ if (previousDefaultModel !== void 0 && config.defaultModel === void 0) config.defaultThinking = void 0;
170104
+ }
169236
170105
  function pickDefaultModel(config, providerId, models) {
169237
170106
  const firstModel = models[0];
169238
170107
  if (firstModel === void 0) return "";
@@ -169274,6 +170143,7 @@ async function refreshAllProviderModels(host) {
169274
170143
  restoreProviderAliases(next, preserveUserProviderAliases(config, KIMI_CODE_PROVIDER_NAME, refreshedAliasKeys));
169275
170144
  restoreDefaultSelection(next, config.defaultModel, config.defaultThinking);
169276
170145
  clampDanglingDefault(next);
170146
+ clearDefaultThinkingWhenDefaultRemoved(next, config.defaultModel);
169277
170147
  if (providerModelsEqual(config, next, "managed:kimi-code", refreshedAliasKeys)) unchanged.push(KIMI_CODE_PROVIDER_NAME);
169278
170148
  else {
169279
170149
  const { added, removed } = computeChanges(collectModelIdsForAliases(config, refreshedAliasKeys), collectModelIdsForAliases(next, refreshedAliasKeys));
@@ -169325,6 +170195,7 @@ async function refreshAllProviderModels(host) {
169325
170195
  restoreProviderAliases(next, preserveUserProviderAliases(config, providerId, refreshedAliasKeys));
169326
170196
  restoreDefaultSelection(next, config.defaultModel, config.defaultThinking);
169327
170197
  clampDanglingDefault(next);
170198
+ clearDefaultThinkingWhenDefaultRemoved(next, config.defaultModel);
169328
170199
  if (providerModelsEqual(config, next, providerId, refreshedAliasKeys)) unchanged.push(providerId);
169329
170200
  else {
169330
170201
  const { added, removed } = computeChanges(collectModelIdsForAliases(config, refreshedAliasKeys), collectModelIdsForAliases(next, refreshedAliasKeys));
@@ -169355,26 +170226,55 @@ async function refreshAllProviderModels(host) {
169355
170226
  if (isOpenPlatformId(providerId)) continue;
169356
170227
  const source = readCustomRegistrySource(providerConfig);
169357
170228
  if (source === void 0) continue;
169358
- const key = `${source.url}${source.apiKey}`;
170229
+ const key = customRegistrySourceKey(source);
170230
+ const sourceKey = customRegistrySourceCredentialKey(source);
169359
170231
  const entry = customSources.get(key);
169360
- if (entry !== void 0) entry.providerIds.push(providerId);
169361
- else customSources.set(key, {
169362
- source,
170232
+ if (entry !== void 0) {
170233
+ if (!entry.sourceKeys.has(sourceKey)) {
170234
+ entry.sources.push(source);
170235
+ entry.sourceKeys.add(sourceKey);
170236
+ }
170237
+ entry.providerIds.push(providerId);
170238
+ } else customSources.set(key, {
170239
+ sources: [source],
170240
+ sourceKeys: new Set([sourceKey]),
169363
170241
  providerIds: [providerId]
169364
170242
  });
169365
170243
  }
169366
- for (const { source, providerIds } of customSources.values()) try {
169367
- const entries = await fetchCustomRegistry(source);
170244
+ for (const { sources, providerIds } of customSources.values()) try {
170245
+ const { entries, source } = await fetchCustomRegistryFromSources(sources);
169368
170246
  const next = structuredClone(config);
169369
170247
  const changedProviders = [];
169370
- for (const providerId of providerIds) {
169371
- const entry = entries[providerId];
169372
- if (entry === void 0) continue;
170248
+ const providersToRemoveBeforeSet = /* @__PURE__ */ new Set();
170249
+ let hasUnreportedConfigChange = false;
170250
+ const remoteEntries = Object.values(entries);
170251
+ const remoteEntriesByProviderId = new Map(remoteEntries.map((entry) => [entry.id, entry]));
170252
+ const providerIdsToSync = new Set(providerIds);
170253
+ for (const entry of remoteEntries) providerIdsToSync.add(entry.id);
170254
+ for (const providerId of providerIdsToSync) {
170255
+ const entry = remoteEntriesByProviderId.get(providerId);
170256
+ if (entry === void 0) {
170257
+ const oldIds = collectModelIdsForAliases(config, providerAliasKeys(config, providerId));
170258
+ removeCustomRegistryProvider(asManaged(next), providerId);
170259
+ changedProviders.push({
170260
+ providerId,
170261
+ providerName: providerId,
170262
+ added: 0,
170263
+ removed: oldIds.size
170264
+ });
170265
+ providersToRemoveBeforeSet.add(providerId);
170266
+ continue;
170267
+ }
170268
+ const existed = config.providers[providerId] !== void 0;
169373
170269
  applyCustomRegistryProvider(asManaged(next), entry, source);
169374
170270
  const refreshedAliasKeys = providerRefreshAliasKeys(config, next, providerId, `${providerId}/`);
169375
- restoreProviderAliases(next, preserveUserProviderAliases(config, providerId, refreshedAliasKeys));
169376
- if (providerModelsEqual(config, next, providerId, refreshedAliasKeys)) unchanged.push(providerId);
169377
- else {
170271
+ if (existed) restoreProviderAliases(next, preserveUserProviderAliases(config, providerId, refreshedAliasKeys));
170272
+ if (existed && providerModelsEqual(config, next, providerId, refreshedAliasKeys) && providerConfigEqual(config, next, providerId)) unchanged.push(providerId);
170273
+ else if (existed && providerModelsEqual(config, next, providerId, refreshedAliasKeys)) {
170274
+ unchanged.push(providerId);
170275
+ providersToRemoveBeforeSet.add(providerId);
170276
+ hasUnreportedConfigChange = true;
170277
+ } else {
169378
170278
  const { added, removed } = computeChanges(collectModelIdsForAliases(config, refreshedAliasKeys), collectModelIdsForAliases(next, refreshedAliasKeys));
169379
170279
  changedProviders.push({
169380
170280
  providerId,
@@ -169382,19 +170282,26 @@ async function refreshAllProviderModels(host) {
169382
170282
  added,
169383
170283
  removed
169384
170284
  });
170285
+ if (existed) providersToRemoveBeforeSet.add(providerId);
169385
170286
  }
169386
170287
  }
169387
- if (changedProviders.length > 0) {
170288
+ if (changedProviders.length > 0 || hasUnreportedConfigChange) {
169388
170289
  restoreDefaultSelection(next, config.defaultModel, config.defaultThinking);
169389
170290
  clampDanglingDefault(next);
169390
- for (const { providerId } of changedProviders) await host.removeProvider(providerId);
170291
+ clearDefaultThinkingWhenDefaultRemoved(next, config.defaultModel);
170292
+ for (const providerId of providersToRemoveBeforeSet) await host.removeProvider(providerId);
169391
170293
  config = await host.setConfig({
169392
170294
  providers: next.providers,
169393
170295
  models: next.models,
169394
170296
  defaultModel: next.defaultModel,
169395
170297
  defaultThinking: next.defaultThinking
169396
170298
  });
169397
- for (const change of changedProviders) changed.push(change);
170299
+ for (const change of changedProviders) changed.push({
170300
+ providerId: change.providerId,
170301
+ providerName: change.providerName,
170302
+ added: change.added,
170303
+ removed: change.removed
170304
+ });
169398
170305
  }
169399
170306
  } catch (error) {
169400
170307
  for (const providerId of providerIds) failed.push({
@@ -170991,173 +171898,6 @@ var EditorKeyboardController = class {
170991
171898
  }
170992
171899
  };
170993
171900
  //#endregion
170994
- //#region src/tui/components/messages/goal-markers.ts
170995
- /**
170996
- * Low-profile transcript markers for the autonomous goal loop.
170997
- *
170998
- * Lifecycle changes (paused / resumed / cancelled) and `no_progress` verdicts
170999
- * render as a single dim line — `◦ Goal paused` — that expands (ctrl+o, shared
171000
- * with tool output) to show the reason when there is one. Terminal outcomes use
171001
- * the richer completion card (the `/goal` box), not this marker.
171002
- */
171003
- const HEAD_INDENT = " ";
171004
- const DETAIL_INDENT = " ";
171005
- var GoalMarkerComponent = class {
171006
- headline;
171007
- detail;
171008
- accentToken;
171009
- expanded = false;
171010
- marker;
171011
- textToken;
171012
- expandable;
171013
- indent;
171014
- leadingBlank;
171015
- constructor(headline, detail, accentToken, options = {}) {
171016
- this.headline = headline;
171017
- this.detail = detail;
171018
- this.accentToken = accentToken;
171019
- this.marker = options.marker ?? "◦";
171020
- this.textToken = options.textToken ?? "textDim";
171021
- this.expandable = options.expandable ?? true;
171022
- this.indent = options.indent ?? HEAD_INDENT;
171023
- this.leadingBlank = options.leadingBlank ?? false;
171024
- }
171025
- invalidate() {}
171026
- setExpanded(expanded) {
171027
- this.expanded = expanded;
171028
- }
171029
- render(width) {
171030
- const dot = currentTheme.fg(this.accentToken, this.marker);
171031
- const head = currentTheme.fg(this.textToken, this.headline);
171032
- if (!(this.detail !== void 0 && this.detail.length > 0)) return this.clampToWidth([`${this.indent}${dot} ${head}`], width);
171033
- if (!this.expandable) return this.clampToWidth([`${this.indent}${dot} ${head}`], width);
171034
- if (!this.expanded) return this.clampToWidth([`${this.indent}${dot} ${head} ${currentTheme.fg("textMuted", "(ctrl+o)")}`], width);
171035
- const out = [`${this.indent}${dot} ${head}`];
171036
- const wrapWidth = Math.max(20, width - 4);
171037
- for (const line of wrap(this.detail, wrapWidth)) out.push(DETAIL_INDENT + currentTheme.fg("textDim", line));
171038
- return this.clampToWidth(out, width);
171039
- }
171040
- clampToWidth(lines, width) {
171041
- const withBlank = this.withLeadingBlank(lines);
171042
- if (width <= 0) return withBlank.map(() => "");
171043
- return withBlank.map((line) => truncateToWidth(line, width));
171044
- }
171045
- withLeadingBlank(lines) {
171046
- return this.leadingBlank ? ["", ...lines] : lines;
171047
- }
171048
- };
171049
- /**
171050
- * Builds a marker for a lifecycle change (paused / resumed / blocked), or `null`
171051
- * when the change should be silent (a `completion` change posts its own message,
171052
- * not a marker). `expanded` seeds the initial ctrl+o state.
171053
- */
171054
- function buildGoalMarker(change, expanded, actor) {
171055
- const spec = markerSpec(change, actor);
171056
- if (spec === null) return null;
171057
- const marker = new GoalMarkerComponent(spec.headline, spec.detail ?? change.reason, spec.accentToken, spec.options);
171058
- marker.setExpanded(expanded);
171059
- return marker;
171060
- }
171061
- function markerSpec(change, actor) {
171062
- if (change.kind === "lifecycle") switch (change.status) {
171063
- case "paused": return prominentMarker(pausedHeadline(change.reason, actor), "warning");
171064
- case "active": return prominentMarker(resumedHeadline(actor), "primary");
171065
- case "blocked": return {
171066
- headline: "Goal blocked",
171067
- accentToken: "warning"
171068
- };
171069
- default: return null;
171070
- }
171071
- return null;
171072
- }
171073
- function prominentMarker(headline, accentToken) {
171074
- return {
171075
- headline,
171076
- accentToken,
171077
- detail: void 0,
171078
- options: {
171079
- marker: STATUS_BULLET.trimEnd(),
171080
- textToken: accentToken,
171081
- expandable: false,
171082
- indent: "",
171083
- leadingBlank: true
171084
- }
171085
- };
171086
- }
171087
- function pausedHeadline(reason, actor) {
171088
- if (reason === "Paused after interruption") return "Goal paused due to user's interruption";
171089
- if (actor === "user") return "Goal paused by the user.";
171090
- if (reason?.startsWith("Paused ") === true) return `Goal ${lowercaseFirst(reason)}`;
171091
- if (reason !== void 0 && reason.length > 0) return `Goal paused: ${reason}`;
171092
- if (actor === "model") return "Goal paused by the agent.";
171093
- return "Goal paused";
171094
- }
171095
- function resumedHeadline(actor) {
171096
- if (actor === "user") return "Goal resumed by the user.";
171097
- if (actor === "model") return "Goal resumed by the agent.";
171098
- return "Goal resumed";
171099
- }
171100
- function lowercaseFirst(text) {
171101
- return text.length === 0 ? text : `${text[0].toLowerCase()}${text.slice(1)}`;
171102
- }
171103
- function wrap(text, width) {
171104
- const words = text.replaceAll(/\s+/g, " ").trim().split(" ");
171105
- const lines = [];
171106
- let current = "";
171107
- for (const word of words) {
171108
- const candidate = current.length === 0 ? word : `${current} ${word}`;
171109
- if (candidate.length > width && current.length > 0) {
171110
- lines.push(current);
171111
- current = word;
171112
- } else current = candidate;
171113
- }
171114
- if (current.length > 0) lines.push(current);
171115
- return lines.length > 0 ? lines : [""];
171116
- }
171117
- //#endregion
171118
- //#region src/tui/components/messages/status-message.ts
171119
- var StatusMessageComponent = class extends Container {
171120
- textComponent;
171121
- content;
171122
- color;
171123
- constructor(content, color) {
171124
- super();
171125
- this.content = content;
171126
- this.color = color;
171127
- const text = color === void 0 ? currentTheme.fg("textDim", content) : currentTheme.fg(color, content);
171128
- this.textComponent = new Text(` ${text}`, 0, 0);
171129
- this.addChild(this.textComponent);
171130
- }
171131
- invalidate() {
171132
- const text = this.color === void 0 ? currentTheme.fg("textDim", this.content) : currentTheme.fg(this.color, this.content);
171133
- this.textComponent.setText(` ${text}`);
171134
- super.invalidate();
171135
- }
171136
- };
171137
- var NoticeMessageComponent = class extends Container {
171138
- titleText;
171139
- detailText;
171140
- title;
171141
- detail;
171142
- constructor(title, detail) {
171143
- super();
171144
- this.title = title;
171145
- this.detail = detail;
171146
- this.addChild(new Spacer(1));
171147
- this.titleText = new Text(` ${currentTheme.fg("textStrong", title)}`, 0, 0);
171148
- this.addChild(this.titleText);
171149
- if (detail !== void 0 && detail.length > 0) {
171150
- this.detailText = new Text(` ${currentTheme.fg("textDim", detail)}`, 0, 0);
171151
- this.addChild(this.detailText);
171152
- }
171153
- }
171154
- invalidate() {
171155
- this.titleText.setText(` ${currentTheme.fg("textStrong", this.title)}`);
171156
- if (this.detailText !== void 0 && this.detail !== void 0) this.detailText.setText(` ${currentTheme.fg("textDim", this.detail)}`);
171157
- super.invalidate();
171158
- }
171159
- };
171160
- //#endregion
171161
171901
  //#region src/tui/utils/goal-completion.ts
171162
171902
  /**
171163
171903
  * Deterministic goal-completion text rendered by the TUI when the model marks a
@@ -171437,6 +172177,7 @@ var SubAgentEventHandler = class {
171437
172177
  name: event.name,
171438
172178
  argumentsPart: event.argumentsPart ?? null
171439
172179
  });
172180
+ else if (event.type === "tool.progress" && (event.update.kind === "stdout" || event.update.kind === "stderr") && event.update.text !== void 0) toolCall.appendSubToolLiveOutput(`${childAgentId}:${event.toolCallId}`, event.update.text);
171440
172181
  else if (event.type === "tool.result") toolCall.finishSubToolCall({
171441
172182
  tool_call_id: `${childAgentId}:${event.toolCallId}`,
171442
172183
  output: serializeToolResultOutput(event.output),
@@ -172174,12 +172915,15 @@ var SessionEventHandler = class {
172174
172915
  streamingUI.scheduleFlush();
172175
172916
  }
172176
172917
  handleToolProgress(event) {
172177
- if (event.update.kind !== "status") return;
172178
172918
  const text = event.update.text;
172179
172919
  if (text === void 0 || text.length === 0) return;
172180
172920
  const tc = this.host.streamingUI.getToolComponent(event.toolCallId);
172181
172921
  if (tc === void 0) return;
172182
- tc.appendProgress(text);
172922
+ if (event.update.kind === "status") {
172923
+ tc.appendProgress(text);
172924
+ return;
172925
+ }
172926
+ if (event.update.kind === "stdout" || event.update.kind === "stderr") tc.appendLiveOutput(text);
172183
172927
  }
172184
172928
  handleToolResult(event) {
172185
172929
  const { streamingUI } = this.host;
@@ -172888,6 +173632,9 @@ var SessionReplayRenderer = class {
172888
173632
  case "message":
172889
173633
  this.renderMessage(context, record.message);
172890
173634
  return;
173635
+ case "compaction":
173636
+ this.renderCompaction(context, record);
173637
+ return;
172891
173638
  case "goal_updated":
172892
173639
  this.renderGoalReplayRecord(context, record);
172893
173640
  return;
@@ -173050,6 +173797,28 @@ var SessionReplayRenderer = class {
173050
173797
  skillTrigger: skill.trigger
173051
173798
  });
173052
173799
  }
173800
+ renderCompaction(context, record) {
173801
+ this.flushAssistant(context);
173802
+ if (record.result === void 0) return;
173803
+ if (record.result === "cancelled") {
173804
+ this.host.appendTranscriptEntry({
173805
+ ...replayEntry(context, "status", "Compaction cancelled", "plain"),
173806
+ compactionData: {
173807
+ result: "cancelled",
173808
+ instruction: record.instruction
173809
+ }
173810
+ });
173811
+ return;
173812
+ }
173813
+ this.host.appendTranscriptEntry({
173814
+ ...replayEntry(context, "status", "Compaction complete", "plain"),
173815
+ compactionData: {
173816
+ tokensBefore: record.result.tokensBefore,
173817
+ tokensAfter: record.result.tokensAfter,
173818
+ instruction: record.instruction
173819
+ }
173820
+ });
173821
+ }
173053
173822
  renderGoalReplayRecord(context, record) {
173054
173823
  this.flushAssistant(context);
173055
173824
  const { change } = record;
@@ -173320,6 +174089,23 @@ function supportsOsc9Notification(env = process.env) {
173320
174089
  if (term === "xterm-kitty" || term === "xterm-ghostty") return true;
173321
174090
  return false;
173322
174091
  }
174092
+ /**
174093
+ * Best-effort detection of ConEmu-style OSC 9;4 progress support, driven
174094
+ * off well-known environment variables like `supportsOsc9Notification`.
174095
+ * The two allow-lists must stay separate: iTerm2 posts a desktop
174096
+ * notification for ANY `OSC 9;<payload>` it receives, so sending the 9;4
174097
+ * progress sequence there pops a "4;3" notification every keepalive tick.
174098
+ * Terminals outside this list simply get no progress reporting, which is
174099
+ * always safe.
174100
+ */
174101
+ function supportsTerminalProgress(env = process.env) {
174102
+ if ((env["WT_SESSION"] ?? "").length > 0) return true;
174103
+ if (env["ConEmuANSI"] === "ON") return true;
174104
+ const termProgram = env["TERM_PROGRAM"] ?? "";
174105
+ if (termProgram === "ghostty" || termProgram === "WezTerm") return true;
174106
+ if ((env["TERM"] ?? "") === "xterm-ghostty") return true;
174107
+ return false;
174108
+ }
173323
174109
  function isInsideTmux(env = process.env) {
173324
174110
  return (env["TMUX"] ?? "").length > 0;
173325
174111
  }
@@ -173967,7 +174753,7 @@ var StreamingUIController = class {
173967
174753
  * TasksBrowserApp. Snapshot view (no live tail) — content is fetched
173968
174754
  * once when the viewer opens.
173969
174755
  */
173970
- const ELLIPSIS$3 = "…";
174756
+ const ELLIPSIS$2 = "…";
173971
174757
  const STATUS_LABEL$1 = {
173972
174758
  running: "running",
173973
174759
  completed: "completed",
@@ -173989,12 +174775,12 @@ function statusColor$1(status) {
173989
174775
  function padToWidth$1(line, width) {
173990
174776
  const w = visibleWidth(line);
173991
174777
  if (w === width) return line;
173992
- if (w > width) return truncateToWidth(line, width, ELLIPSIS$3);
174778
+ if (w > width) return truncateToWidth(line, width, ELLIPSIS$2);
173993
174779
  return line + " ".repeat(width - w);
173994
174780
  }
173995
174781
  function fitExactly$1(line, width) {
173996
174782
  let s = line;
173997
- if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$3);
174783
+ if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$2);
173998
174784
  return padToWidth$1(s, width);
173999
174785
  }
174000
174786
  var TaskOutputViewer = class extends Container {
@@ -174153,7 +174939,7 @@ var TaskOutputViewer = class extends Container {
174153
174939
  * Data (tasks list, tail output) flows in via `setProps`; user actions
174154
174940
  * fire the `on*` callbacks back to the controller.
174155
174941
  */
174156
- const ELLIPSIS$2 = "…";
174942
+ const ELLIPSIS$1 = "…";
174157
174943
  const STATUS_LABEL = {
174158
174944
  running: "running",
174159
174945
  completed: "completed",
@@ -174201,13 +174987,13 @@ function singleLine(text) {
174201
174987
  function padToWidth(line, width) {
174202
174988
  const w = visibleWidth(line);
174203
174989
  if (w === width) return line;
174204
- if (w > width) return truncateToWidth(line, width, ELLIPSIS$2);
174990
+ if (w > width) return truncateToWidth(line, width, ELLIPSIS$1);
174205
174991
  return line + " ".repeat(width - w);
174206
174992
  }
174207
174993
  /** Fit `line` into exactly `width` columns, even after CJK-edge truncation. */
174208
174994
  function fitExactly(line, width) {
174209
174995
  let s = line;
174210
- if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$2);
174996
+ if (visibleWidth(s) > width) s = truncateToWidth(s, width, ELLIPSIS$1);
174211
174997
  return padToWidth(s, width);
174212
174998
  }
174213
174999
  function visibleTasks(tasks, filter) {
@@ -174468,7 +175254,7 @@ var TasksBrowserApp = class extends Container {
174468
175254
  const prefixWidth = visibleWidth(prefix);
174469
175255
  const descBudget = Math.max(0, innerWidth - prefixWidth - 1);
174470
175256
  if (descBudget < 4) return fitExactly(prefix, innerWidth);
174471
- const desc = truncateToWidth(singleLine(task.description) || (task.kind === "process" ? singleLine(task.command) : "") || "(no description)", descBudget, ELLIPSIS$2);
175257
+ const desc = truncateToWidth(singleLine(task.description) || (task.kind === "process" ? singleLine(task.command) : "") || "(no description)", descBudget, ELLIPSIS$1);
174472
175258
  return fitExactly(`${prefix} ${currentTheme.fg("text", desc)}`, innerWidth);
174473
175259
  }
174474
175260
  adjustScroll(visibleRows) {
@@ -174845,289 +175631,6 @@ var TasksBrowserController = class {
174845
175631
  }
174846
175632
  };
174847
175633
  //#endregion
174848
- //#region src/tui/components/editor/file-mention-provider.ts
174849
- const PATH_DELIMITERS = new Set([
174850
- " ",
174851
- " ",
174852
- "\"",
174853
- "'",
174854
- "="
174855
- ]);
174856
- const MAX_FALLBACK_SCAN = 2e3;
174857
- const MAX_FALLBACK_SUGGESTIONS = 50;
174858
- /**
174859
- * Kimi wrapper around pi-tui's combined autocomplete provider.
174860
- *
174861
- * File / folder mention behavior uses pi-tui's fd-backed provider when fd is
174862
- * available. While managed fd is downloading (or when it is unavailable), a
174863
- * small filesystem fallback keeps basic `@` file and folder completion usable.
174864
- * Ordinary path completion is still handled by pi-tui's readdir-backed path
174865
- * completer. This wrapper also keeps Kimi-specific slash-command guards.
174866
- */
174867
- var FileMentionProvider = class {
174868
- slashCommands;
174869
- workDir;
174870
- fdPath;
174871
- inner;
174872
- constructor(slashCommands, workDir, fdPath) {
174873
- this.slashCommands = slashCommands;
174874
- this.workDir = workDir;
174875
- this.fdPath = fdPath;
174876
- const expanded = [];
174877
- for (const cmd of slashCommands) {
174878
- expanded.push(cmd);
174879
- for (const alias of cmd.aliases ?? []) expanded.push({
174880
- ...cmd,
174881
- name: alias
174882
- });
174883
- }
174884
- this.inner = new CombinedAutocompleteProvider(expanded, workDir, fdPath);
174885
- }
174886
- async getSuggestions(lines, cursorLine, cursorCol, options) {
174887
- const currentLine = lines[cursorLine] ?? "";
174888
- const textBeforeCursor = currentLine.slice(0, cursorCol);
174889
- if (shouldSuppressLeadingWhitespaceSlashPath(textBeforeCursor, options.force)) return null;
174890
- if (shouldSuppressSlashArgumentCompletion(textBeforeCursor, currentLine.slice(cursorCol), options.force)) return null;
174891
- const atPrefix = extractAtPrefix(textBeforeCursor);
174892
- if (atPrefix !== null) {
174893
- if (this.fdPath === null) return getFsMentionSuggestions(this.workDir, atPrefix, options.signal);
174894
- try {
174895
- return await this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
174896
- } catch {
174897
- return getFsMentionSuggestions(this.workDir, atPrefix, options.signal);
174898
- }
174899
- }
174900
- if (!options.force && textBeforeCursor.startsWith("/")) {
174901
- if (textBeforeCursor.indexOf(" ") === -1) {
174902
- const tokens = textBeforeCursor.slice(1).trim().split(/\s+/).filter((t) => t.length > 0);
174903
- const matches = [];
174904
- for (const cmd of this.slashCommands) {
174905
- const nameScore = scoreTokens(tokens, cmd.name);
174906
- if (nameScore !== null) {
174907
- matches.push({
174908
- cmd,
174909
- score: nameScore,
174910
- viaAlias: false,
174911
- label: cmd.name
174912
- });
174913
- continue;
174914
- }
174915
- const aliases = cmd.aliases ?? [];
174916
- let bestAliasScore = null;
174917
- for (const alias of aliases) {
174918
- const aliasScore = scoreTokens(tokens, alias);
174919
- if (aliasScore !== null && (bestAliasScore === null || aliasScore < bestAliasScore)) bestAliasScore = aliasScore;
174920
- }
174921
- if (bestAliasScore !== null) matches.push({
174922
- cmd,
174923
- score: bestAliasScore,
174924
- viaAlias: true,
174925
- label: `${cmd.name} (${aliases.join(", ")})`
174926
- });
174927
- }
174928
- matches.sort((a, b) => a.score - b.score || Number(a.viaAlias) - Number(b.viaAlias));
174929
- if (matches.length === 0) return null;
174930
- return {
174931
- items: matches.map((m) => ({
174932
- value: m.cmd.name,
174933
- label: m.label,
174934
- description: formatSlashCommandDescription(m.cmd)
174935
- })),
174936
- prefix: textBeforeCursor
174937
- };
174938
- }
174939
- }
174940
- try {
174941
- return await this.inner.getSuggestions(lines, cursorLine, cursorCol, options);
174942
- } catch {
174943
- return null;
174944
- }
174945
- }
174946
- applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
174947
- return this.inner.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
174948
- }
174949
- };
174950
- function extractAtPrefix(text) {
174951
- let tokenStart = 0;
174952
- for (let i = text.length - 1; i >= 0; i -= 1) if (PATH_DELIMITERS.has(text[i] ?? "")) {
174953
- tokenStart = i + 1;
174954
- break;
174955
- }
174956
- if (text[tokenStart] !== "@") return null;
174957
- return text.slice(tokenStart);
174958
- }
174959
- function getFsMentionSuggestions(workDir, atPrefix, signal) {
174960
- if (signal.aborted) return null;
174961
- const query = atPrefix.slice(1);
174962
- const candidates = collectFsMentionCandidates(workDir, signal);
174963
- if (candidates.length === 0 || signal.aborted) return null;
174964
- const ranked = rankFsMentionCandidates(candidates, query).slice(0, MAX_FALLBACK_SUGGESTIONS);
174965
- if (ranked.length === 0) return null;
174966
- return {
174967
- prefix: atPrefix,
174968
- items: ranked.map(toMentionItem)
174969
- };
174970
- }
174971
- function collectFsMentionCandidates(workDir, signal) {
174972
- const result = [];
174973
- const stack = [""];
174974
- while (stack.length > 0 && result.length < MAX_FALLBACK_SCAN) {
174975
- if (signal.aborted) break;
174976
- const relativeDir = stack.pop() ?? "";
174977
- const absoluteDir = relativeDir.length === 0 ? workDir : join$1(workDir, relativeDir);
174978
- let entries;
174979
- try {
174980
- entries = readdirSync(absoluteDir, { withFileTypes: true });
174981
- } catch {
174982
- continue;
174983
- }
174984
- for (const entry of entries) {
174985
- if (signal.aborted || result.length >= MAX_FALLBACK_SCAN) break;
174986
- if (entry.name === ".git") continue;
174987
- const relativePath = normalizePath(relativeDir.length === 0 ? entry.name : join$1(relativeDir, entry.name));
174988
- const isSymlink = entry.isSymbolicLink();
174989
- let isDirectory = entry.isDirectory();
174990
- if (!isDirectory && isSymlink) try {
174991
- isDirectory = statSync(join$1(workDir, relativePath)).isDirectory();
174992
- } catch {}
174993
- result.push({
174994
- path: relativePath,
174995
- isDirectory
174996
- });
174997
- if (isDirectory && !isSymlink) stack.push(relativePath);
174998
- }
174999
- }
175000
- return result;
175001
- }
175002
- function rankFsMentionCandidates(candidates, query) {
175003
- const lowerQuery = query.toLowerCase();
175004
- const scored = [];
175005
- for (const candidate of candidates) {
175006
- const score = scoreCandidate(candidate, lowerQuery);
175007
- if (score > 0) scored.push({
175008
- candidate,
175009
- score
175010
- });
175011
- }
175012
- scored.sort((a, b) => {
175013
- if (a.score !== b.score) return b.score - a.score;
175014
- if (a.candidate.isDirectory !== b.candidate.isDirectory) return a.candidate.isDirectory ? -1 : 1;
175015
- return a.candidate.path.localeCompare(b.candidate.path);
175016
- });
175017
- return scored.map((entry) => entry.candidate);
175018
- }
175019
- function scoreCandidate(candidate, lowerQuery) {
175020
- if (lowerQuery.length === 0) {
175021
- const depthPenalty = candidate.path.split("/").length - 1;
175022
- return (candidate.isDirectory ? 120 : 100) - depthPenalty;
175023
- }
175024
- const lowerPath = candidate.path.toLowerCase();
175025
- const lowerBase = basename$1(candidate.path).toLowerCase();
175026
- let score = 0;
175027
- if (lowerBase === lowerQuery) score = 100;
175028
- else if (lowerBase.startsWith(lowerQuery)) score = 80;
175029
- else if (lowerBase.includes(lowerQuery)) score = 50;
175030
- else if (lowerPath.includes(lowerQuery)) score = 30;
175031
- if (candidate.isDirectory && score > 0) score += 10;
175032
- return score;
175033
- }
175034
- function toMentionItem(candidate) {
175035
- const valuePath = candidate.isDirectory ? `${candidate.path}/` : candidate.path;
175036
- return {
175037
- value: valuePath.includes(" ") ? `@"${valuePath}"` : `@${valuePath}`,
175038
- label: `${basename$1(candidate.path)}${candidate.isDirectory ? "/" : ""}`,
175039
- description: valuePath
175040
- };
175041
- }
175042
- function normalizePath(path) {
175043
- return path.replaceAll("\\", "/");
175044
- }
175045
- function shouldSuppressLeadingWhitespaceSlashPath(textBeforeCursor, force) {
175046
- if (force === true) return false;
175047
- if (textBeforeCursor.startsWith("/")) return false;
175048
- return textBeforeCursor.trimStart().startsWith("/");
175049
- }
175050
- function shouldSuppressSlashArgumentCompletion(textBeforeCursor, textAfterCursor, force) {
175051
- if (force === true) return false;
175052
- if (!textBeforeCursor.startsWith("/")) return false;
175053
- if (!textBeforeCursor.includes(" ")) return false;
175054
- return textAfterCursor.trimStart().length > 0;
175055
- }
175056
- /**
175057
- * All tokens must fuzzy-match `text`; returns the summed score, or null when
175058
- * any token misses. An empty token list matches everything with score 0.
175059
- * Mirrors pi-tui fuzzyFilter's token semantics — keep in sync if it changes.
175060
- */
175061
- function scoreTokens(tokens, text) {
175062
- let score = 0;
175063
- for (const token of tokens) {
175064
- const m = fuzzyMatch(token, text);
175065
- if (!m.matches) return null;
175066
- score += m.score;
175067
- }
175068
- return score;
175069
- }
175070
- /**
175071
- * Mirrors CombinedAutocompleteProvider's description rendering so the
175072
- * intercepted name completion keeps showing the argument hint.
175073
- */
175074
- function formatSlashCommandDescription(cmd) {
175075
- const desc = cmd.description ?? "";
175076
- return (cmd.argumentHint ? desc ? `${cmd.argumentHint} — ${desc}` : cmd.argumentHint : desc) || void 0;
175077
- }
175078
- //#endregion
175079
- //#region src/tui/components/panes/activity-pane.ts
175080
- var ActivityPaneComponent = class extends Container {
175081
- constructor(options) {
175082
- super();
175083
- if (options.mode === "waiting" || options.mode === "tool") {
175084
- if (options.spinner !== void 0) {
175085
- this.addChild(new Spacer(1));
175086
- this.addChild(options.spinner);
175087
- }
175088
- return;
175089
- }
175090
- if (options.mode === "composing" && options.spinner !== void 0) {
175091
- this.addChild(new Spacer(1));
175092
- this.addChild(options.spinner);
175093
- }
175094
- }
175095
- };
175096
- //#endregion
175097
- //#region src/tui/components/panes/queue-pane.ts
175098
- const ELLIPSIS$1 = "…";
175099
- var QueuePaneComponent = class extends Container {
175100
- messages;
175101
- hint;
175102
- constructor(options) {
175103
- super();
175104
- this.messages = options.messages;
175105
- if (options.messages.length > 0) this.hint = options.isCompacting && !options.isStreaming ? " ↑ to edit · will send after compaction" : !options.canSteerImmediately ? " ↑ to edit · will send after current task" : " ↑ to edit · ctrl-s to steer immediately";
175106
- }
175107
- render(width) {
175108
- const accent = (text) => currentTheme.fg("accent", text);
175109
- const dim = (text) => currentTheme.fg("textDim", text);
175110
- const lines = [];
175111
- for (const item of this.messages) {
175112
- const singleLine = item.text.replaceAll(/\s+/g, " ").trim();
175113
- const prefix = ` ❯ `;
175114
- const truncated = truncateToWidth(singleLine, Math.max(1, width - visibleWidth(prefix)), ELLIPSIS$1);
175115
- lines.push(accent(prefix + truncated));
175116
- }
175117
- if (this.hint !== void 0) lines.push(dim(truncateToWidth(this.hint, width, ELLIPSIS$1)));
175118
- return lines;
175119
- }
175120
- };
175121
- //#endregion
175122
- //#region src/tui/utils/startup.ts
175123
- function combineStartupNotice(existing, next) {
175124
- if (existing !== void 0 && next !== void 0) return `${existing}\n${next}`;
175125
- return existing ?? next;
175126
- }
175127
- function isOAuthLoginRequiredError(error) {
175128
- return error.code === OAUTH_LOGIN_REQUIRED_CODE;
175129
- }
175130
- //#endregion
175131
175634
  //#region src/tui/reverse-rpc/approval/adapter.ts
175132
175635
  const DEFAULT_APPROVAL_CHOICES = [
175133
175636
  {
@@ -175538,144 +176041,6 @@ function createApprovalRequestHandler(controller, onResponse) {
175538
176041
  };
175539
176042
  }
175540
176043
  //#endregion
175541
- //#region src/tui/banner/banner-provider.ts
175542
- function normalizeTag(value) {
175543
- if (typeof value !== "string") return null;
175544
- const trimmed = value.trim();
175545
- return trimmed.length > 0 ? trimmed : null;
175546
- }
175547
- function normalizeText(value) {
175548
- if (typeof value !== "string") return null;
175549
- const trimmed = value.trim();
175550
- return trimmed.length > 0 ? trimmed : null;
175551
- }
175552
- function normalizeUtcDate(value) {
175553
- if (value.endsWith("Z")) return value;
175554
- if (/[+-]\d{2}:\d{2}$/.test(value)) return value;
175555
- return `${value}Z`;
175556
- }
175557
- function parseDate(value) {
175558
- if (typeof value !== "string" || value.length === 0) return null;
175559
- const normalized = normalizeUtcDate(value);
175560
- const date = new Date(normalized);
175561
- return Number.isNaN(date.getTime()) ? null : date;
175562
- }
175563
- function isWithinWindow(start, end, now) {
175564
- if (start !== null && now < start) return false;
175565
- if (end !== null && now > end) return false;
175566
- return true;
175567
- }
175568
- function meetsMinVersion(minVersion, clientVersion) {
175569
- if (minVersion === void 0 || minVersion === null) return true;
175570
- if (typeof minVersion !== "string" || minVersion.length === 0) return true;
175571
- const min = valid(minVersion);
175572
- const current = valid(clientVersion);
175573
- if (min === null || current === null) return false;
175574
- return gte(current, min);
175575
- }
175576
- function pickActiveBanner(json, clientVersion, now) {
175577
- if (json.banner_enabled !== true) return null;
175578
- if (!meetsMinVersion(json.banner_min_version, clientVersion)) return null;
175579
- if (!isWithinWindow(parseDate(json.banner_start_time), parseDate(json.banner_end_time), now)) return null;
175580
- const mainText = normalizeText(json.banner_maintext);
175581
- if (mainText === null) return null;
175582
- return {
175583
- tag: normalizeTag(json.banner_title),
175584
- mainText,
175585
- subText: normalizeText(json.banner_subtext)
175586
- };
175587
- }
175588
- function pickFallbackBanner(json, clientVersion, now, random) {
175589
- if (json.banner_fallback_enabled !== true) return null;
175590
- const list = Array.isArray(json.banner_fallback_list) ? json.banner_fallback_list : [];
175591
- const candidates = [];
175592
- for (const raw of list) {
175593
- if (typeof raw !== "object" || raw === null) continue;
175594
- const item = raw;
175595
- if (item.enabled !== true) continue;
175596
- if (!meetsMinVersion(item.banner_min_version, clientVersion)) continue;
175597
- const mainText = normalizeText(item.banner_maintext);
175598
- if (mainText === null) continue;
175599
- candidates.push({
175600
- tag: normalizeTag(item.banner_title),
175601
- mainText,
175602
- subText: normalizeText(item.banner_subtext)
175603
- });
175604
- }
175605
- if (candidates.length === 0) return null;
175606
- return candidates[Math.floor(random() * candidates.length)];
175607
- }
175608
- function selectBannerState(json, clientVersion, now, random) {
175609
- const typed = typeof json === "object" && json !== null ? json : {};
175610
- return pickActiveBanner(typed, clientVersion, now) ?? pickFallbackBanner(typed, clientVersion, now, random);
175611
- }
175612
- var BannerProvider = class {
175613
- clientVersion;
175614
- url;
175615
- constructor(clientVersion, url = KIMI_CODE_TIPS_BANNER_URL) {
175616
- this.clientVersion = clientVersion;
175617
- this.url = url;
175618
- }
175619
- async load(fetchImpl = fetch) {
175620
- try {
175621
- const controller = new AbortController();
175622
- const timeout = setTimeout(() => {
175623
- controller.abort();
175624
- }, 3e3);
175625
- const response = await fetchImpl(this.url, { signal: controller.signal });
175626
- clearTimeout(timeout);
175627
- if (!response.ok) return null;
175628
- return selectBannerState(await response.json(), this.clientVersion, /* @__PURE__ */ new Date(), Math.random);
175629
- } catch {
175630
- return null;
175631
- }
175632
- }
175633
- };
175634
- //#endregion
175635
- //#region src/tui/components/chrome/banner.ts
175636
- const PREFIX_STAR = "✦";
175637
- const PADDING = " ";
175638
- var BannerComponent = class {
175639
- state;
175640
- constructor(state) {
175641
- this.state = state;
175642
- }
175643
- invalidate() {}
175644
- render(width) {
175645
- const main = (s) => currentTheme.boldFg("textStrong", s);
175646
- const dim = (s) => currentTheme.fg("textDim", s);
175647
- if (width < 1) return [""];
175648
- const tagText = this.state.tag ?? "";
175649
- const tagLabel = tagText.length > 0 ? `${PREFIX_STAR} ${tagText}` : "";
175650
- const tagStyled = tagLabel.length > 0 ? currentTheme.boldFg("primary", tagLabel) : "";
175651
- const tagDisplay = tagStyled.length > 0 ? tagStyled + PADDING : "";
175652
- const tagWidth = visibleWidth(tagDisplay);
175653
- const showTag = tagWidth > 0 && tagWidth < width;
175654
- const bodyIndent = showTag ? " ".repeat(tagWidth) : "";
175655
- const descIndent = showTag ? " ".repeat(visibleWidth(PREFIX_STAR + PADDING)) : "";
175656
- const bodyContentWidth = width - (showTag ? tagWidth : 0);
175657
- const descContentWidth = width - (showTag ? visibleWidth(PREFIX_STAR + PADDING) : 0);
175658
- if (bodyContentWidth <= 0) return [""];
175659
- const mainSegments = this.state.mainText.split("\n");
175660
- const subSegments = this.state.subText ? this.state.subText.split("\n") : [];
175661
- const result = [];
175662
- for (let i = 0; i < mainSegments.length; i++) {
175663
- const wrapped = wrapTextWithAnsi(mainSegments[i], bodyContentWidth);
175664
- for (let j = 0; j < wrapped.length; j++) {
175665
- const boldLine = main(wrapped[j]);
175666
- if (i === 0 && j === 0 && showTag) result.push(tagDisplay + boldLine);
175667
- else result.push(bodyIndent + boldLine);
175668
- }
175669
- }
175670
- for (const sub of subSegments) {
175671
- const wrapped = wrapTextWithAnsi(sub, descContentWidth <= 0 ? bodyContentWidth : descContentWidth);
175672
- for (const line of wrapped) result.push(descIndent + dim(line));
175673
- }
175674
- result.push("");
175675
- return result;
175676
- }
175677
- };
175678
- //#endregion
175679
176044
  //#region src/tui/reverse-rpc/modal-coordinator.ts
175680
176045
  var ReverseRpcModalCoordinator = class {
175681
176046
  hooks;
@@ -175824,13 +176189,6 @@ function adaptQuestionAnswers(event, response) {
175824
176189
  } : null;
175825
176190
  }
175826
176191
  //#endregion
175827
- //#region src/tui/types.ts
175828
- const INITIAL_LIVE_PANE = {
175829
- mode: "idle",
175830
- pendingApproval: null,
175831
- pendingQuestion: null
175832
- };
175833
- //#endregion
175834
176192
  //#region src/utils/git/git-status.ts
175835
176193
  /**
175836
176194
  * Cached git branch + working-tree status for the footer/statusline.
@@ -176997,11 +177355,19 @@ function createTerminalState() {
176997
177355
  notificationKeys: /* @__PURE__ */ new Set(),
176998
177356
  focused: true,
176999
177357
  supportsOsc9: supportsOsc9Notification(),
177358
+ supportsProgress: supportsTerminalProgress(),
177000
177359
  insideTmux: isInsideTmux(),
177001
177360
  progressActive: false
177002
177361
  };
177003
177362
  }
177004
177363
  //#endregion
177364
+ //#region src/tui/types.ts
177365
+ const INITIAL_LIVE_PANE = {
177366
+ mode: "idle",
177367
+ pendingApproval: null,
177368
+ pendingQuestion: null
177369
+ };
177370
+ //#endregion
177005
177371
  //#region src/tui/tui-state.ts
177006
177372
  function createTUIState(options) {
177007
177373
  const initialAppState = options.initialAppState;
@@ -177215,6 +177581,15 @@ function sessionRowsForPicker(sessions, currentSessionId, currentSessionHasConte
177215
177581
  }));
177216
177582
  }
177217
177583
  //#endregion
177584
+ //#region src/tui/utils/startup.ts
177585
+ function combineStartupNotice(existing, next) {
177586
+ if (existing !== void 0 && next !== void 0) return `${existing}\n${next}`;
177587
+ return existing ?? next;
177588
+ }
177589
+ function isOAuthLoginRequiredError(error) {
177590
+ return error.code === OAUTH_LOGIN_REQUIRED_CODE;
177591
+ }
177592
+ //#endregion
177218
177593
  //#region src/tui/utils/terminal-focus.ts
177219
177594
  function installTerminalFocusTracking(state) {
177220
177595
  state.terminalState.focused = true;
@@ -177599,10 +177974,8 @@ var KimiTUI = class {
177599
177974
  try {
177600
177975
  const result = await this.authFlow.refreshProviderModels();
177601
177976
  for (const c of result.changed) {
177602
- const parts = [c.providerName];
177603
- if (c.added > 0) parts.push(`+${String(c.added)} model${c.added > 1 ? "s" : ""}`);
177604
- if (c.removed > 0) parts.push(`-${String(c.removed)} model${c.removed > 1 ? "s" : ""}`);
177605
- this.showStatus(parts.join(" · ") + ".");
177977
+ if (c.added <= 0) continue;
177978
+ this.showStatus(`${c.providerName} · +${String(c.added)} model${c.added > 1 ? "s" : ""}.`);
177606
177979
  }
177607
177980
  for (const f of result.failed) this.showStatus(`Skipped refreshing ${f.provider}: ${f.reason}`, "warning");
177608
177981
  } catch {}
@@ -177617,7 +177990,10 @@ var KimiTUI = class {
177617
177990
  this.bootstrapFromPicker();
177618
177991
  return;
177619
177992
  }
177620
- if (shouldReplayHistory) await this.sessionReplay.hydrateFromReplay(this.requireSession());
177993
+ if (shouldReplayHistory) {
177994
+ await this.sessionReplay.hydrateFromReplay(this.requireSession());
177995
+ this.applyStartupPermissionAndPlanToAppState();
177996
+ }
177621
177997
  const resumeState = this.session?.getResumeState();
177622
177998
  if (resumeState?.warning !== void 0) this.showStatus(`Warning: ${resumeState.warning}`, "warning");
177623
177999
  if (this.session !== void 0) this.sessionEventHandler.startSubscription();
@@ -177675,7 +178051,10 @@ var KimiTUI = class {
177675
178051
  }
177676
178052
  }
177677
178053
  } else session = await this.harness.createSession(createSessionOptions);
177678
- if (session !== void 0 && startup.model !== void 0 && isResumeStartup) await session.setModel(startup.model);
178054
+ if (session !== void 0 && shouldReplayHistory) {
178055
+ await this.applyStartupModesToResumedSession(session);
178056
+ if (startup.model !== void 0) await session.setModel(startup.model);
178057
+ }
177679
178058
  } catch (error) {
177680
178059
  if (!isOAuthLoginRequiredError(error)) throw error;
177681
178060
  this.authFlow.enterLoginRequiredStartupState();
@@ -177684,6 +178063,7 @@ var KimiTUI = class {
177684
178063
  if (session === void 0) throw new Error("Startup session was not initialized.");
177685
178064
  await this.setSession(session);
177686
178065
  await this.syncRuntimeState(session);
178066
+ this.applyStartupPermissionAndPlanToAppState();
177687
178067
  this.state.startupState = "ready";
177688
178068
  return shouldReplayHistory;
177689
178069
  }
@@ -178038,6 +178418,20 @@ var KimiTUI = class {
178038
178418
  goal: goalResult.goal
178039
178419
  });
178040
178420
  }
178421
+ async applyStartupModesToResumedSession(session) {
178422
+ const { startup } = this.options;
178423
+ if (startup.auto) await session.setPermission("auto");
178424
+ else if (startup.yolo) await session.setPermission("yolo");
178425
+ if (startup.plan) {
178426
+ if (!(await session.getStatus()).planMode) await session.setPlanMode(true);
178427
+ }
178428
+ }
178429
+ applyStartupPermissionAndPlanToAppState() {
178430
+ const { startup } = this.options;
178431
+ if (startup.auto) this.setAppState({ permissionMode: "auto" });
178432
+ else if (startup.yolo) this.setAppState({ permissionMode: "yolo" });
178433
+ if (startup.plan) this.setAppState({ planMode: true });
178434
+ }
178041
178435
  async activateRuntime() {
178042
178436
  const session = this.requireSession();
178043
178437
  await session.setPermission(this.state.appState.permissionMode);
@@ -178203,12 +178597,21 @@ var KimiTUI = class {
178203
178597
  this.sessionEventHandler.startSubscription();
178204
178598
  this.clearTranscriptAndRedraw();
178205
178599
  this.showStatus(`Started a new session (${session.id}).`);
178600
+ this.showConfigWarningsIfAny();
178601
+ }
178602
+ /** Surface config.toml load warnings (degraded or kept-previous config) in the status bar. */
178603
+ async showConfigWarningsIfAny() {
178604
+ try {
178605
+ const { warnings } = await this.harness.getConfigDiagnostics();
178606
+ for (const warning of warnings) this.showStatus(warning, "warning");
178607
+ } catch {}
178206
178608
  }
178207
178609
  createTranscriptComponent(entry) {
178208
178610
  if (entry.compactionData !== void 0) {
178209
178611
  const data = entry.compactionData;
178210
178612
  const block = new CompactionComponent(this.state.ui, data.instruction);
178211
- block.markDone(data.tokensBefore, data.tokensAfter);
178613
+ if (data.result === "cancelled") block.markCanceled();
178614
+ else block.markDone(data.tokensBefore, data.tokensAfter);
178212
178615
  return block;
178213
178616
  }
178214
178617
  switch (entry.kind) {
@@ -178475,6 +178878,7 @@ var KimiTUI = class {
178475
178878
  this.sessionEventHandler.syncAgentSwarmActivitySpinner(spinner);
178476
178879
  }
178477
178880
  syncTerminalProgress(active) {
178881
+ if (!this.state.terminalState.supportsProgress) return;
178478
178882
  if (this.state.terminalState.progressActive === active) return;
178479
178883
  this.state.terminal.setProgress(active);
178480
178884
  this.state.terminalState.progressActive = active;
@@ -178554,16 +178958,18 @@ var KimiTUI = class {
178554
178958
  }
178555
178959
  async showSessionPicker() {
178556
178960
  await this.fetchSessions();
178557
- this.mountSessionPicker(() => {
178961
+ this.mountSessionPicker({ onCancel: () => {
178558
178962
  this.hideSessionPicker();
178559
- });
178963
+ } });
178560
178964
  }
178561
178965
  async bootstrapFromPicker() {
178562
178966
  await this.fetchSessions();
178563
- this.mountSessionPicker(() => {
178564
- this.hideSessionPicker();
178565
- this.stop();
178566
- }, {
178967
+ this.mountSessionPicker({
178968
+ applyStartupModes: true,
178969
+ onCancel: () => {
178970
+ this.hideSessionPicker();
178971
+ this.stop();
178972
+ },
178567
178973
  onCtrlC: () => {
178568
178974
  this.state.editor.onCtrlC?.();
178569
178975
  },
@@ -178577,20 +178983,27 @@ var KimiTUI = class {
178577
178983
  this.state.activeDialog = null;
178578
178984
  this.restoreEditor();
178579
178985
  }
178580
- mountSessionPicker(onCancel, shortcuts = {}) {
178986
+ mountSessionPicker(options) {
178581
178987
  this.state.activeDialog = "session-picker";
178582
178988
  this.mountEditorReplacement(new SessionPickerComponent({
178583
178989
  sessions: this.state.sessions,
178584
178990
  loading: this.state.loadingSessions,
178585
178991
  currentSessionId: this.state.appState.sessionId,
178586
178992
  onSelect: (sessionId) => {
178587
- this.resumeSession(sessionId).then((switched) => {
178588
- if (switched) this.hideSessionPicker();
178993
+ this.resumeSession(sessionId).then(async (switched) => {
178994
+ if (!switched) return;
178995
+ if (options.applyStartupModes === true) {
178996
+ await this.applyStartupModesToResumedSession(this.requireSession());
178997
+ this.applyStartupPermissionAndPlanToAppState();
178998
+ }
178999
+ this.hideSessionPicker();
179000
+ }).catch((error) => {
179001
+ this.showError(`Failed to apply startup flags: ${formatErrorMessage$2(error)}`);
178589
179002
  });
178590
179003
  },
178591
- onCancel,
178592
- onCtrlC: shortcuts.onCtrlC,
178593
- onCtrlD: shortcuts.onCtrlD
179004
+ onCancel: options.onCancel,
179005
+ onCtrlC: options.onCtrlC,
179006
+ onCtrlD: options.onCtrlD
178594
179007
  }));
178595
179008
  }
178596
179009
  showApprovalPanel(payload) {
@@ -178718,6 +179131,7 @@ async function runShell(opts, version, runOptions = {}) {
178718
179131
  return;
178719
179132
  }
178720
179133
  const config = await harness.getConfig();
179134
+ for (const warning of (await harness.getConfigDiagnostics()).warnings) configWarning = combineStartupNotice(configWarning, warning);
178721
179135
  const configMs = Date.now() - configStartedAt;
178722
179136
  const tui = new KimiTUI(harness, {
178723
179137
  cliOptions: opts,