Compare commits

...

127 commits

Author SHA1 Message Date
qer
d9ec566e51
docs(changelog): sync 0.34.0 from apps/kimi-code/CHANGELOG.md (#2704)
Some checks are pending
CI / test (5) (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
* docs(changelog): sync 0.34.0 from apps/kimi-code/CHANGELOG.md

* chore: link
2026-08-06 22:55:50 +08:00
wenhua020201-arch
51ef78b8c9
docs: add Official Plugins section with WebBridge and Computer Use (#2653)
* docs: add Official Plugins section with WebBridge and Computer Use

Group the three official capabilities (Kimi Datasource, Kimi WebBridge,
Kimi Computer Use) under a new Official Plugins section on the plugins
page, with a single shared install/upgrade flow. Add an authorization
walkthrough screenshot for Computer Use and regroup the Datasource
coverage table by category with named data sources.

* docs: add browser extension install steps for Kimi WebBridge

Installing via /plugins is not enough on its own: AI can only drive the
browser after the Kimi WebBridge extension is present. Document both
install paths (Chrome Web Store / Edge Add-ons, and manual load-unpacked
via chrome://extensions with Developer mode) plus a quick way to verify.

* docs: split WebBridge manual install into illustrated steps

Break the manual extension install into numbered steps with per-step
screenshots: enable Developer mode on chrome://extensions, then load the
unpacked kimi-webbridge-extension folder.

* docs: tighten WebBridge install screenshots to the relevant area

* docs: add WebBridge ready-state verification screenshot

* docs: note WebBridge's two-part install in the shared install steps

* docs: even out WebBridge install screenshot edges

* docs: replace WebBridge install screenshots with clean crops

Re-shoot source images: split the two-step manual install guide into
per-step screenshots with clean edges, and replace the ready-state popup
screenshot with the toolbar-icon success indicator.

* docs: use newly provided WebBridge step screenshots

* docs: sharpen Computer Use auth screenshot and center it

Replace the downscaled auth-window image with a crisp native capture,
constrain its display width to 380px, and center it on the page. Also
move the WebBridge two-part install note into an info callout directly
under the shared install steps.

* docs: drop the coverage start year from the Datasource table

* docs: spell out the two WebBridge extension install options

* docs: show the Kimi Code toggle enabled in the Computer Use auth screenshot

* docs: show version badges for WebBridge and Computer Use, rework Computer Use scenarios

Add version badges next to all three official plugin names. Rewrite the
Computer Use capability list around verified task shapes and add a
warning callout for operations that should not be delegated. Keep the
final WebBridge install step inside the numbered list.

* docs: add Windows (WinCU) notes to Computer Use

Computer Use now ships a Windows runtime with a different install path
and behavior: it may briefly take over the real mouse and keyboard
instead of running fully in the background. Document the install
command, system requirements, permission model, and privilege matching,
and stop claiming the feature is macOS-only.

* docs: break up the plugin manager wall of text

Split the Installation and Management paragraph into bullets, drop the
parts duplicated by the Official Plugins section (including the outdated
macOS-only note), and link to that section instead.

* docs: list the plugin manager tabs and drop the tab-behavior block

* docs: give the WebBridge extension install section an English anchor

* docs: restore the /reload or /new activation step for official plugins

* docs: align the plugins page wording with the published docs site

* chore: retrigger CI

---------

Co-authored-by: qer <wbxl2000@outlook.com>
2026-08-06 22:51:27 +08:00
github-actions[bot]
f0614c53e5
ci: release packages (#2641)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-06 21:56:49 +08:00
Haozhe
794714ebef
fix(agent-core-v2): gate plugin changes behind session baselines and reminders (#2702)
* fix(agent-core-v2): gate plugin changes behind session baselines and reminders

- capture a per-session MCP server baseline (ISessionMcpHandle.isBaselineServer)
  so servers added mid-session (plugin install, mcp.json edit) never register
  tools in live sessions; they take effect on /new, /reload, or resume, while
  removed servers stay tombstoned and fail calls with a removal notice
- stop rebuilding the system prompt on plugin-source catalog changes: the
  frozen skill listing and plugin sections cannot move anyway, and the rebuild
  only churned the ${now} timestamp, invalidating the provider prompt cache
- freeze the Agent tool description's catalog profile list once the session
  catalog has loaded, keeping the tools payload byte-stable across mutations
- append a plugin_change system reminder to live sessions on plugin mutations
  (new IPluginService.onDidMutate; explicit reloadPlugins does not raise it)
- revert the TUI hint to "Run /new or /reload to apply plugin changes." and
  update the plugin/MCP docs and changesets to the corrected contract

* fix(agent-core-v2): import LifecycleScope from app/scopes in sessionOutcomeMirror

#2666 imported LifecycleScope from #/_base/di/scope, which does not export
it (it lives in #/app/scopes), breaking the package build and typecheck on
main.

* fix(agent-core-v2): close the mutation-driven session-start refresh and overlay baseline leaks

Codex review on the PR found two contract leaks:

- a plugin mutation re-pulls the plugin skill source, and the existing
  catalog listener answered with a fresh plugin_session_start reminder —
  injecting the newly installed plugin's instructions into the live session
  alongside (and contradicting) the plugin_change notice. The session-start
  refresh now skips mutation-driven catalog changes (one per mutation,
  counted; explicit reloads keep the old refresh behavior).
- a session created with ephemeral mcpServers kept its MCP baseline open
  until the overlay connect finished; a workspace server added in that
  window (plugin install, config edit) leaked into the live session through
  the merged view. The overlay handle's baseline now freezes on the
  workspace manager's initial load, with the ephemeral names baseline by
  construction.

* fix(agent-core-v2): drop duplicate LifecycleScope import in sessionOutcomeMirror test

---------

Signed-off-by: Haozhe <yanghaozhe@moonshot.ai>
2026-08-06 21:11:03 +08:00
qer
fa3325404b
fix(agent-core-v2): import LifecycleScope from the L3 scopes module (#2703)
The L3 unit layer refactor moved LifecycleScope out of the _base DI kernel
into the app tier, leaving the session outcome mirror with a stale import
that broke typecheck and import-time evaluation on main.
2026-08-06 20:56:54 +08:00
qer
03aa66ca0c
fix(tui): show WebBridge setup steps after install (#2692)
* fix(tui): show WebBridge setup steps after install

* fix(tui): scope WebBridge hint to capability install

* fix(tui): format WebBridge setup links

* fix(tui): align WebBridge setup with live reload

* fix(tui): retain WebBridge session activation step

* fix(tui): compact WebBridge setup links

* fix(tui): list WebBridge setup links clearly

* fix(tui): show clickable WebBridge URLs

* Revert "fix(tui): show clickable WebBridge URLs"

This reverts commit 373ffae4b8cf4256131002b990f9a1869e9ee156.

* fix(tui): restore WebBridge setup heading
2026-08-06 20:19:32 +08:00
qer
335588e259
feat(agent-core-v2): persist the last turn outcome into session metadata for cold listings (#2666)
* feat(agent-core-v2): persist the last turn outcome into session metadata for cold listings

A cold session (no live handle) reported no lastTurnReason, so after a
server restart the session list could not mark a session whose last turn
failed until it was opened and resumed.

A new Session-scope SessionOutcomeRecorder subscribes to the activity
aggregate's turn_ended changes and persists the outcome
(completed/failed) into the session metadata document; the summary
pipeline (mirror + cold reader) carries it as SessionSummary
.lastTurnReason, and toWireSession falls back to it when no live fact
exists. 'cancelled' is deliberately not persisted: it is also what an
in-flight turn ends with during scope disposal, and writing there races
the host's home-dir teardown.

Verified end to end with an isolated home and a dead provider: a turn
fails, the server restarts, and GET /sessions reports
last_turn_reason=failed without opening the session.

* fix(klient): carry lastTurnReason/lastTurnOutcome in the validated contracts

Review follow-up: zod strips unknown keys on parse, so the new outcome
fields never reached klient callers; add them to the session summary and
metadata/patch/key schemas (contract parity test covers the engine
mirror).

* fix(agent-core-v2): persist user-cancelled outcomes, never teardown aborts

Review follow-up: skipping every 'cancelled' left a stale earlier outcome
in the metadata (e.g. a prior failed reported for a session whose latest
turn was stopped by the user). The recorder now subscribes to the main
agent's turn.ended facts directly and keys on interruptReason:
user_cancelled is persisted like any other terminal state, while
programmatic aborts — including the cancel every in-flight turn suffers
during scope disposal — are never written, so no metadata write races the
host's home-dir teardown.

* fix(kap-server): only fall back to the persisted outcome for cold sessions

Review follow-up: a warm session that just started a new turn clears its
live lastTurn, and the unconditional ?? fallback would then report the
previous turn's persisted outcome for a turn that is still running.
SessionFacts now reports whether a live handle exists, and the wire
projection only reads the persisted value when the session is cold.

* docs(agent-core-v2): keep the outcome-recorder header at role level

* fix(agent-core-v2): settle turn outcomes on turn start and drain metadata writes on close

Review follow-ups:

- a new main turn now clears the persisted outcome (turn.started), so a
  process that dies mid-retry no longer reports the previous turn's
  terminal state for a turn that never ended
- the dedupe marker only advances after a successful write, so a failed
  persist no longer suppresses the next identical outcome
- session metadata writes are tracked in a module-level pending set with
  drainSessionMetadataWrites(), awaited by kap-server close alongside the
  mirror/query-store drains — an event-driven write (e.g. the outcome
  recorder) can no longer land in a session dir while the host removes it

* fix(agent-core-v2): track the metadata dispose flag locally

Disposable exposes no public isDisposed accessor; keep a class-local flag
set in the dispose override.

* fix(kap-server): drain session metadata writes before the mirror and disposal

A write still in flight when close() begins must settle before the mirror
flushes its summary into the read model and before scope disposal marks
the service disposed — not after.

* fix(agent-core-v2): reattach the recorder when the main agent is recreated

Review follow-up: a failed bootstrap still fires onDidCreate before the
handle is dropped; the subscription then pointed at a dead bus and the
guard blocked any later reattach. Track onDidDispose and reset so the
next main creation attaches cleanly.

* test(agent-core-v2): resolve the recorder through the scoped DI harness

Review follow-up: construct SessionOutcomeRecorder via registerScopedService
+ a Session-scope test host (stubbed lifecycle/metadata), so the test covers
the production registration path; add the durable-value adoption case.

* fix(agent-core-v2): unbreak CI — iterable Promise.all and the debug channel surface

- Promise.all takes the pending-writes set directly (oxlint error)
- the disposed flag moves into a _register'd marker instead of a public
  dispose() override, which the debug channels listing (and its test)
  correctly rejects as framework plumbing

* fix(kap-server): surface persisted failures on the v2 session status

The v2 list folds the outcome into activity.status, which previously
read only live facts — a cold session always looked idle. Cold sessions
now map a persisted failed outcome to status 'failed' (completed and
cancelled stay idle, matching the live fold); warm sessions are
unchanged, and the statuses filter inherits the mapping.

* refactor(agent-core-v2): name the persisted field lastTurnReason

Aligns with the established name for the same concept end to end
(activity view's lastTurnReason, the v1 wire's last_turn_reason, and the
SessionSummary mirror), instead of introducing a third variant.

* fix(agent-core-v2): drain pending metadata writes before session teardown

Review follow-up: closing/archiving a session right after a turn ended
could dispose the scope while the outcome write was still queued, and
delete() removes the session dir immediately after close. Await the
pending metadata writes before the handle goes away.

* fix(node-sdk): carry lastTurnReason through the SDK session summary

Review follow-up: the in-process SDK path maps the engine summary through
v2SummaryToSessionSummary, which dropped the new outcome field. Add it to
the public SessionSummary type and the mapper; the parity gate projects
it away (the v1 engine never records an outcome).

* fix(node-sdk): populate lastTurnReason on live SDK summaries

Review follow-up: resumeSession/reloadSession build their summary from the
live session's metadata document, which now carries the outcome — surface
it there too so the SDK reports it consistently for live and listed
sessions.

* fix(agent-core-v2): carry the last turn outcome across session forks

Review follow-up: fork skips state.json when copying the session dir, so
the fork's fresh metadata never had the outcome and a restart dropped a
marker the warm fork was still reporting. The fork's metadata patch now
inherits the source's lastTurnReason.

* fix(agent-core-v2): settle pending outcome writes before reading a fork source

Review follow-up: a fork requested right after the source's turn ended
could read the metadata before the recorder's queued write landed,
inheriting a stale or absent outcome. Drain pending metadata writes
first.

* fix(agent-core-v2): backfill restored outcomes into the session metadata

Review follow-up: for sessions whose last turn ended before this field
existed, the cold-resume seed restores the outcome into the activity view
without a turn.ended fact, so the recorder never persisted it and cold
listings stayed blank. The recorder now also watches the main agent's
activity updates and backfills the restored outcome when nothing is
persisted yet.

* fix(agent-core-v2): never backfill restored cancellations

Review follow-up: a restored 'cancelled' cannot be told apart from a
programmatic abort (the activity event carries no interruptReason), and
those are never persisted. Backfill now covers only completed/failed;
user stops are still persisted from the live turn.ended fact.

* refactor(agent-core-v2): rename the outcome recorder to outcome mirror

Mirror is the codebase's established term for a write side that reflects
live state into a store (SessionIndexMirror); Recorder has no precedent.

* fix(agent-core-v2): backfill without bumping recency; header-only comments

Review follow-ups:

- a mere resume must not float an old session to the top of the list:
  metadata updates accept touchUpdatedAt:false and the outcome mirror's
  backfill uses it (live outcome writes keep bumping — turn end is a
  recency moment)
- the mirror service's inline notes move into the file header per the
  package comment convention
- drop the redundant |undefined from the SDK's optional outcome field

* fix(node-sdk): read the live outcome for resumed session summaries

Review follow-up: on a fresh resume the restored outcome can still be
queued as a metadata backfill, so the document may lag a tick; the live
activity aggregate already holds it. Resume/reload summaries now prefer
the live value and fall back to the metadata field.

* fix(agent-core-v2): confine the outcome backfill to pure resumes

Review follow-up: the view publishes its turn.ended fold before this
mirror's own turn.ended handler runs, so a live ending reached the
backfill branch first and got persisted without the recency bump. The
backfill now only applies when no turn ever started in this process —
live endings always take the bumped write.

* fix(agent-core-v2): drain the session-index mirror before session teardown

Review follow-up: settling the metadata write alone left the fresh
summary in the mirror's pending queue, so a list right after close could
read a stale outcome from the read model. close/archive now also drain
ISessionIndexMirror. Test harnesses register a mirror stub for the new
dependency.

* docs(agent-core-v2): fold the metadata drain contract into the file header

* chore: include the SDK package in the changeset; fold the drain note into the header

* fix(agent-core-v2): backfill restored cancellations too, quietly

Review follow-ups: dropping every restored cancel loses legitimate user
stops whose live write never landed (or was rejected) before a restart —
cold surfaces never mark cancelled anyway, so healing them is harmless
and strictly more accurate. The metadata disposal note moves into the
file header per the comment convention.

* fix(node-sdk): prefer the live outcome over the index in SDK listings

Review follow-up: a live session that just started a new turn after a
failure can briefly keep the stale outcome in the index while the
mirror's clear is queued. listSessions now reads the live activity
aggregate for warm sessions, matching the kap-server cold-only fallback.

* fix(node-sdk): never read the metadata outcome for a live session

Review follow-up: with a retry in flight the live aggregate has no
outcome while the document may still hold the previous failure — the
fallback showed the stale one. Live summaries now take the live
aggregate's answer alone; the restored outcome is already seeded there
on resume.
2026-08-06 20:14:05 +08:00
liruifengv
e6e4ba2357
chore: sync web dist from code-app (#2697)
* chore: sync web dist from code-app

* chore: add changesets for the synced web UI changes

* chore: drop changesets already covered by the previous web bundle sync

* chore: correct the drop-folder changeset for web

* chore: drop the drop-folder changeset (desktop-only feature, no web announcement)
2026-08-06 19:58:01 +08:00
Haozhe
02c026d487
feat(agent-core-v2): tombstone removed MCP servers and freeze plugin prompt inputs (#2694)
* feat(mcp): tombstone removed MCP servers and apply plugin changes immediately (20 files)

- add 'removed' MCP server status: workspace config removals call markRemoved
  instead of remove, keeping tool registrations alive while short-circuiting
  calls with a removal notice
- fire onDidReload after every plugin mutation (install/enable/disable/remove)
  so workspace consumers refresh contributions immediately
- TUI renders the removed status in the MCP panel/startup summary and shows an
  apply-immediately hint on the v2 engine

* feat(agent-core-v2): freeze plugin prompt inputs for live agents (2 files)

- snapshot the model skill listing and plugin system-prompt sections on the
  first successful prompt build and reuse the frozen values for the agent's
  lifetime, so plugin install / enable / disable / remove / reload never
  rewrites a live agent's prompt (same keep-live-sessions-stable philosophy
  as the MCP tombstone)
- freeze only on success: a not-yet-ready skill catalog or a failed
  enabledSystemPrompts() read must not pin empty values for the agent's
  lifetime
- refreshSystemPrompt still rebuilds on catalog change events but reuses
  the frozen values, so the prompt only moves when non-plugin inputs change
  (AGENTS.md, [tools] section, session tool policy, compaction); new agents
  snapshot the then-current state

* chore(changeset): add changesets for MCP tombstone and frozen plugin prompt inputs

* docs: describe immediate plugin changes and the removed MCP status on the v2 engine

* fix(klient): mirror the removed MCP server status in the wire contract

* docs: drop the legacy-engine behavior notes from the plugin and MCP pages

* fix(agent-core-v2): freeze plugin sections only on a loaded snapshot

- enabledSystemPrompts() resolves to its consumption fallback (never
  rejects) while the initial plugin load has failed; freezing that empty
  read locked plugin sections out of the live agent even after a later
  successful reload
- expose hasLoadedSnapshot() on IPluginService so resolvePluginSections
  can tell a real empty snapshot from the fallback before freezing
2026-08-06 19:19:51 +08:00
liruifengv
cfd14a1fe2
feat(kap-server): accept attachments on skill activation (#2693)
* feat(kap-server): accept attachments on skill activation

The :activate endpoint only took {args?}, so REST clients (web/desktop
composers) could not attach uploads to a /skill invocation — attachments
were silently dropped at the edge.

- activateSkillRequestSchema gains an optional attachments field carrying
  the image/video/file subset of the prompt content wire shape.
- The skills route resolves them through the same edge pipeline as prompt
  submissions (validate file refs → materialize/compress → convert),
  extracted from routes/prompts.ts into lib/promptMedia.ts.
- AgentSkillService.activate appends the resolved parts after the rendered
  skill prompt in the activation's user message; SkillActivationInput
  gains an optional content field. The native RPC/TUI path is unchanged.
- Attachment failures map to 40407 file.not_found / 40001
  validation.failed, mirroring the prompts route.

* fix(kap-server): drop the unused parseKimiFileUrl import in promptMedia

* refactor: address review — header-only comments in the skill domain, provider id on protocol URL sources

- agent-core-v2 keeps comments solely in the top-of-file block (scoped
  guide): SkillActivationInput.content documented in the skill.ts header,
  the activate() note folded into the skillService.ts header.
- packages/protocol's image/video URL source gains the optional
  provider-issued id, matching the kap-server wire schema so parsing the
  public contract no longer strips it.

* fix(kap-server): validate the skill before materializing activation attachments

An unknown or non-user-activatable skill name with attachments ran the
media pipeline first, streaming bytes into the session/cache dirs and
compressing images for a request that activate() would reject with
40415/40912. The route now checks the session catalog up front (the
service still re-validates) so invalid activations leave no disk or CPU
side effects.
2026-08-06 19:09:32 +08:00
Haozhe
4d39f4fa6f
fix(agent-core-v2): omit max_context_tokens in REST session status when unknown (#2696)
- align the REST status rollup with the WS push: a bound alias that no
  longer resolves omits max_context_tokens instead of reporting 0 (0 is the
  engine's UNKNOWN_CAPABILITY marker, not a real limit)
- fall back to the default model's limit only when no model is bound,
  resolved through IModelService like the WS side
- mark max_context_tokens optional in the shared session status schema
2026-08-06 18:50:16 +08:00
Haozhe
8c766a6c30
feat(agent-core-v2): add the L3 unit layer and the Feature seam (#2678)
* feat(agent-core-v2): add the L3 unit layer and the Feature seam

- introduce the L3 Service/Fiber unit layer: the Service base class with this.provide/effect/on/get/ref capabilities, the fiber runtime with thenable FiberHandles, collection contribution points, and the per-scope-kind ScopeUnits materialization fold
- provide each scope's static registration batch as one atomic provideAll cascade transaction (waiting-area activation, sticky Failed on construction error)
- add the DI unit inspection surface: App-scope debug ledger / dependency graph / cascade history services and the kimi-inspect DI view
- add the Feature unit seam (IFeatureManager + feature assembly), port plan mode onto it, and add the contributed-command seam (agent-command domain + node-sdk RPC types)
- remove the legacy dep-graph tooling
- apply the header-only comment convention across src and test: strip non-header narration, keep the file header, tooling pragmas, and NOTE comments

* feat(kap-server): gate the event.di.* debug feed to kimi-inspect connections

- add an opt-in target set in SessionEventBroadcaster; the global fan-out
  now skips event.di.* frames for connections that never opted in, so
  kimi-web and other clients no longer receive the high-churn DI feed
- WsConnectionV1 opts a connection in when client_hello carries
  client_id 'kimi-inspect'; removeGlobalTarget drops the opt-in on close
- temporary gate until a client-declared event-type whitelist lands

* chore(agent-core-v2): fix oxlint errors in the DI unit layer

- build the live-ref container chain without aliasing this (no-this-alias)
- snapshot the materialized map with Array.from and document why the copy
  is required (no-useless-spread)

* test(klient): use string scope kinds in the lifecycle handle fakes

The engine's LifecycleScope is a string enum now; the facade test doubles
still returned the old numeric kinds and failed the handleWireSchema output
validation.

* build(nix): update the pnpmDeps fetch hash
2026-08-06 18:22:36 +08:00
qer
ef61084009
fix(kimi-code): select compatible PowerShell for Computer Use (#2686)
* fix(kimi-code): select compatible PowerShell for Computer Use

* fix(kimi-code): handle locked Computer Use plugin files

* fix(kimi-code): align Windows Computer Use name

* fix(agent-core-v2): reuse PowerShell fallback for detection

* fix(agent-core-v2): refresh ready Computer Use plugin
2026-08-06 18:08:28 +08:00
liruifengv
7b2784b9b7
feat: surface the bound model and thinking effort on subagent UIs (#2679)
* feat: surface the bound model on subagent UIs

The subagent.spawned event now carries the display-normalized model alias
(the derived __secondary__ entry resolves to its base alias), so clients can
show which model a subagent is bound to. The TUI subagent card, swarm panel
header, and background-agent entry show it at spawn; the WS snapshot roster
and REST /tasks (background/detached subagents) carry it too, keeping the
model visible across client reconnects.

* feat: carry the subagent thinking effort alongside the model

The spawned event, snapshot roster, and REST /tasks now also carry the
child's effective thinking effort (read from the child profile at spawn, the
same vocabulary as agent.status.updated). UIs show it only when it diverges
from the main session's current effort — an inherited level adds no
information, and 'off' is never shown.

* feat(tui): show the bound model and effort in the /tasks browser

The task browser's Detail pane renders Model and Effort rows for agent
tasks (raw alias and level — it is the inspector surface, so no diff
filtering), and its minimum height grows to fit the new rows. The values
were already persisted on SubagentTaskInfo; the TaskInfo union, its zod
schemas (protocol, kap-server, klient contract), and the v1 type
declaration now carry them so nothing strips them in transit.

* feat(tui): show concrete subagent effort levels unconditionally

Display rule simplified: any concrete effort tier (low/high/max/…) is
shown next to the model — including when it matches the main session's
level. Only the boolean states stay hidden: 'off' (no thinking) and 'on'
(generic thinking) carry no level information.

* docs: trim the changeset entry

* fix(tui): keep the model and effort on background-agent entries across resume

replayBackgroundProjection only copied agentId/parentToolCallId/
description, so a background subagent that outlived a resume lost its
model/effort on the later terminal transcript entry. The projection now
threads the persisted values (catalog-mapped model; boolean effort states
dropped), and session replay passes the loaded model catalog through.

* fix(agent-core-v2): normalize the derived secondary alias regardless of the flag

A child bound while the secondary-model experiment was on keeps
__secondary__ in its persisted binding; if the flag is later switched off
with the recipe still configured, resolveSecondaryModel() gated the
normalization and the sentinel leaked back onto resumed subagents.
subagentDisplayModel now reads the recipe straight from config (the flag
gates new bindings, not the interpretation of existing ones), which also
drops SessionSwarmService's now-unused IFlagService dependency. Also adds
the SDK package to the release: the new SubagentSpawnedEvent/AgentTaskInfo
fields are SDK-visible types.

* fix(agent-core-v2): normalize the status-frame model at the source

A derived-bound child republishes agent.status.updated right after spawn
with its raw modelAlias, which overwrote the spawned event's normalized
display model on single-subagent cards (swarm headers were first-wins and
escaped). emitStatusUpdated now maps through subagentDisplayModel, a no-op
for the never-derived main agent. Also moves the inline comments added by
this branch into top-of-file headers per the v2 comment convention.

* fix(tui): clamp the /tasks detail frame to the available body

At terminals near the minimum height the forced 10-row detail frame
overflowed the body and truncated the preview frame's border. The detail
height now caps out at whatever leaves the preview its borders plus one
content row, with a regression test at exactly MIN_HEIGHT.

* fix: normalize inherited derived aliases and keep model/effort on replayed terminal entries

- resolveSubagentBinding's caller-fallback branch also maps through
  subagentDisplayModel: a caller itself bound to the derived entry (a
  resumed subagent making a nested Agent call) no longer publishes
  __secondary__.
- The replayed background-task terminal notification builds its metadata
  with the persisted model (catalog-mapped) and concrete effort, matching
  the live completion path.
- Drops the inline comments this branch added inside v2 test bodies; the
  scenario context lives in the source file headers.
2026-08-06 16:59:45 +08:00
qer
013203421d
fix(tui): surface capability install errors (#2682)
Some checks are pending
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
2026-08-06 14:02:59 +08:00
liruifengv
3c75a27da6
feat(tui): add cache-expiry hint dialog for resumed and idle sessions (v2 engine) (#2646)
* feat(agent-core-v2): detect prompt-cache breaks from per-step usage and emit telemetry

Track consecutive turn-scoped LLM requests per agent; when the cache-read
token count drops by more than 5% and by more than 2000 tokens between
requests, log a debug line and emit cache_break_detected with both usages,
the drop ratio, and the interval. Operation requests (e.g. compaction) act
as a baseline barrier so expected drops are not reported.

* feat(tui): add cache-expiry hint dialog for resumed and idle sessions (v2 engine)

Resuming a long-idle session or submitting after a long idle stretch
re-sends the whole history with an expired context cache. Show a dialog
offering to compact, start a new session, continue as-is, or never ask
again (persisted as cache_expiry_hint in tui.toml). Thresholds come from
the client_configs endpoint (estimated_cache_duration) via a generic
per-name cached client; only OAuth-managed providers participate.

* fix(tui): preserve submit order and revalidate session in cache-hint flows

Cold-cache submits during the in-flight config fetch are now swallowed and
replayed through a FIFO chain, so a later prompt can never overtake the
stashed one. Both the resume and idle paths re-check the current session
after the async fetch: a switch mid-flight drops the dialog (resume) or
hands the stashed input back to the editor instead of sending it into the
wrong session (idle).

* chore(agent-core-v2): regenerate state manifest after merging main

* fix(tui): apply cache_expiry_hint on /reload and /reload-tui

* fix(agent-core-v2): skip unmeasured all-zero usage in cache break detection

* fix(tui): restore chained cache-hint submits when the dialog is not sent

When several submits are swallowed during the cold-config fetch and the
first dialog is dismissed (or its compact/new action fails), the stashed
inputs were restored while later chained submits were still released —
reordering the conversation. Chained submits now follow the fate of the
message that opened the dialog, and multiple restores append newline-joined
instead of overwriting the editor.

* fix(agent-core-v2): reset cache-break baseline on model change

Caches are per-model, so a cache-read drop after /model is expected, not a
break. The baseline now carries the model and only same-model records are
compared.

* fix(tui): only count LLM-activity replay records for the resume cache hint

The v2 resume replay also carries local-only state records (permission,
plan, config updates, approval results) that slash commands append without
an LLM request. Filter lastActiveAt to message/compaction records so a
recent local change no longer masks an expired cache.

* style(agent-core-v2): rewrite the cacheBreak impl header per package convention

State the domain role, collaborators, and scope instead of narrating
implementation steps; the behavior guards now live in the code alone.

* fix(tui): drop the resume cache hint when a turn started mid-fetch

The resume dialog is fire-and-forget over an async config fetch; if the
user already sent the first prompt by the time it resolves, mounting would
overlay an active turn and its actions would hit the live session. Re-check
streamingPhase/isCompacting after the await, next to the session check.

* style(agent-core-v2): trim the cacheBreak contract header to contract and scope

* refactor: report cache-break detection from the TUI client

Move the detector out of the engine so the telemetry event carries the
client's own identity (which client produced it is now attributable). The
TUI observes main-loop turn.step.completed usage directly, with the same
guards: first-step/unmeasured/all-zero records skipped, model change and
compaction reset the baseline. The agent-core-v2 cacheBreak module is
removed.

* chore: drop accidentally committed dist-web build output and ignore it

* chore: revert the dist-web ignore rule

* chore: restore dist-web to the tracked content from main

* feat(tui): record cache breaks caused by mid-session model/effort switches

A model or effort change mid-session busts the prompt-cache key — that is
a real cache break worth attributing, not noise. The baseline now carries
model and effort, the same-model exemption is gone, and cache_break_detected
reports prev/curr model and effort alongside both usages.

* chore(changeset): simplify the cache-expiry hint entry

* chore(changeset): trim the cache-expiry hint entry to one line

* fix(tui): cache-hint review follow-ups

- carry the pre-dialog media extraction through compact/new resends so
  pasted attachments survive the image-store clear on a new session
- reset the cache-break baseline after /undo — the context cut makes the
  next cache-read drop expected
- release the stashed submit when a foreground operation started during
  the cold-config fetch instead of mounting the dialog over it
- count a completed compaction as activity so the next submit is not
  judged against the pre-compaction timestamp

* chore(changeset): drop the v2-engine-only suffix

* fix(tui): seed the activity baseline when the resume check skips

* fix(tui): cache-hint review follow-ups

* fix(tui): record cache activity on completed steps, not turn begin

* feat(cli): persist the client-configs cache across restarts
2026-08-06 13:26:37 +08:00
liruifengv
713bf1a5a2
fix(kimi-code): render v2 background task notifications on session replay (#2677)
* fix(kimi-code): render v2 background task notifications on session replay

* chore: add changeset for v2 task notification replay fix
2026-08-06 13:00:53 +08:00
Haozhe
34c4181437
fix(kimi-code): keep kimi -p alive while background tasks are pending (#2675)
The 10-year default print wait ceiling (315360000s) overflowed Node's
setTimeout limit (2^31-1 ms) into a 1ms fire, so the steer/drain wait
returned instantly and kimi -p exited right after the main turn, killing
pending background tasks and subagents.

- add setClampedTimeout in agent-core-v2 _base, clamping delays to
  MAX_TIMER_DELAY_MS, and route every config-driven timer through it
  (timeoutOutcome, task wait/manager timeout, swarm attempt timeout)
- chunk the print turn-endings wait against the real deadline instead of
  returning null on the first clamped timer fire
- restore v1 semantics: a non-positive swarm subagent timeout is unbounded
- default print_wait_ceiling_s to 2147483s (~24.8 days, the timer maximum)
2026-08-06 11:02:34 +08:00
Haozhe
7bd3fd9f6e
feat(agent-core-v2): read UTF-16 text files by transcoding to UTF-8 (#2647)
- detect UTF-16 LE/BE from a BOM or a zero-byte parity heuristic
  (tolerant of CJK content), derived from VS Code's encoding detection
- Read tool and workspace fs.read transcode UTF-16 text to UTF-8
  instead of refusing it as binary; larger than 10 MiB still refused
- refuse other non-UTF encodings (e.g. GBK) with a clearer message
2026-08-06 10:39:21 +08:00
qer
d1ded01b7c
fix(agent-core-v2): seed the activity view's lastTurn from the persisted turn.ended record (#2648)
Some checks are pending
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(agent-core-v2): seed the activity view's lastTurn from the persisted turn.ended record

A cold-resumed agent seeded its activity view only from live loop/task
state, so the last turn's outcome was lost on a server restart: sessions
came back with no lastTurnReason, and clients could not surface a
previously failed turn (e.g. a provider 429 that killed the turn before
the restart).

The loop already persists the terminal turn.ended record (reason, error,
durationMs); fold the latest one into the TurnModel as lastEnded and have
AgentActivityView.seedFromLoop adopt it when no turn is active, so the
session work aggregate (and everything built on it) reflects the last
turn's outcome again after a cold start.

* fix(agent-core-v2): seed lastTurn on wire restore and add a changeset

Review follow-up: the agent scope (and with it this view) is constructed
before wire.restore() replays the journal, so a constructor-time read of
TurnModel.lastEnded always saw the initial state on a cold resume. Move
the wire-backed seed behind the onDidRestore hook (constructor seed kept
for views built after a restore), and drop the inline comments in favor
of the file header per the package comment convention.

* docs(agent-core-v2): trim the activityView header to role and collaborators

Review follow-up: the previous revision narrated the restore-hook
mechanics in the header; the package convention keeps headers at the
module's external role plus collaborators, so drop the implementation
narrative.

* fix(agent-core-v2): keep TurnModel.lastEnded across clock advances

Review follow-up: advanceTurnClock built a fresh state object without
spreading, so a new prompt or a queued cancel silently dropped the stored
last-ended outcome even though no new turn had ended — after a restart the
activity view would again find nothing to seed. Spread the prior state and
cover the prompt/queued-cancel/replace cycle with a model-level test.

* fix(agent-core-v2): clear the stored turn outcome once a newer turn starts

Review follow-up: with the clock advances preserving lastEnded, a prompt
persisted without its turn ever starting would leave the previous turn's
outcome to be seeded after a restart, reporting a stale result for a turn
that never ended. The loop-event fold now drops lastEnded as soon as a
newer turn's events land, while prompts and queued cancels keep it.

* docs(agent-core-v2): keep the turnOps header at the domain role

Review follow-up: the lastEnded keep/clear mechanics read as
implementation narrative in the header; the convention there is role and
collaborators only.
2026-08-06 01:07:22 +08:00
qer
68ba740ebf
feat(kimi-code): support Kimi Computer Use on Windows (#2652)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
2026-08-05 21:15:02 +08:00
Haozhe
2b893733f9
fix(kap-server): bypass Node arg quoting for explorer /select, on Windows (#2645)
explorer.exe parses its raw command line rather than argv, so Node's
default spawn quoting breaks the `/select,` argument whenever the path
contains spaces: the command line becomes `"/select,\"C:\...\""`, which
explorer rejects, silently opening the Documents folder instead of
selecting the file. Quote only the path portion and launch with
windowsVerbatimArguments so the command line keeps the documented
`/select,"C:\some dir\f.txt"` form.
2026-08-05 20:15:27 +08:00
Haozhe
510fbe7ec5
refactor(kap-server): wrap /api/v2/sessions in the v1 response envelope (#2644)
- return the domain-grouped page payload inside { code, msg, data,
  request_id } and carry business outcomes in code (40001 invalid
  params with details, 40922 page_token mismatch) instead of raw HTTP
  statuses plus an { error: { code, message } } body
- add ErrorCode.PAGE_TOKEN_MISMATCH (40922)
- register the route via defineRoute (shared runtime validation and
  envelope-wrapped OpenAPI docs); fold include-domain validation into
  the query schema and replace the preprocess/doc-twin pair with
  scalar-or-array union params
- update the kimi-inspect client to unwrap the envelope and sync the
  two AGENTS.md guides
2026-08-05 18:20:00 +08:00
Haozhe
6f1cd7ca22
feat: add v2 sessions API and spreadsheet-like session table in kimi-inspect (#2640)
- kap-server: add GET /api/v2/sessions with a domain-grouped response
  (workspace / meta / activity, opt-in git), status / archived /
  updated_after filters, three sort orders, and fingerprint-bound opaque
  cursor pagination
- kimi-inspect: rebuild the chat sidebar as a spreadsheet-like session
  table on the v2 endpoint — preset views (All / Opened / Archived /
  By workspace / Git), column visibility config, header sort toggles,
  cursor-paged Load more, and localStorage-persisted panel prefs
- live activity frames from the WS hub override the REST status badge;
  session created / meta-updated events invalidate the v2-sessions query
2026-08-05 17:28:11 +08:00
7Sageer
858812193a
fix(kimi-code): open /feedback to all signed-in users (#2639)
* fix(kimi-code): open /feedback to all signed-in users

Gate the command on holding a kimi-for-coding OAuth token instead of
the active model's provider, so signed-in users on API-key models can
also submit feedback through the authenticated channel. When signed
out, open the sign-up page alongside GitHub Issues.

Also harden the failure paths: a failing auth status lookup or a
rejected submit promise now falls back to GitHub Issues, while
attachment-stage failures degrade to a non-fatal partial failure
instead of triggering the fallback.

* fix(kimi-code): print sign-up and issue links for signed-out /feedback

Opening two browser pages at once is jarring; just print the links in
the transcript instead.
2026-08-05 17:27:54 +08:00
liruifengv
7c919f0376
docs(changelog): sync 0.33.0 from apps/kimi-code/CHANGELOG.md (#2636)
* docs(changelog): sync 0.33.0 from apps/kimi-code/CHANGELOG.md

* docs(changelog): move the v2 engine entry to Refactors and the trust prompt to Polish
2026-08-05 16:46:10 +08:00
github-actions[bot]
53c832dfdf
ci: release packages (#2592)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-05 16:21:32 +08:00
qer
2b3e9a9f79
fix(tui): clarify curated plugin marketplace (#2635) 2026-08-05 16:18:50 +08:00
liruifengv
7a631522fb
chore(changeset): trim the v2 engine entry and drop the custom identity entry (#2634) 2026-08-05 16:08:42 +08:00
liruifengv
421e8f8065
fix(minidb): silence the transform-types warning from the dev worker (#2632) 2026-08-05 15:53:13 +08:00
liruifengv
3bd098b806
chore(changeset): add changesets for the v2 TUI and web UI updates (#2630) 2026-08-05 15:27:27 +08:00
qer
75fe068a01
fix(cli): stabilize built-in capability installation (#2601)
* fix(cli): show built-in capabilities before the first session exists

The lazy-session refactor left capability calls going through
requireSession(), so on a session-less v2 startup /plugins reported the
capabilities unavailable and hid the built-in rows behind the promo.
Like plugin management, capability readiness and installs are app-global
on the v2 engine: the node-sdk harness gains a capability facade over
the global channel, and the TUI resolves session-or-harness for every
capability call.

* fix(cli): count the dev marketplace server as the default catalog

dev.mjs always points KIMI_CODE_PLUGIN_MARKETPLACE_URL at its own
repo-serving server, which the override gate mistook for a user-configured
marketplace and suppressed the built-in capability rows in every dev run.
The dev server now marks itself, and the gate treats that marked URL as
the default catalog while still honoring real overrides (slash-command
source, user-set env, KIMI_CODE_DEV_MARKETPLACE_URL).

* fix(cli): align built-in capability updates
2026-08-05 14:55:04 +08:00
Haozhe
f881cdd970
feat(cli): default CLI surfaces to the agent-core-v2 engine (#2627)
Some checks are pending
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
* feat(cli): default to agent-core-v2 engine with KIMI_CODE_LEGACY_FLAG opt-out

- invert the engine gate: isKimiV2Enabled() now returns true unless
  KIMI_CODE_LEGACY_FLAG is truthy; KIMI_CODE_EXPERIMENTAL_FLAG no longer
  selects the engine
- replace the experimental `kimi acp-v2` command with the native v2
  implementation as the default `kimi acp`; the legacy acp-adapter path
  remains under the legacy flag
- drop the acp-v2 experimental flag from the registry
- rename the dev:cli:v2 script to dev:cli:legacy
- update en/zh docs for the new default engine and the legacy flag

* feat(cli): route export and provider through the engine gate

- select the harness via isKimiV2Enabled(): agent-core-v2 by default,
  the legacy harness when KIMI_CODE_LEGACY_FLAG is truthy
- close the harness after each one-shot command so the v2 engine's
  watchers do not keep the process alive
- document both commands in the KIMI_CODE_LEGACY_FLAG env-var entry
2026-08-05 14:42:23 +08:00
7Sageer
e3570280bd
fix(agent-core-v2): bound the project skill-root watch fd footprint (#2612)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(agent-core-v2): bound the project skill-root watch fd footprint

The workspace skill-root source recursively watches the skill-root
candidates with chokidar, which holds one fs.watch fd per file and per
directory on macOS. A skill bundling a large runtime tree can exhaust
the process fd budget and break every subsequent spawn (EBADF).

Mirror the scanner's own pruning (node_modules / dot entries, scan
depth cap) in the watch filter, and add a signal mode to hostFsWatch:
rescan-style consumers get ONE native recursive fs.watch on
darwin/win32, whose fd footprint is constant in the subtree size.

* fix(agent-core-v2): align the skill watch with scanner semantics and harden signal mode

Review follow-up:

- The scanner probes every entry's direct SKILL.md before gating
  recursion, so the watch filter now keeps an excluded entry itself and
  its direct SKILL.md (keepEntryFile) instead of pruning them — skills
  under node_modules / dot directories keep their hot reload.
- The signal-mode native leg now owns its recovery: a native watch
  error fires one root invalidation and re-arms with capped exponential
  backoff; chokidar is used only where recursive fs.watch is
  unavailable, so a transient failure can neither silently end hot
  reload nor downgrade to the per-node watcher.
- Native event path resolution handles absolute filenames and the
  root-basename case, clamping out-of-root events to a root
  invalidation instead of dropping them.
- The event mapping is extracted into NativeSignalMapper with the stat
  call injected, so the native-branch decisions are unit-tested on any
  platform.

* Fix spawn EBADF issue on macOS for large file trees

The skill watcher no longer opens every file it watches, improving performance.

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* fix(agent-core-v2): harden native signal watch recovery

* fix(agent-core-v2): align skill watch with scanner traversal

* fix(agent-core-v2): make skill watch handoff converge

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-08-05 13:47:26 +08:00
Haozhe
2a4990182d
docs: slim root AGENTS.md, move deep package docs to package guides (#2626)
- move the kimi-inspect, kap-server, transcript, and minidb project-map
  entries into new package-level AGENTS.md files
- move the standalone Agent class rule to packages/agent-core/AGENTS.md
- merge root-only agent-core-v2 facts (MCP persistence, trust routes,
  seed contract examples) into packages/agent-core-v2/AGENTS.md
- compress the remaining long project-map entries to summaries with
  pointers, and add an anti-bloat rule for map entries
- drop the stale server-e2e entry; the package no longer exists
2026-08-05 13:43:33 +08:00
Haozhe
4e5f36aa66
test(agent-core-v2): stop the reconcile loop during the sessionIndex read baseline (#2625)
The baseline gates warm reads on behavioral work counts, including zero
directory listings inside each counting window. The service's background
reconcile loop (60s interval) runs an authoritative scan on every tick —
two storage.list calls — and once a slow CI runner stretches the test past
that interval, a tick lands inside a counting window on every attempt and
is attributed to the read under test, failing fsLists === 0 even with the
retry (observed on run 30975794688: 168s across two attempts, fsLists = 2
both times).

Freeze the loop right after prepare() via a new stopReconcileLoop() test
hook (sibling of reconcileNow/reprojectNow). The only remaining directory
listing sources inside a window are the fallback read paths themselves,
so a non-zero count is deterministically a real regression again.
2026-08-05 13:43:22 +08:00
liruifengv
541ddd2d89
chore(web): replace apps/kimi-web with the code-app web bundle (#2599)
* chore(web): remove apps/kimi-web in favor of the code-app web bundle

The web UI source now lives in the code-app repo (apps/web); this repo
only ships the prebuilt bundle at apps/kimi-code/dist-web, synced from
code-app via \.

- delete apps/kimi-web (source, tests, docs)
- root package.json: drop dev:web and the kimi-web typecheck leg
- apps/kimi-code: drop the workspace dep and the build-from-source step;
  replace copy-web-assets.mjs with check-web-assets.mjs so packaging
  fails fast when the committed bundle is missing
- CI: _native-build verifies the committed bundle instead of building
  from source; ci.yml and pkg-pr-new.yml drop the kimi-web legs
- docs: AGENTS.md project map, changeset README, gen-changesets and
  sync-changelog skills now key web UI entries on dist-web

* chore(web): stop ignoring dist-web now that the bundle is committed

The ignore entry predates the code-app sync flow, when dist-web was a
local build artifact. The bundle is now the canonical, committed form of
the web UI, so ignoring it only forces every sync to git add -f.

* docs(skills): drop the web-specific changeset and changelog rules

The web: prefix convention and the web-specific dedup guidance belonged
to the in-repo web app. With the source moved to code-app, web UI
changes follow the same generic rules as any other CLI-bundle change.

* chore(web): add changesets for the web UI changes in the bundle

* chore(web): collapse the bundle changesets into one umbrella entry

* ci: unbreak nix and lint after the kimi-web removal

- flake.nix: drop apps/kimi-web from the source fileset and package
  lists, and verify the committed dist-web in the native build phase
  instead of building the web app from source
- .oxlintrc.json: exclude dist-web (a committed build artifact) now
  that .gitignore no longer hides it from the linter

* ci(nix): update pnpmDeps hash for the post-kimi-web lockfile

* chore(web): resync dist-web from code-app main

Bundle rebuilt from code-app main (upstream parity ports #175, pinned
sidebar #176) with the CLI version 0.32.0 embedded.
2026-08-05 13:38:30 +08:00
Kai
e7d5a0aee7
test(agent-core-v2): give the sessionIndex list baseline CI headroom (#2615)
* test(agent-core-v2): give the sessionIndex list baseline CI headroom

* test(agent-core-v2): retry the sessionIndex baseline once to absorb runner load spikes

* test(agent-core-v2): gate the sessionIndex baseline on work counts, not wall clock

The baseline asserted wall-clock medians (list < 300ms, get/count < 50ms)
plus a relative time check — all still load-sensitive on shared CI runners.
Replace every time assertion with behavioral complexity assertions recorded
at the IQueryStore and fs seams: list must be served by bounded pageByColumn
fetches only, get is a single point lookup, count never touches the session
collection, no warm read enumerates session directories, and the per-op work
snapshot must be identical at 1k, 10k, and 50k sessions. Medians are still
logged for phase-to-phase comparison; the retry now absorbs a background
reconcile tick landing inside a counting window.

---------

Co-authored-by: haozhe.yang <yanghaozhe@moonshot.ai>
2026-08-05 10:59:28 +08:00
zy
2ee6e43124
fix(mcp): re-register the OAuth client when its redirect URI no longer matches (#2620)
The callback listener binds a random port per flow, while DCR
registration records the redirect URI of the flow that created it —
so every interactive authorization after the first was rejected with
"Invalid redirect URI", an error rendered only in the user's browser
while the client waited for a callback that never came. Detect the
mismatch before invoking auth() and drop the stale registration so the
flow re-registers with the current callback URI (v1 + v2).

Resolve #2606

Co-authored-by: zouying <zouying@moonshot.cn>
2026-08-05 10:24:24 +08:00
Kai
8db7d42f23
feat(tui): add /bug as an alias for /feedback (#2614)
Some checks are pending
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
2026-08-04 23:54:21 +08:00
Kai
98ee35afd2
feat(agent-core-v2): add custom agent identity (#2573)
* refactor(agent-core-v2): simplify context tags and shared copy

Rename the context-injection tags to `<skill-loaded>` and
`<plugin-instructions>`, drop the product prefix from the CronCreate tool
description and the default agent description, and point the MCP OAuth
callback page back to "your terminal" instead of naming one client.

The callback page is shared by the ACP host, the web UI, and embedding
hosts, so naming a single client was inaccurate there. The tags and the
two descriptions read exactly the same without the prefix. Verified no
runtime consumer matches the old tag names; the updated snapshots cover
the tool descriptions that changed.

* feat(agent-core-v2): add a switch for the product-documentation skills

Five builtin skills document this CLI itself — `update-config`,
`custom-theme`, `mcp-config`, `check-kimi-code-docs`, and
`import-from-cc-codex`. Their names and descriptions sit in the system
prompt on every turn, which is dead weight for runs that will never
reconfigure the CLI.

Add a top-level `builtin_product_skills` field (also settable through
`KIMI_CODE_BUILTIN_PRODUCT_SKILLS`) to drop them. On by default, so
nothing changes unless it is set; the trade when off is that the model
loses the guided flows for those tasks.

Filtering happens where the catalog is assembled — a later filter would
leave the skills advertised to the model. The whole section is one
scalar, so it exercises the section-level env binding branch and needs
its own strip: `stripEnvBoundFields` only walks object fields, so an env
override would otherwise be written back into `config.toml`.

* feat(agent-core-v2): add custom agent identity

Add an `[identity]` config section (`name`, optional `slug`, both also
settable through `KIMI_CODE_IDENTITY_NAME` / `KIMI_CODE_IDENTITY_SLUG`)
that sets the identity the agent presents: the name it calls itself in
the system prompt, the `User-Agent` product token sent to third-party
providers, and the client name announced to MCP servers. Leaving it
unset changes nothing.

Until now every one of these was fixed, which left no way to run the
agent as part of another product — an internal deployment, a fork with
its own branding, an embedding host.

The identity resolves inside the engine rather than being seeded by each
host, so it applies to every launch surface — including headless runs,
which today seed no display name at all and fall through to the built-in
default.

Two deliberate asymmetries:

- The display name is a filling value with a fallback chain (config >
  host-declared > the consumer's own default); the slug is a rewriting
  value with two states only, so with no identity configured the
  rewriting paths are equivalent to not existing.
- The rewrite happens in the outbound header assembly, the one layer
  that knows which vendor it is building for. Vendors declaring
  `hostHeaders: 'full'` keep the host's own product token, which that
  header set is built around and which backends key on; the configured
  identity applies to the third-party path.

Resolution is lazy throughout: config loads asynchronously, and a
constructor snapshot would freeze the pre-load value under some startup
orderings.

Two input edges the resolver has to absorb, since both would otherwise
reach the User-Agent builder and either break it or quietly rewrite the
header: blank and whitespace-only values read as unset in the file just
as they already did in the env, so a stray `name = ""` cannot claim an
identity; and a name that folds away to nothing under slug
normalization (a CJK-only name, say) falls back to a neutral token
rather than producing a blank product, which the builder rejects.

* fix(agent-core-v2): keep the file value when a scalar env binding fails to parse

`config.ts` documents that an env value failing its binding's `parse` is
ignored, and `applyEnvBindings` honors that for object fields by
assigning only when the resolved value is defined. `applySectionEnv`
returned the parse result straight through for whole-section scalar
bindings, so a blank or mistyped variable resolved to `undefined` and
cleared the configured file value instead of being ignored.

Nothing hit this before: every existing section either binds object
fields or is env-only. `builtin_product_skills` is the first
whole-section scalar binding, where exporting an empty or misspelled
`KIMI_CODE_BUILTIN_PRODUCT_SKILLS` would silently undo a configured
`false`.

* feat(agent-core-v2): extend the custom identity to discovery and global MCP

Two outbound paths still announced the built-in product name under a
configured identity:

- `DiscoveryService` read the host User-Agent straight from bootstrap
  args when refreshing provider models, so custom registries — which are
  third-party endpoints — saw the original token while chat requests to
  the same class of endpoint saw the configured one.
- `SDKRpcClientV2` builds its own global `McpOAuthService` plus a
  throwaway `McpConnectionManager` for server testing, neither of which
  goes through the workspace-owned manager that carries the resolver.

Both now resolve the identity from the App scope.

* refactor(agent-core-v2): neutralize remaining copy and align comments

The synthetic MCP authentication tool description is injected into the
model context and still named the product; it and the OAuth callback
pages now use client-neutral wording. "Return to your terminal" was no
improvement over naming a client — both assume what the host is, and
that page serves the ACP host, the web UI and embedding hosts alike.

Comments introduced by the identity work move into their module headers,
per the domain convention. Interface field docs stay: the rule names
functions, methods and statements, and field-level docs are established
across the codebase.

The new tests gain scenario headers and dispose the scoped hosts they
create, and the `[identity]` docs state which engine reads the section.

* fix(agent-core-v2): read the product-skill switch after config is ready

`BuiltinSkillSource` is the lowest-priority skill source, so the workspace
catalog loads it first — before `IConfigService` has finished loading — and
keeps the contribution it returns for the life of the handler, with no
reload path and no change event. Reading `builtin_product_skills` eagerly
therefore stranded the startup configuration: an explicit `false` could be
ignored for the whole process. `UserFileSkillSource` already awaits config
readiness for exactly this ordering; this source now does the same.

Also record the identity collaborator in the two module headers that gained
the dependency without documenting it, and scope the
`builtin_product_skills` docs to the engine that reads it, matching the
note the identity section already carries.

* fix(agent-core-v2): apply the product-skill switch to session-less listings

`builtin_product_skills = false` only reached the scoped skill source. The
SDK's `listWorkspaceSkills` and the server's `GET /workspaces/{id}/skills`
both composed the raw `BUILTIN_SKILLS` constant, and the web app feeds its
pre-session onboarding menu from that route — so the five product skills
stayed listed until a session existed, then vanished from the session's
catalog.

Move the decision into `visibleBuiltinSkills(enabled)` next to the constant
and route every consumer through it, reading the switch via the shared
`builtinProductSkillsEnabled`. Keeping "what counts as a product skill" in
one place is the point: three copies of the predicate would drift the next
time a builtin is added. The SDK listing also awaits config readiness,
which it did not do before.

* fix(node-sdk): await config before materializing the global MCP OAuth provider

`McpOAuthService` caches providers by store key and stamps the client name
when it first builds one, and the preceding `globalMcpConfig.get()` reads
`mcp.json` directly rather than through `IConfigService`. So a
`beginGlobalMcpServerAuth` call made right after the harness is created
could resolve the identity before config finished loading, pinning the
built-in label for the rest of the process — including the OAuth dynamic
registration a third-party MCP server records.

`testGlobalMcpServer` already awaited config readiness for its own reasons;
this path now does too.

* refactor(agent-core-v2): drop the unused builtin-skill registrar

`registerBuiltinSkills` stamped the raw constant into a catalog for "edge
composition without a Session" — exactly the shape that now has to respect
`builtin_product_skills`. It has no callers in v2 and is not exported from
the package index, so it was dead code that also stood as an invitation to
bypass the switch. v1 keeps its own copy.

Every remaining path composes builtins through `visibleBuiltinSkills`.

* fix(agent-core-v2): send the configured identity on custom-registry imports

`:import_registry` fetched a user-supplied third-party URL with a
hardcoded `kimi-code-kap-server` User-Agent, so the first request to a
registry announced the product while every scheduled refresh of the same
registry announced the configured identity. The hardcoded value was wrong
on its own terms too: that token names the server, and this path also runs
in the CLI.

Both services now project the identity through `identityUserAgent`, which
carries the two guards (no host header, or no identity) once instead of
per caller. The model catalog keeps an inline copy on purpose — kosong is
a foundational layer and must not import an app domain.

Sweeping the remaining outbound User-Agent sources found no further gaps:
WebFetch deliberately sends a Chrome-like UA, the models.dev catalog fetch
sends none from the CLI, and kap-server's `user-agent` reads are inbound.

* docs: scope the identity env vars and condense the changeset

The environment-variable reference advertised all three new variables
without noting that only the agent-core-v2 engine reads them; the
configuration page already carried that note. Added in both locales.

The changeset had grown into two paragraphs of implementation detail,
which is what would land in the CLI release changelog. `gen-changesets`
asks for one short sentence plus at most a one-line usage hint.

* docs(agent-core-v2): describe the identity as what the agent calls itself

The module headers had drifted into describing the feature by what it
keeps off the wire rather than what it configures. Reworded so they state
the capability: the identity is the name the agent uses for itself, and
the unset case is a no-op rather than something "safe". The product-skill
switch excludes skills rather than hiding them.

Wording only; behavior and structure unchanged.

* test(agent-core-v2): cover the identity on custom-registry imports

The import path switched from a hardcoded `kimi-code-kap-server` token to
the host User-Agent projected through the identity, but nothing asserted
it. Two cases pin both halves: a configured identity reaches the request,
and an unconfigured one leaves the host header intact — the second matters
because a single case would also pass if one hardcoded value had simply
replaced another.

Both fail against the previous implementation.

* fix(node-sdk): guard every global MCP OAuth path behind config readiness

`McpOAuthService` caches providers by store key and stamps the client name
when it first builds one, so any path that can materialize a provider has
to run after config has loaded. `beginGlobalMcpServerAuth` awaited
readiness, but `resetGlobalMcpServerAuth` reaches the same cache through
`invalidate()` -> `getProvider()` without waiting: resetting auth right
after the harness is constructed pinned the built-in client name, and the
await added to the begin path could not help because it then reused that
cached provider.

Rather than add the missing await, the accessor is now async and holds the
guard itself, so the service cannot be obtained before config is ready and
a future entry point cannot forget. The remaining `configReady` in
`testGlobalMcpServer` stays — that one is for its own `[mcp]` section read.

* fix(agent-core-v2): send the configured identity on models.dev requests

The directory fetch behind `listModelsDevProviders` / `getModelsDevProvider`
still hardcoded a `kimi-code-kap-server` User-Agent, so browsing or importing
from models.dev announced the built-in product — and claimed to be the server
even when running in the CLI. Only the custom-registry import had been fixed.

`getModelsDevCatalog` now takes the User-Agent from its caller: the module is
plain module-level state with no container access, and the value depends on
the host and the configured identity, which only the calling service can see.
All four third-party fetches in that service share one helper.

Where the host states no User-Agent, a neutral token stands in rather than
dropping the header — these are directories the service chooses to call, so
there is no host intent to preserve, unlike the provider requests the model
catalog assembles.

Both new tests fail against the previous hardcoded value.

* test(agent-core-v2): assert the product-skill set literally

The expected sets were derived from the same `productSpecific` field the
production filter reads, so a builtin silently losing its marker would just
move between sets and leave every assertion green — while staying visible to
the model once the switch is off. The five names are now literal, with a test
asserting the marked set matches them exactly.

Dropping the marker from one skill now fails four tests instead of none.

Also states the App scope in the identity contract header, per the domain's
comment convention for contract files.

* fix(agent-core-v2): normalize the host-declared display name too

Blank and padded values were normalized on the config side but not on the
host fallback, so an embedding host passing `displayName: "   "` rendered
"You are   ," into the system prompt, and a padded name kept its padding.
Same rule now applies to every source of the name.

Also names `agentIdentity` as the collaborator in the request-headers
adapter header, which described the value it obtains without saying which
domain resolves it.

The three new cases fail against the previous implementation.

* fix(agent-core-v2): keep the configured slug when the host sends no User-Agent

The neutral fallback added for hosts that state no `User-Agent` discarded a
configured identity along with it: `identityUserAgent` returns `undefined`
as soon as there is no host header to rewrite, so `?? DEFAULT_IDENTITY_SLUG`
sent the literal `agent` even when `[identity].slug` was set — precisely the
case that fallback exists to serve. The configured slug now stands on its
own, with the neutral token reserved for having neither.

The four combinations of (host header, configured slug) had three tests; the
missing one is the one that was wrong. It now fails without this change.

`outboundUserAgent` also awaits config readiness before reading the identity,
so a browse issued right after bootstrap cannot send the pre-load value — the
guard lives in the accessor rather than at its four call sites, matching how
the same race is handled elsewhere in this branch.

Both headers here and in `discoveryService` now name `agentIdentity` as the
collaborator resolving that token.

* test(acp-server): follow the renamed skill-activation tag

`acp-server` arrived on main after the tag rename, so its two assertions
still expected `kimi-skill-loaded` and failed once the branches met. Also
updates the web app's CSS comment, which named the old tag from the start
of this branch — a comment, so nothing ever failed on it.

Found by CI: the merge verification only ran agent-core-v2's suite, and
this package is neither a dependency nor a dependent of it.

* fix(agent-core-v2): present the configured slug on registry refreshes too

The previous round taught the import path to fall back to the configured
slug when the host states no `User-Agent`, but left the scheduled refresh
of the same registry on the bare projection — so one registry could see
`acme` on import and the runtime default on refresh.

Extracting `identityUserAgent` had made the two paths share a function
without sharing the policy. The choice itself is now the shared piece:
`identityUserAgentOrDefault` always yields a value, for the directories
this process chooses to call, while `identityUserAgent` stays the form
that rewrites only what the host already sends — what a provider request
needs, where the host's silence is its own choice.

* docs(agent-core-v2): move new member docs into the module headers

The domain's comment convention is absolute — comments live solely in the
top-of-file block — and I had read the "functions, methods, or statements"
clause as leaving interface members out. It does not: only 25 of 734 v2
sources carry an indented block, so the members I documented were the
exception, not the pattern.

Seven members across six files move into their headers. `types.ts` had no
header at all, so it gains one.

* fix(agent-core-v2): connect session MCP overlays after config is ready

The shared manager reaches `connectAll` through `initialize()`, which awaits
the config domain first; `sessionOverlay` called it straight away. A session
carrying ephemeral `mcpServers` created right after bootstrap therefore
resolved the client name before config had loaded and initialized under the
built-in one.

The blast radius is wider than that one connection: a remote server sends
the overlay through `hasTokens()`, which materializes an OAuth provider on
the *shared* service and caches it by store key — so the early name outlives
the connection that raced. The overlay now connects behind `mcpConfig.ready`,
leaving the returned readiness promise unchanged.

* fix(agent-core-v2): reload builtin skills when their switch changes

The workspace catalog keeps each source's contribution for the life of the
handler, so a `builtin_product_skills` toggle never reached an existing
handler's sessions. That was harmless while every surface read the same
constant — but routing the session-less listings through the config made the
two views disagree, since those read the switch on every call.

Follows `ExtraFileSkillSource`: subscribe to the owning section and fire
`onDidChange`, which the catalog already turns into a source reload. The
test asserts an unrelated section does not trigger it.

* fix(agent-core-v2): apply the identity to self-configured web services

`[services.moonshot_search]` and `[services.moonshot_fetch]` name their own
`base_url`, so both services can point at an endpoint the user chose — but
each forwarded the host request headers verbatim, sending the built-in
product token there under a configured identity.

Only the services-config path is rewritten; the managed OAuth path keeps the
host headers as they are, being the endpoint the session authenticated
against. The distinction is the same one the model catalog draws per vendor.

`identityHeaders` carries the rewrite across a whole header set, so this is
the fourth caller sharing the projection rather than repeating its guards.
A pair of tests pins both halves.

My earlier sweep classified these two as official by their names instead of
asking who chooses the URL, which is why they were missed. The contract
header is also condensed here, per the convention below.

* docs(agent-core-v2): condense the identity headers to their contracts

The comment convention is one sentence with two halves — comments live only
in the top-of-file block, *and* that block states the module's role without
narrating implementation. Moving the member docs up last round satisfied the
first and broke the second: the headers ended up spelling out the slug
folding algorithm, the strip mechanics, and the load order.

Kept what a caller or the next editor would get wrong without it (why the
value is read rather than snapshotted, what `undefined` obliges a consumer
to do, why this source waits for config). Dropped what the code already
says. 22/12/12/13 lines, against 53 in `catalogService.ts` — length was
never the problem.

* fix(agent-core-v2): rebuild active prompts when the builtin skills change

Reloading the catalog on a `builtin_product_skills` toggle left existing
agents holding the old listing: `AgentProfileService` refreshes the prompt
only for the plugin source, so a disabled switch kept advertising skills
that were gone, and enabling it left them missing until an unrelated
refresh.

The plugin source is special because it also contributes prompt sections
(#2314), and the file-backed sources are left out for cost — their fs
watches would rebuild every agent's prompt on each edit. The builtin source
has no watch: it changes only when its config switch is toggled, so it
belongs with the plugin source rather than with the file ones.

Subscribing to the catalog rather than the config section is load-bearing.
The catalog fires after the contribution is replaced, whereas a config
subscription would race the reload, and `resolveSkillListing` only awaits
the catalog's *initial* readiness — so the rebuilt prompt could read the
listing it was meant to replace.

The source id is a named constant now, so the subscription does not match
on a bare string.

* refactor(agent-core-v2): freeze the agent identity for the process lifetime

The identity is announced outward (MCP initialize, OAuth registration,
provider request logs) and cannot be re-announced, so mid-process changes
could only ever apply partially. Resolve it once when config first loads
and hold it for the life of the process: IAgentIdentity now hands out a
frozen snapshot via resolved()/current(), carrying finished products
(outbound User-Agent variants, rewritten header set) so call sites stop
composing host headers with the slug themselves. The kosong host-headers
port carries two finished layers and the catalog only picks one; consumers
gain no invalidation obligations because the value can never change after
the freeze. [identity] edits take effect on the next start (documented).

* fix(agent-core-v2): locate the User-Agent header case-insensitively

HTTP header names are case-insensitive, but the snapshot builder looked up
'User-Agent' by exact key: an embedding host spelling it 'user-agent' got no
third-party UA and kept its own product token on the services path even with
an identity configured. The builder now locates every case variant and
rewrites each in place, keeping the host's spelling. Also corrects the two
web-service headers that still described both paths as sending the bootstrap
headers, naming agentIdentity as the collaborator behind the config path,
and documents that a resumed session keeps its recorded system prompt.

* fix(agent-core-v2): attribute header provenance from the finished third-party layer

Inspection reconstructed the non-full host layer from the raw headers with an
exact-case 'User-Agent' lookup, so a host spelling the header 'user-agent'
got a resolved User-Agent with no provenance entry even though the runtime
sends the rewritten value. buildModel now captures the port's finished
third-party layer in the trace and attribution reads it, keeping inspect()
on the same resolution pass as get(). Also condenses the identity contract
header to its external role, and documents that an existing MCP OAuth
authorization keeps the client registration it was granted under (reset the
server's auth to register under the new identity).

* fix(agent-core-v2): keep web tool backends from racing the identity freeze

An env-configured [services] endpoint is visible before config finishes
loading, and FetchURLTool / WebSearchTool materialized their backends at
construction — so a fast bootstrap could hit the identity snapshot's
pre-freeze guard during agent creation, and the composed backend pinned
config and login state for the agent's lifetime against the service's
documented per-call resolution. Both tools now resolve their backend per
invocation, the WebSearch activation gate checks presence alone through the
new hasWebSearchProvider() (no provider composition, no identity read), and
bind() awaits the identity freeze before materializing the model, whose
resolution reads the identity through the host-headers port.
2026-08-04 22:35:15 +08:00
Haozhe
119a33f7f1
feat(minidb): persistent index generations and lifecycle hardening (#2604)
Some checks are pending
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* perf(minidb): skip idle everysec fsyncs and add lifecycle stats

- everysec WAL now fsyncs on the timer only while dirty (tracked by a
  write/sync generation watermark); close() keeps its unconditional
  final sync, and background sync failures surface via walFsyncErrors
  plus a sticky lastWalFsyncError instead of being silently swallowed
- add WAL queue/group-commit counters (walQueuedBytes,
  walMaxQueuedBytes, walGroupCommits, walGroupCommitFrames) and
  lifecycle phase stats: recovery bytes/frames/duration, index/text
  rebuild durations, compaction total/snapshot/rotation/postings
  durations, rotation pause, and query candidates/decoded/sorted
  rows; add a syncIntervalMs open option threaded through compaction
  WAL rotation
- rewrite the bench on fixed-seed synthetic data with a stable
  machine-readable JSON report (cold open 10k/50k/100k, word/ngram
  search, idle-fsync acceptance, 100k compaction, event-loop delay,
  peak heap/RSS per scenario) and pin the schema in
  test/bench-json.test.ts; add app-side baselines with loose
  complexity budgets in sessionIndex and searchService tests
- fix ClusterDb lock-pool closeAll() leaking in-flight shard opens
  and drain the query store's async close on server shutdown,
  eliminating the ENOTEMPTY directory-teardown race

* perf(minidb): bound startup rebuild and steady-state hot paths

- rebuild all derived indexes in one shared store walk: a single decode
  per record fans out to staged builders, dt rebuild reads record
  metadata only, and index-less opens no longer decode at all
- rank full-text results with a bounded min-heap plus a stable key
  tie-break instead of sorting every candidate
- remove/overwrite text docs via a docID -> delta-terms reverse map
  instead of scanning the whole delta vocabulary
- validate unique batches incrementally against touched postings
  instead of copying the full per-index owner map
- reap due TTL entries from the expiry heap on the write path instead
  of a full-store sweep per write

* feat(session-index): add minidb read model with keyset pagination

- add ISessionIndex read-model lifecycle (prepare/status, ready/degraded
  states) behind the persistence_minidb_readmodel experimental flag
- add ISessionIndexMirror write side recording fresh summaries into a
  bounded, coalescing queue after the authoritative document is durable
- replace the offset cursor with before/after keyset pagination; rename
  list/countActive to listRecent/count
- extend IQueryStore with ordered columns and pageByColumn, plus
  getMany/listKeys/dropCollection
- wire the read model through kap-server routes and start, and update the
  klient sessions contract
- index every session for global search instead of the 500 most recent

* feat(kap-server): bound search sync lifecycle, pagination, and query budgets

- split search requests from sync work: searchIndex() no longer awaits
  runSync/reopen/reindex; a single-flight sync coordinator with debounce
  and backpressure runs in the background, stale generations keep
  serving with explicit stale/degraded state, and refresh/sync/reindex
  failures surface via lastRefreshError instead of being swallowed
- scope file-meta keys by session id (\0meta\file\<sessionId>\<hash>)
  with lazy + one-shot background migration from the legacy hash-only
  keys, so one session sync only touches its own meta rows
- make authoritative scans incremental (mtime/ino/size rescan
  conditions, unchanged files no longer rewrite meta) and read wire
  deltas in 1 MiB chunks instead of whole-file buffer + split
- replace offset pagination with versioned v2 keyset page tokens
  (fingerprint + index generation + sort boundary); generation changes
  fail old tokens with invalid_page_token, legacy v1 offset tokens are
  served once and upgraded, and pages collect via bounded top-K instead
  of full sort + offset skip
- add query budgets enforced at the postings/score stage: max query
  terms, literal length cap, postings visit budget (minidb
  searchBounded/maxVisits with prefix decoding that never fabricates
  hits and skips the postings LRU), candidate caps, deadline and text
  budget; truncation is reported via incomplete reasons
  candidate_cap/postings_budget/deadline
- reopen read-only dbs by opening the next handle before closing the
  previous one so a failed refresh keeps the old generation serving;
  failed opens now self-heal through search traffic

100k-message bench: first page p95 < 300ms and page-100 cost on par
with page 1; event-loop delay during queries stays sub-millisecond.

* fix(minidb): poison, roll back, and recover the WAL on write failures

- give WAL writes a commit point: a failed flushBatch poisons the WAL
  (WAL_POISONED, tracked separately as walWriteErrors vs walFsyncErrors),
  rejects queued frames in reverse enqueue order, and stops scheduling
  further batches; everysec background sync failures stay non-rejecting
  per stage-1 semantics
- recover in place to a known-safe point: a serialized recovery chain
  truncates the WAL back to the first un-acked frame, rebuilds
  size/nextOffset, and clears the poison; writes queue behind the
  recovery gate (zero-cost when idle), a failed truncate flips the
  instance into an explicit writeDisabled state, and a stale truncate
  offset (WAL file replaced by a rotation) skips the truncate
- roll failed flush groups back as a unit: frames are stamped with
  their batchId, MiniDb keeps per-group earliest pre-state, and the
  first rejection restores every key of the group (rejected writes no
  longer reappear after reopen, and in-memory state matches reopen for
  any failure interleaving); the per-op seq guard remains for
  cross-group and rotation-retry races
- wrap applyOp and the following in-memory mutations so a contract
  violation poisons the WAL and rolls the group back instead of
  escaping as a half-commit; frames never enqueued (seal race) roll
  back per-op without poisoning
- tag errors past the commit point with ambiguous: true so callers can
  distinguish "definitely not applied" from "maybe applied but revoked"
- close() waits for the recovery chain to go idle and backup() fences
  behind in-flight recovery before copying files

Controlled A/B bench (22 alternating iterations, 100k concurrent sets):
write-path throughput regression is within the 2% budget.

* fix(minidb): turn the file lock into an instance-owned serialized lease

- distinguish lock ownership by instance instead of pid: every acquire
  mints a pid:uuid token carried by lock/bid/watch files, inspect().mine
  compares tokens, liveness still follows pid, tokenless legacy files
  keep the old stale-takeover path, and hasLiveForeignWatch excludes
  self by token so same-process contenders see each other (closing the
  double-win takeover and the cross-instance release); a live same-pid
  lock is still respected, and re-acquiring a held lock is idempotent
- serialize acquire/renew/release through a per-instance promise-chain
  mutex: renew re-checks held inside the chain and release waits for an
  in-flight renew, eliminating the renew/rename-after-unlink ghost lock
- make MiniDb.close() a state machine (open/closing/closed) with a
  shared closePromise: cleanup runs per-resource try/catch in
  dependency order (text indexes, store, valueReader, WAL, lock),
  aggregates every cleanup error into an AggregateError, stays in
  'closing' on failure so a retry finishes the cleanup, and no longer
  leaks the lock when the WAL close fails; a rejected in-flight
  compaction no longer escapes the cleanup pass

* fix(minidb): keep readers on one consistent file generation

- add an internal persistent-files module as the single source of truth
  for the persisted file set (snapshot, WAL, sidecars, postings
  pattern, fingerprint subset); lock-pool fingerprints, persistentFiles,
  open stale-tmp cleanup, and backup/restore filtering all derive from
  it, and fingerprints upgrade to dev:ino:size:mtimeMs so compound
  sidecar changes can no longer hide from cluster readers
- pair snapshot and WAL generations during recovery (transitional
  stat-pairing until stage-5 manifests): each pass anchors the fds it
  scans, re-stats afterwards, tolerates append-only WAL growth, retries
  bounded times on generation churn with a clean store reset, and
  throws RECOVERY_GENERATION_CHURN when churn exceeds the budget; the
  disk-mode ValueReader attach re-validates inodes so stale offsets
  never read a replaced file
- make the rotation directory fsyncs strict: failures abort the
  rotation through the existing rollback path instead of being
  swallowed, while platforms without directory fsync degrade once with
  a warn and stats.dirFsyncUnsupported

* fix(minidb): serialize index-definition sidecar mutations, persist before publish

- extract the promise-chain mutex into a shared createSerializer() and
  give each sidecar family (secondary/compound/text) its own chain:
  create/drop run uninterruptibly (memory change + rebuild + persist),
  different families stay independent, and the data write path never
  shares these chains
- reverse the publication order to staged -> persist -> publish: a
  create stages the definition, rebuilds via the staged builder,
  persists the sidecar including the new definition, then publishes
  atomically; any failure discards the staged state leaving live and
  sidecar untouched (no phantom indexes, retry-safe); a drop persists
  the sidecar without the definition before removing it live; text
  index create/drop adopt the same pattern, replacing the hand-rolled
  unwind, and a dropping marker keeps compaction postings rebuilds out
  of the persist window
- feed staged indexes from the incremental write path (add/remove/
  checkUnique/checkUniqueBatch visit live+staged) so writes landing in
  the persist window are not lost at publish; queries still see live
  only
- harden writeFileAtomic: instance-unique tmp names (.tmp-pid-seq),
  a strict fsyncDir after rename so a successful persist is crash
  durable, and whitelist-based stale-tmp cleanup that never touches
  lock tmp files

* fix(minidb): validate writes before any side effect, canonicalize values once

- canonical value at the write boundary: the json codec re-parses the
  encoded bytes once and every downstream consumer (unique checks,
  secondary/compound/text indexes, dt extraction) sees exactly the
  persisted representation, so getter/toJSON/Proxy documents can no
  longer diverge between the index view and the storage view
- reorder the set/batch pipeline so every fallible check happens before
  any visible side effect: prepare (key/ttl checks, encoding, canonical
  decode, index field extraction, tokenization) -> unique checks ->
  ensureMemoryFor eviction -> commit; a constraint failure now leaves
  the database untouched (no more evicted victims on rejected inserts),
  and applyOp is structurally pure against pre-validated data
- tokenize at the prepare boundary: TextIndex gains prepareAdd/
  addPrepared and the buildQueue carries validated key+tokens mutations
  instead of raw docs, so a throwing custom tokenizer can no longer
  poison the live view or the queue, and custom-tokenizer output is
  rejected per token over 0xffff bytes before it can permanently break
  postings rebuilds; prepared tokens are keyed by index instance so a
  same-name drop+create mid-write re-tokenizes instead of crossing
  tokenizers
- strict batch structure validation: scanBatchOpRefs/decodeBatchOps
  reject unknown op types, out-of-bounds lengths, and trailing bytes
  (offset must equal body length), so a valid-CRC but malformed batch is
  skipped as a unit and counted via RecoveryInfo.corruptBatches instead
  of being partially applied

Bench vs the stage-1 baseline: json write throughput regression is
within the 5% budget (median ~2-4% depending on the measurement).

* feat(minidb): add OpTracker drain primitive and atomic backup, harden tests

- introduce the internal OpTracker (close gate + in-flight counter with
  enter/leave/close/whenIdle and reference-counted pause/resume) and
  drive every shutdown/drain path from it: WAL background syncs are
  tracked so close() waits out an in-flight sync before closing the fd,
  cluster lock-pool closeAll() closes the gates and drains busy
  callbacks before closing handles, and MiniDb writes pass a write gate
- make backup() atomic with a defined linearization point: pause the
  write gate, drain in-flight writes (every acknowledged write is now
  included), copy to a sibling temp dir with per-file fsyncs, write the
  manifest last as the commit marker, and rename into place; failures
  clean up and leave no partial backup, and concurrent writes are
  rejected with BACKUP_IN_PROGRESS
- reap emptied compound-index groups on remove (the groups map no
  longer grows monotonically), move the open-time mkdir behind the
  readOnly check so a read-only open of a missing directory fails with
  ENOENT instead of creating it, and never run a destructive rebuild
  for a read-only open failure (explicit or onLockFail fallback)
- consolidate every review fault-injection repro into the formal suite
  behind deterministic barrier helpers (programmable writev/sync/
  rename/tokenize hooks) and convert the six timing-based tests to
  barrier/tick-driven assertions; the .tmp repro scripts are removed

The converted timing tests and the full suite pass 50 repeat runs
(including under CPU load injection) with zero flakes.

* feat(minidb): persist derived indexes as atomic generations, open from WAL delta

- checkpoint the store, dt/secondary/compound indexes, and text
  dictionary/postings/docs into immutable generations under
  generations/g-NNNNNN published atomically (tmp build, per-file
  checksums and fsyncs, dir rename, CURRENT swap, strict dir fsyncs);
  the manifest records the format version, WAL/snapshot checkpoint
  anchors, per-index definition hashes, and codec/value-mode
  compatibility
- open now loads the published generation and replays only the WAL
  delta after its checkpoint: no full value decode, corpus
  tokenization, or postings rewrite on a normal reopen (warm opens are
  3.5-13.8x faster at 100k/1M records); a definition change rebuilds
  only the affected index, and corrupt generation files fall back to
  the previous generation or the legacy full recovery without ever
  touching the authoritative snapshot/WAL
- build generations transactionally with compaction (rotation plus
  derived state publish as one unit, replacing the synchronous
  rebuildTextPostings tail), capture concurrent writes through a sealed
  op queue with byte/op caps, hard-link clean postings and the snapshot
  into the new generation, and repoint every live text base into the
  CURRENT generation after publish
- cluster/read-only refresh watches CURRENT and the WAL watermark:
  pure generation publishes keep readers on incremental catch-up while
  rotations reopen onto the new generation; writers building the next
  generation never disturb readers of the current one
- legacy databases open through the old path unchanged and gain their
  first generation in the background; OpenOptions.indexGenerations:
  false fully restores the pre-generation behavior

* feat(minidb): workerize text-index builds and split MiniDb into facets

- split the monolithic src/index.ts into facet modules (mini-db, types,
  value-codec, memory-guard, backup, query-engine, text-registry,
  wal-group, generation-builder/loader, write-path, read-path,
  index-admin, lifecycle, stats) and move text-index.ts to text-index/
- run corpus-scale text-index builds off the main thread via the bounded
  worker engine (src/worker/), exported through the new worker-runtime
  subpath, with inline fallback for small corpora and rollback switches
- defer the open-time fallback text rebuild into a maintenance task;
  searches on a not-yet-committed base raise TextIndexBuildingError
- add the unified maintenance scheduler, bounded async read surface,
  and a maintenance bench
- kap-server search: switch to searchBoundedAsync and serve the
  building page while the index base rebuilds after fallback recovery
- kimi-code: install the SEA-bundled minidb text-build worker at
  startup, bundle it via the native asset scripts, and add the
  startup-trace util plus the KIMI_TUI_INPUT_LATENCY debug probe

* fix(minidb): treat win32 EPERM as unsupported directory fsync

- extract isUnsupportedDirectoryFsyncError and cover win32 EPERM
- drop the one-shot console.warn; stats.dirFsyncUnsupported carries the degraded state

* fix(kap-server): harden search-index dispose and drain lifecycle

- dispose() now closes an OpTracker gate and drains in-flight sync/refresh
  passes before closing the db, so no background write can hit a closed
  handle; the deleteSessionDocs loop and trailing stats write skip once
  the gate closes (review #20)
- drainGlobalSearchDisposals loops to a fixpoint so disposals registered
  while a drain is in flight are also awaited (review #21)
- pin the post-open failure semantics with a regression test: a failed
  text-index setup closes the handle and the next open reacquires the
  writer lock instead of self-locking read-only (review #19)
- export OpTracker from the minidb root for the search service's drain

* chore: fix oxlint type-aware lint errors
2026-08-04 20:38:42 +08:00
zy
74c321e4c6
fix(mcp): drop protocol-reserved _meta keys from model-visible output (#2600)
* fix(mcp): drop protocol-reserved _meta keys from model-visible output

Follow-up to #2596. The MCP spec reserves _meta key prefixes whose labels
include "modelcontextprotocol" or "mcp" for protocol use; those entries
carry host/protocol plumbing rather than model-facing data, so filter them
out before serializing the <mcp-structured-result> block. Unprefixed and
vendor-prefixed keys still pass through — their semantics belong to the
server. Also moves the v2 implementation commentary into the module header
per the agent-core-v2 comment convention.

* fix(mcp): reserve _meta prefixes only when a label follows mcp/modelcontextprotocol

Per the spec's key-name rules a prefix is reserved when a
modelcontextprotocol or mcp label is followed by at least one more label;
a trailing reserved word (com.example.mcp/) is a legitimate vendor
namespace and now passes through.

---------

Co-authored-by: zouying <zouying@moonshot.cn>
2026-08-04 20:03:10 +08:00
zy
96f77fe392
fix(agent-core-v2): port the MCP auth:'oauth' opt-in from v1 (#2608)
v1 lets a remote MCP server combine static headers with OAuth via the
auth:'oauth' config marker; v2 neither parsed the field nor honored it,
so any configured headers were treated as static credentials — first
authorization never started and an expired refresh token left the entry
permanently failed. Add the field to the v2 remote config schemas and
mirror the v1 guard in shouldMarkNeedsAuth.

Resolve #2605

Co-authored-by: zouying <zouying@moonshot.cn>
2026-08-04 20:02:57 +08:00
zy
3126422757
feat(mcp): carry an absolute expiresAt on the OAuth authorization-url update (#2609)
* feat(mcp): carry an absolute expiresAt on the OAuth authorization-url update

The authenticate flow waits for the OAuth callback with a fixed budget,
but the authorization-url tool update surfaced to embedding hosts did not
say when that window ends — hosts had to hardcode a mirror of the
15-minute constant to render countdowns. Include the absolute deadline
(now + effective wait timeout) in the update payload for v1 and v2.

Resolve #2607

* fix(protocol,kap-server): accept expiresAt in the OAuth authorization-url update schemas

The zod validators mirrored the pre-expiresAt payload shape and would
strip the new field at the kap-server boundary.

---------

Co-authored-by: zouying <zouying@moonshot.cn>
2026-08-04 20:02:47 +08:00
7Sageer
aec755fad6
feat(agent-core-v2): announce date changes via a system reminder (#2564)
* feat(agent-core-v2): announce date changes via a system reminder

* refactor(agent-core-v2): hide profile rendering behind service

* refactor(agent-core-v2): normalize profile prompt rendering at registration

* chore: retrigger ci
2026-08-04 19:31:00 +08:00
zy
c32e661faa
fix(mcp): pass structuredContent and _meta through to model-visible tool output (#2596)
MCP tool results were narrowed to {content, isError}, dropping the
spec-defined structuredContent field and _meta metadata. Servers that
return structured contracts in these fields (validated against
outputSchema, or namespaced metadata such as browser-handoff payloads)
were invisible to the agent. Surface them as a serialized
<mcp-structured-result> block appended to the tool output, still subject
to the existing text budget.

Co-authored-by: zouying <zouying@moonshot.cn>
2026-08-04 18:01:42 +08:00
qer
0abcd00f7f
feat(cli): add built-in Computer Use and WebBridge capabilities (#2407)
* feat(agent-core-v2): add built-in capabilities (kimi-cu, kimi-webbridge) with REST routes

Add a capability domain holding a closed registry of built-in product
capabilities. Each entry owns layered readiness detection and idempotent
install orchestration: binary runtimes from fixed official CDN URLs
(KimiCU.app + launchd service + TCC permission state; the WebBridge
daemon with start-if-down semantics for Kimi Work coexistence) plus
agent wiring through the plugin service. The WebBridge wiring un-shadows
stale user-source skill copies (user priority beats plugin priority).

kap-server exposes the domain as GET /api/v1/capabilities,
GET /api/v1/capabilities/{id}, and POST /api/v1/capabilities/{id}:install
with client-polled progress and new wire codes 40418 / 40922 / 40923.

The plugin marketplace gains an official kimi-webbridge entry
(browser-control skills) packaged by the existing CDN build.

* fix(agent-core-v2): rename the webbridge wiring plugin to kimi-webbridge-skill

An official kimi-webbridge guide plugin (install/remove setup skills,
v3.0.4) already exists at the marketplace path the capability installer
pointed at — a different artifact owned by another release line. Give
the browser-control usage-skill plugin its own id/path instead of
colliding with (or overwriting) the guide plugin. The capability entry's
detect/install now tracks kimi-webbridge-skill; a machine with only the
guide plugin correctly reports the skill layer as missing.

* feat(agent-core-v2): shelf installs auto-complete capability binary layers

Two changes to make the plugin marketplace a first-class install path:

- Marketplace gains kimi-cu (sourced from the CU team's CDN zip — no
  repackaging) and the kimi-webbridge usage-skill plugin now claims the
  kimi-webbridge id at v4.0.0, deliberately superseding the WebBridge
  guide plugin (v3.0.4, install/remove guide skills): guide users get a
  version upgrade onto the real usage skill.
- The capability service subscribes to IPluginService.onDidReload: when
  a capability's wiring step flips to ok through ANY install path
  (shelf, TUI, CLI), it auto-completes the missing binary layers
  (KimiCU.app + service, or the WebBridge daemon). Triggers only on the
  false→true edge so completed installs with still-missing manual steps
  (TCC permissions) never retrigger heavy downloads on later reloads.

* fix(plugins): keep kimi-webbridge plugin version aligned with the upstream skill

The plugin version tracks the bundled official usage skill (1.11.3) so
version drift against the WebBridge release line stays visible, instead
of minting an independent 4.0.0.

* fix(agent-core-v2): never report the webbridge installer-script version as the product version

The on-disk ~/.kimi-webbridge/bin/kimi-webbridge.version file tracks the
installer's own lineage (3.1.x, bumps on every install/upgrade run),
not the product version (v1.11.3 — daemon, extension, and skills all
share it). A downed daemon would have shown the misleading installer
number; report no version instead (live /status remains the source of
truth).

* chore(plugins): list kimi-cu on the marketplace without a pinned version

Marketplace versions are optional by schema: rows display the version
detected from the installed plugin's manifest, and update prompts only
fire on a valid semver latest > local comparison. A hand-maintained
number would drift just like the guide plugin's did. The locally built
kimi-webbridge entry keeps its manifest-stamped version (1.11.3).

* fix(agent-core-v2): fire onDidReload on plugin mutations, not just explicit reload

installPlugin / setPluginEnabled / removePlugin changed the catalog
silently — consumers listening to onDidReload (session skill-catalog
convergence, the capability shelf-install hook) only converged on an
explicit reloadPlugins(). Fire the same summary-shaped event on every
mutation (added:[id] / [] / removed:[id]) so every install path
converges. This also unbreaks the shelf-install hook on real hosts:
its unit tests passed against a fake emitter that fired on installs,
which the real service never did.

* feat(kap-server): add plugin management and marketplace REST routes

Expose the App-scope plugin service over the wire so non-CLI hosts
(desktop, web) can manage plugins end to end:

- GET  /api/v1/plugins/marketplace — catalog (pluginMarketplaceUrl
  server option / KIMI_CODE_PLUGIN_MARKETPLACE_URL env / production
  default) merged on demand with live install state; updateAvailable
  only on strict semver catalog > installed (no semver dependency)
- GET  /api/v1/plugins, POST /api/v1/plugins {source}
- POST /api/v1/plugins/{id}:{enable,disable,remove}
- New wire code 40419 plugin.not_found

Mutations flow through IPluginService, so they serialize with other
install paths and fire onDidReload (session skill catalogs and the
capability shelf-install hook converge).

* feat(agent-core-v2): surface a machine-key note from capability installs

CapabilityEntry.install now resolves an optional note exposed through
CapabilityInstallProgress.note (wire-visible). The webbridge entry
returns 'user-skill-migrated' when it replaces a pre-existing
user-source skill (from the official installer) with the plugin-managed
copy — clients can localize the migration instead of the skill silently
disappearing from the user's directory.

* feat(tui): let the real WebBridge marketplace entry win over the pinned promo

The hardcoded Web Bridge row was built when WebBridge had no plugin
package — it pinned above the Official tab and shadowed any catalog
entry with the same id (open-in-browser only). Now that the marketplace
carries the real kimi-webbridge plugin, flip the precedence: the catalog
entry renders and installs normally, and the pinned promo becomes a
loading/error/legacy-catalog fallback only. Footer counts keep their old
semantics (catalog-only; the promo row is never counted).

* fix(tui): dim the installed state so it stops reading as the install action

Both badges shared a near-identical green-ish treatment in the same
column, making a quiet fact look like a clickable action. States now
recede (installed → textDim) while actions stay loud (install →
primary, update → warning).

* feat(agent-core-v2): converge plugin state across processes sharing a home

Multiple hosts share one KIMI_CODE_HOME (CLI, desktop, other agents), but
each PluginService kept a private in-memory snapshot: a plugin installed
or removed in one process stayed invisible to every other live process
until its next restart — new sessions there kept offering stale plugin
skills/MCP, and the capability shelf hook never saw peer installs.

Watch <home>/plugins for installed.json changes and reloadPlugins
(debounced, echo-suppressed around our own mutations) so all consumers
converge in well under a second: session skill catalogs, plugin MCP
mounts, and the capability shelf-install hook alike.

* fix(agent-core-v2): un-shadow webbridge user skills in BOTH user dirs

kimi-code resolves user-scope skills from two roots (~/.kimi-code/skills
and ~/.agents/skills), both at priority 20 — a stale copy in either
shadows the plugin-managed wiring (priority 5), and also keeps the
capability working after the plugin is removed, which reads as
'uninstall did nothing'. Migrate copies in both dirs during install;
other runtimes' dirs (~/.claude, ~/.codex) remain untouched.

* feat(tui): show live runtime-setup progress for capability installs

Installing a capability plugin (kimi-cu, kimi-webbridge) from the
/plugins shelf kicked off a silent background binary install — the row
flipped to installed while megabytes of runtime downloaded invisibly.
Route capability entries through the capability surface instead: the
panel's inline installing line now mirrors live progress (step +
percent) until the install settles, and the transcript reports
ready / failure-with-retry / still-running accordingly. Capability
removal prints an explicit note that runtime binaries are deliberately
left untouched (the capability keeps working), since that read as
'uninstall did nothing'.

Plumbs the capability service through klient's global facade
('capabilityService' decorator resolves in-process) and the node-sdk
v2 client; Session exposes it with a structural feature-detect so v1
engines fail clearly.

* docs(plugins): keep the kimi-cu marketplace blurb accurate for every client

Only the capability-aware clients auto-install the KimiCU.app runtime;
older builds still get wiring-only (the wrapper's error message then
points at the official setup script). Don't overpromise in the catalog
text every version reads.

* feat(agent-core-v2): install capability wiring from client-bundled plugin copies

The kimi-cu / kimi-webbridge wiring plugins ship inside the client release
instead of the marketplace catalog, binding their visibility to the client
version. Capability installs now resolve the bundled copy (env override,
then npm-layout and source-checkout probes from the module) and install it
as a local path, replacing the two CDN zip URLs. A missing bundle fails the
wiring step with a clear reinstall-or-upgrade message.

* build(cli): bundle the capability wiring plugins into client releases

Vendor the official kimi-cu plugin (v0.5.4, from the CU team's plugin zip)
next to kimi-webbridge under plugins/official, copy both into
apps/kimi-code/bundled-plugins at build time, and ship them in the npm
package (files) and the native SEA blob (a new bundled-plugins asset set
extracted into the native cache at startup, published to the engine via
KIMI_CODE_BUNDLED_PLUGINS_DIR). Desktop points the same variable at its
extraResources copy. The .gitignore build-output entries are anchored so
sources under src/native and test/native stop being silently ignored.

* revert(plugins): remove the kimi-cu and kimi-webbridge marketplace entries

Both capabilities now distribute with the client (bundled wiring), so the
catalog drops back to kimi-datasource / superpowers / vercel-plugin. Older
clients never see the entries; current clients install from the Built-in
section. This also reverts the marketplace blurb commit 0635e99c5.

* feat(tui): add a Built-in capabilities section to the plugins panel

The Official tab now opens with a Built-in section fed by the engine's
capability registry (kimi-cu / kimi-webbridge): per-row install state
(install / finish setup / ready), Enter runs the full capability install
with live progress, and unsupported rows hide (kimi-cu off macOS). The
WebBridge promo fallback only remains for v1 engines — on v2 the real
built-in entry wins. Rows double as the reinstall path: a client upgrade
ships newer wiring, and installing again upserts from the new bundle.

* docs(plugins): document the Built-in section and refresh the capability changeset

* build(nix): stage bundled capability plugins into the SEA build

The native SEA blob now embeds the bundled-plugins asset set, so the nix
derivation needs the plugins tree in its src fileset and the staging step
alongside copy-web-assets before build:native:sea.

* revert: drop the client-bundled wiring distribution

Built-in visibility is simpler to get by injecting the two capability
entries into the marketplace catalog at load time; the wiring plugins
themselves keep installing from their fixed official CDN zips. Removes
the vendored kimi-cu plugin, the bundled-plugins npm/SEA packaging and
flake staging, the engine bundle resolver, and the plugins panel's
Built-in section. Keeps the /agents/ and /native/ gitignore anchors so
sources under src/native and test/native are not silently ignored.

* feat(cli): inject the built-in capability entries into the marketplace catalog

The kimi-cu / kimi-webbridge entries are appended by the client at catalog
load time instead of being served by the remote marketplace.json, binding
their visibility to the client version (older clients never see them). No
version is pinned — reinstalling upserts the wiring — and ids the catalog
already carries always win. In a source checkout the webbridge entry
installs the repo's own plugin copy; packaged builds use the official CDN
zip. This reverts the docs paragraph about the Built-in section, which the
simpler approach makes unnecessary.

* test(tui): select the catalog's own first row in marketplace install tests

The client-injected capability entries suppress the WebBridge promo and
append after the catalog rows, so Kimi Datasource now leads the Official
tab — the extra down-key landed on kimi-cu instead.

* feat(cli): surface the built-in capabilities as client-injected marketplace entries

The kimi-cu / kimi-webbridge entries are injected into the marketplace
catalog by the client (v2 engine, default catalog only) instead of being
served remotely, binding their visibility to the client version; injected
rows mask same-id catalog rows, so what these ids mean stays decided by
the client release — a future official listing only reaches older clients,
whose fix is to upgrade.

The /plugins panel shows capability readiness on the rows (setup
incomplete / installing…), platform-gates kimi-cu to macOS, and Enter
finishes the runtime setup with live progress; v1 keeps the plain plugin
install path and the WebBridge promo fallback.

Capability and plugin calls move from the ad-hoc REST routes onto the
typed klient contract (capabilityService next to pluginService), so the
public REST surface returns to its pre-feature shape. Detection is
presence-only — version pins removed: the current version is always read
live (Info.plist, daemon status, install records), installs are
detect-first and idempotent so an interrupted setup can be retried, and
reinstalling pulls the latest managed artifacts (the passive upgrade
path).

* ci: retrigger checks

* fix(cli): recognize Computer Use CDN plugins as official

* fix(cli): keep built-in entries on catalog outage and isolate detector failures

Two review follow-ups: the client-injected entries no longer disappear when
the marketplace catalog is unreachable (they are not served by it), and a
single capability's failing detect probe degrades to a failed step on that
entry instead of rejecting the whole listCapabilities call.

* refactor(cli): simplify built-in capability integration

* refactor(cli): source built-in catalog rows from the engine and tighten detect probes

The injected marketplace entries are now derived from the engine's
capability registry (listCapabilities) instead of hardcoded client-side
copies — the util only owns the mask/append mechanics, and capability ids
are no longer pinned in the CLI (the remove note resolves them through the
registry too). kimi-cu's detect-path probes (service-status, xpc-ping) get
a 3s timeout — they answer in milliseconds when healthy but run on every
status listing, so a wedged binary must degrade quickly instead of
stalling the panel. Document the Official tab's built-in capability rows
in the plugins guide.

* fix(cli): answer capability id membership without running detectors

listCapabilities() runs every entry's detect probes (seconds on a wedged
binary), so using it to decide whether to print the post-remove hint made
every plugin removal pay a full detection round. The id set is part of the
client/engine contract (mirrored in the klient schema), not product data
that drifts — restore the closed-set check. The injected catalog rows keep
flowing from the registry.

* fix(agent-core-v2): make capability setup recover from disabled, partial, and wedged states

Three review follow-ups on the install path: setup now re-enables the
wiring plugin when a previous disable survived installPlugin's upsert
(detection requires enabled, so it would otherwise strand the capability
at partial); the webbridge daemon-binary step verifies the executable bit
on POSIX, so an install interrupted between rename and chmod re-downloads
instead of failing start with EACCES; and kimi-cu's detect degrades
wedged CLI probes (service-status, xpc-ping) to failed steps instead of
throwing, keeping the detect-first install able to repair the remaining
layers — with the probe timeout injectable for tests.

* fix(agent-core-v2): abort capability downloads whose byte stream stalls

downloadToFile had no inactivity deadline: a CDN connection that stops
producing bytes hung the background install forever, wedging the
capability in a permanent installing state (retries rejected as
in-progress) until the process restarted. An idle watchdog now fails the
download after 30s without a chunk; slow but flowing downloads are
unaffected.

* fix(tui): stop offering capability setup on unsupported platforms

An installed wiring plugin whose capability is unsupported on this
OS/arch (kimi-cu off macOS, webbridge on an unknown arch) was treated
like a partial setup: the Installed tab showed setup incomplete and
Enter routed to installCapability, which the service always rejects.
Setup actions are now gated to actionable states (not_installed /
partial); unsupported renders as a dim fact and Enter opens details.

* fix(agent-core-v2): cover the two remaining install wedge modes

Review follow-ups: the KimiCU app step now requires an executable binary,
so a ditto interrupted mid-copy reads as missing and the next setup
re-copies instead of failing EACCES forever; and downloadToFile's idle
budget now also covers the response-header phase via an AbortSignal on
the fetch itself, so a connection that never completes headers fails the
install (clearing the running state) instead of hanging it.

* fix(tui): render capability rows independently of the catalog fetch

While the marketplace catalog was loading or unreachable, the Official
tab showed only the pinned WebBridge promo — built-in runtime setup was
blocked by an unrelated remote fetch, and Enter opened the browser
instead of installing. Locally-known capability rows (from the engine
registry) now render and install in every catalog state; the promo
remains only as the v1 fallback.

* fix(agent-core-v2): keep KimiCU cleanup timeouts best-effort

stopOldProcesses is documented as || true, but runCommand propagates
timeouts: a wedged old binary made kimi-cu uninstall exceed the command
timeout and the reinstall died before ditto could replace the app.
Cleanup commands now swallow failures (the timeout already attempts a
kill) so the replacement always proceeds; the command timeout is
injectable for tests alongside the probe timeout.

* fix(cli): inject built-in entries only for the default marketplace catalog

Injection is part of the default catalog experience: any explicit
replacement (slash-command source or KIMI_CODE_PLUGIN_MARKETPLACE_URL)
now opts out wholesale — its same-id rows are never masked by the
built-ins, and an unreachable custom catalog surfaces its own failure
instead of being silently replaced by a built-in-only tab.

* refactor: align capability row rendering on the source marker and drop conditional spreads

Marketplace-row capability enrichment (status, badges, issue details,
platform filtering) now keys on the capability:<id> source marker — the
same condition Enter uses to route installs — so a custom catalog row
that merely reuses a built-in id renders and installs as a plain plugin.
Also replaces the conditional-spread optional fields with direct
undefined-valued assignments per the repo coding rules.

* refactor(agent-core-v2): move capability comments to the file headers

The domain's comment convention allows only the top-of-file block:
responsibility and scope context for the recent hardening (detect-first
idempotent install, executability gates, probe-failure degradation,
best-effort cleanup, download watchdog, per-entry detection isolation)
now lives in the module headers, and inline narration beside statements
and members is removed.

* fix(tui): follow an in-progress capability install instead of restarting it

Opening /plugins while a capability setup is already running showed the
installing… row, but Enter called installCapability again and the
service's duplicate-start rejection (40922) surfaced as a fake failure.
The panel now checks the live status first and, when an install is
already running, skips the start call and just polls for the existing
progress.

* fix: align two more replacement paths with their contracts

The EXDEV daemon-binary fallback now stages on the target filesystem and
atomically renames over the destination instead of opening a
possibly-running binary for write (ETXTBSY on Linux). And the panel's
fallback capability rows (catalog loading/error) now follow the same
default-catalog condition as the loader injection, so an explicitly
overridden marketplace fully replaces the Official tab.

* fix(tui): make the built-in row marker unforgeable

The capability:<id> source string was the trust signal for routing rows
into capability installs, but any catalog can write that string — a
custom marketplace could smuggle a row past the third-party trust path
into an official runtime install. Injected rows now carry an internal
builtIn flag that the field-by-field catalog parser never produces;
rendering and install routing key on the flag, and the source string is
purely diagnostic.

* fix(agent-core-v2): include MCP server enablement in capability readiness

A user who disabled the kimi-cu stdio MCP server (/plugins mcp disable)
got a ready capability with no Computer Use tools in new sessions: the
plugin step only checked the plugin toggle, and installPlugin's upsert
preserves per-server state. Readiness now requires every declared MCP
server enabled (reporting e.g. mcp 0/1 enabled), and setup re-enables
disabled servers alongside the plugin toggle.

* fix(agent-core-v2): shell-quote ditto paths in the elevated KimiCU copy

The elevated fallback escaped paths only for the AppleScript string
delimiters, not for the /bin/sh command line inside do shell script: a
TMPDIR with spaces broke the install, and shell metacharacters in the
temp path could inject commands into an administrator-privileged script.
Paths are now POSIX single-quoted first, then the assembled command is
AppleScript-escaped.

* fix(agent-core-v2): never break a working KimiCU on a failed update

The reinstall stopped and uninstalled the old service before the
downloaded archive was unpacked: a corrupt or captive-portal zip then
tore down a previously ready setup. The archive is now staged and
unpacked first, and the app step additionally requires the bundle's
Info.plist, so a partially copied bundle reads as missing and gets
re-copied instead of failing registration against a corrupt bundle.

* fix(agent-core-v2): limit the fetch deadline to the header phase

The 30s AbortSignal stayed attached for the whole request, so a
slow-but-healthy download of a large archive was aborted at 30s total
even while chunks kept arriving — exactly what the per-chunk idle
watchdog was meant to allow. The header phase now uses an
AbortController cleared once headers arrive; the body remains governed
by the inactivity watchdog alone.

* test(tui): provide the harness plugin facade in the capability command fakes

The lazy-session refactor routes session-less plugin calls through
host.harness; the fake host now mirrors that shape.
2026-08-04 17:59:16 +08:00
qer
da6646bf57
docs(changelog): fix 0.32.0 entry formatting and doc links (#2598) 2026-08-04 17:02:08 +08:00
7Sageer
278b6af19d
fix(agent-core-v2): make MCP initial connect non-blocking during startup (#2586)
* fix(agent-core-v2): make MCP initial connect non-blocking during startup

* Delete .changeset/mcp-nonblocking-startup.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* fix(agent-core-v2): wait for MCP readiness before first turn

* fix(klient): wait for MCP startup before listing

* fix(klient): keep MCP server listing non-blocking

* test(acp-server): allow pending MCP snapshot

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-08-04 16:47:55 +08:00
qer
85e4cf0346
docs(changelog): shorten the 0.32.0 loop_control and token_counting entries (#2595)
* docs(changelog): shorten the 0.32.0 loop_control and token_counting entries

* docs(changelog): tighten the 0.32.0 loop_control and token_counting entries further

* docs(changelog): reword 0.32.0 Polish entries from the user perspective
2026-08-04 16:41:59 +08:00
qer
c2e53aef6c
docs: polish the 0.32.0 changelog and fill 0.32.0 doc gaps (#2594)
* docs: polish the 0.32.0 changelog and fill 0.32.0 doc gaps

* docs(changelog): note SessionEnd archive and KIMI_TOKEN_COUNTING_STRATEGY for 0.32.0

* docs(changelog): move the 0.32.0 token_counting entry from Features to Polish
2026-08-04 16:31:25 +08:00
liruifengv
2c3c5a5879
feat(tui): lazy-create the session on first use with the v2 engine (#2458)
* feat(tui): lazy-create the session on first use with the v2 engine

Start the interactive TUI session-less under the v2 engine and create the
session on first use (message, bash input, or a session-requiring slash
command) instead of at startup. Skills and plugin commands are resolved
from the workspace/app-global catalogs without a session, and the footer
shows config defaults (model, permission, plan mode, thinking effort,
context cap) until the session exists.

* fix(tui): serialize lazy session creation and carry session-only thinking

Concurrent first-use triggers (double Enter, a slash command right after a
prompt) both observed `session === undefined` and created their own
session, letting the later setSession close the first one mid-dispatch.
Share an in-flight creation promise instead.

A session-only thinking choice (model picker Alt+S) made before the first
session exists only updated appState, so the lazy-created session fell back
to the engine default while the footer showed the chosen effort. Carry it
as a first-session thinking override and clear it on creation.

* fix(tui): don't re-enter plan mode when creating the lazy session

The v2 engine applies config.defaultPlanMode at session create time
(sessionLifecycleService), so passing the pre-filled appState.planMode as
the create override entered plan mode twice and threw 'Already in plan
mode' on the first message. Pass only the explicit CLI --plan intent for
session-less v2 creation; the config default stays footer-only.

* fix(tui): re-check the busy gate after lazy shell startup, keep /settings session-less

A bash command submitted while the first prompt is still being
lazy-created shared the in-flight creation promise but resumed past
handleUserInput's busy check, running runShellCommand concurrently with
the prompt. Re-check streamingPhase after the await and queue instead.

/settings is a local settings entry point: opening it must not require or
create a session, so it no longer triggers lazy creation; its
session-requiring sub-items keep their own errors.

* fix(tui): re-check busy state after lazy command creation, make /plugins session-free

A skill/plugin or idle-only slash command submitted while the first prompt
was still being lazy-created shared the in-flight creation promise but
resumed past the availability check resolved before the await, running
concurrently with the prompt's turn. Re-check the busy gate after the
shared await in the skill, plugin-command and builtin dispatch paths.

/plugins is app-global on the v2 engine, so a session-less startup no
longer creates a session (or fails with LLM-not-set) just to manage
plugins: the harness now exposes the global plugin API and the command
routes through it until a session exists.

* fix(tui): keep the read-only /add-dir forms session-less

The bare and `list` forms already tolerate a missing session, but the
blanket lazy-create gate forced a session (or failed with LLM-not-set)
before they could run. Only the path-adding form needs a live session, so
it now lazy-creates inside the handler instead of in the dispatch
preflight.

* fix(tui): reflect pending dirs in /add-dir list, refresh plugin commands after reload

/add-dir list looked only at session.summary, so pending startup
additionalDirs were reported as absent until a session existed; fall back
to appState when session-less.

/plugins reload updated the app-global service but left the TUI's plugin
slash-command map stale, so new or re-enabled commands kept parsing as
prompts; rebuild it from the reloaded service (which also covers the
app-global path before the first session exists).

* test(tui): add pluginCommandMap to the MessageDriver interface

* fix(tui): hydrate lazy defaults on sessionless reload, guard /plan re-entry

/reload refreshed only the model/provider dictionaries while session-less,
so defaults edited externally (or a newly added default model) left
appState.model empty/stale and the first lazy-created session failed with
LLM-not-set or used the old value. Reuse the startup default-hydration
path for the no-session reload case.

/plan on as the first command with defaultPlanMode=true re-entered plan
mode after the engine had already applied the config default at create,
throwing Already in plan mode. Skip the call when the session is already
in the requested mode.

* test(tui): widen the getConfig mock return type for the reload case

* fix(tui): clear stale lazy defaults when the default model disappears

hydrateLazyConfigDefaults only patched model when the reloaded config
still had a default, so removing defaultModel from config.toml left
appState.model stale and the first lazy-created session passed it
explicitly instead of failing or following the engine's current defaults.
Reset model and context cap when the default is gone.

* fix(tui): reset a removed permission default, don't re-enter plan on --plan

A removed defaultPermissionMode left appState carrying the old elevated
mode, which createSessionFromCurrentState then passed explicitly to the
first lazy-created session; reset to manual when no CLI permission flag
is present.

With both defaultPlanMode and --plan set, the create payload passed
planMode: true even though the engine already entered plan mode from the
config default, throwing Already in plan mode on the first prompt. Track
the config default separately and suppress the --plan override when it is
already active.

* fix(tui): keep read-only and mode commands session-less

Read-only views (/status, /usage, /mcp, bare /title) already degrade
gracefully without a session, so forcing lazy creation made them fail
with LLM-not-set or create an unused persisted session; they no longer
trigger creation, and /title <name> lazy-creates only for the mutation.

/permission, /auto and /yolo are pending-mode choices: with no session on
v2 they now record the mode in appState (which the lazy create path
passes to the engine) instead of failing or creating a session just to
pick a mode. The runtime permission is applied once a session exists.

* fix(tui): wait out lazy assembly in ensureSession, expose workspace MCP

setSession assigned host.session mid-assembly, so a follow-up prompt or
command in that window skipped the shared creation promise and dispatched
against a session whose subscription and runtime sync had not finished.
Check the in-flight promise before the assigned-session fast path.

/mcp required a live session even though the v2 connection set is
workspace-scoped, so it errored until an unrelated prompt created one.
Route it through a workspace-level MCP view (harness passthrough over the
handler's shared connection manager) before the first session exists.

* fix(tui): await workspace MCP readiness in the session-less /mcp list

* fix(tui): hydrate the model default thinking effort for the session-less picker

* style(tui): trim verbose comments in the lazy-default fixes

* fix(tui): hydrate lazy defaults after login, serialize /new with lazy creation

* fix(tui): re-check the busy gate after lazy /add-dir session creation

* fix(tui): let Shift-Tab lazy-create the session on a v2 session-less start

* fix(tui): hydrate session-less defaults on model-less login, refresh workspace commands on reload

* fix(tui): re-check the idle-only gate for /new after waiting out lazy creation

* fix(tui): wait out lazy creation before accepting model/effort switches

* fix(tui): wait out lazy creation before switching sessions from the picker

* fix(node-sdk): read session-less skills from the workspace skill catalog

* feat(tui): show a session-less notice on v2 startup
2026-08-04 16:09:01 +08:00
7Sageer
af43c5226d
feat(agent-core-v2): advertise model capabilities in the subagent model picker (#2591)
* feat(agent-core-v2): advertise model capabilities in the subagent model picker

* chore(agent-core-v2): follow header-only comment convention

* docs(agent-core-v2): note the model catalog collaborator in the AgentSwarmTool header
2026-08-04 15:57:04 +08:00
7Sageer
54c04bf03d
feat: /fork no longer switches to the forked session (#2565)
* feat: /fork no longer switches to the forked session

Forking used to switch to the new session, which closed the source
session and force-stopped its background tasks (and canceled any
in-flight turn). /fork now creates the copy and stays in the current
session; the fork can be opened explicitly via /sessions.

* fix(tui): release fork runtime when staying current
2026-08-04 15:24:12 +08:00
github-actions[bot]
4ac7240fff
ci: release packages (#2469)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-04 15:11:04 +08:00
qer
6cb615cd37
chore(changeset): downgrade token-counting strategy entry to patch (#2590) 2026-08-04 15:06:54 +08:00
Haozhe
c39687318c
fix(kap-server): accept question ids containing colons on resolve (#2585)
Some checks are pending
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(kap-server): accept question ids containing colons on resolve

Some OpenAI-compatible providers emit tool_call ids like
`AskUserQuestion:0`, which the question service adopts as the question
id. The action-suffix parse then rejected the bare resolve POST as an
unsupported action (40001), so clients could never submit answers.

When the suffix parse fails, fall back to matching the full tail
against the pending question list before emitting 40001. Also add
maxRetries to the test home cleanup to absorb the async query-store
shard flush (ENOTEMPTY on macOS), matching fs.test.ts.

* fix(kap-server): preserve 40902 on duplicate resolve of colon-id questions

A retried bare resolve of a colon-bearing question id re-entered the
invalid-suffix fallback after the question settled, found no pending
match, and returned 40001 — bypassing the recently-resolved idempotency
window. Accept the tail in the fallback when it is recently resolved so
the shared duplicate-resolve path emits 40902 as documented.
2026-08-04 12:58:51 +08:00
Haozhe
f412e105b3
feat(acp-server): bridge questions via elicitation and support host slash commands (#2583)
* fix(acp): preserve cancels that arrive before the turn id is known

A session/cancel landing between prompt submission and the launch
round-trip found driver.turnId undefined and was dropped entirely; the
turn then ran to completion and the prompt resolved end_turn despite
the client's cancel.

The engine's cancel payload makes turnId optional (an empty call
cancels the active turn — the same contract kap-server's cancel route
relies on), so cancel() now issues an unaddressed cancel in that window
and flags the driver; the launch handler re-issues a precisely
addressed cancel once the id lands, and a no-launch outcome settles
cancelled instead of end_turn.

* fix(agent-core-v2): shut session MCP overlays down on service teardown

The ephemeral per-session MCP overlay was only shut down by the
session handle's dispose wrapper, but the DI container disposes session
scopes directly on workspace/app teardown, bypassing the wrapper — so
overlays of sessions still live at shutdown leaked their MCP
connections and stdio child processes.

Track live overlays in the lifecycle service: the handle wrapper
deletes-then-shuts-down (atomic, so close and service disposal can
never double-shutdown), and the service's own dispose shuts down
whatever is still tracked.

* feat(acp-server): bridge questions via elicitation and support host slash commands

- route AskUserQuestion through `elicitation/create` for form-capable
  clients (native multi-question + multi-select), falling back to the
  `request_permission` bridge on RPC failure
- add a `slashCommands` resolver option so hosts can merge their own
  command palette and skill aliases into `available_commands_update`;
  `/help` now lists the merged palette
- bridge `appendText`/`writeBytes` through client text capabilities
  (read-modify-write append, UTF-8-checked byte writes) with local
  filesystem fallbacks
- defer `available_commands_update` until after the lifecycle response
  settles so clients like Zed do not drop the notification
- propagate plan-toggle errors from `setMode` instead of silently
  reporting the new mode; make server `close()` idempotent

* style(acp-server): satisfy oxlint eqeqeq and await-thenable rules

* test(node-sdk): assert v1-v2 tokenCount parity for imports after eager counting
2026-08-04 10:44:56 +08:00
Haozhe
1328b32037
feat(acp): add experimental agent-core-v2 ACP server (kimi acp-v2) (#2571)
* feat(acp): add agent-core-v2 ACP server

- add ACP session lifecycle, configuration, permissions, and event bridging
- expose the experimental kimi acp-v2 command with terminal authentication
- add integration coverage and workspace build configuration

* test: use neutral example domains in test fixtures and docs

- replace placeholder hostnames (evil.com, foo.com, internal.corp,
  real.corp) with example.test / example.com in agent-core-v2 and
  kap-server tests
- replace fixture emails (x@y.com, a@x.com) with example addresses in
  minidb tests and README

* fix(acp): align acp-server with agent-core-v2 interfaces and address review

- add missing appendText to AcpHostFileSystem (IHostFileSystem drift)
- replace IAgentPromptService.prompt with inject
- use Turn.cancel() instead of abortController
- gate FS reverse-RPCs on client capabilities, fallback to local FS
- return PROTOCOL_VERSION constant instead of echoing client version
- remove misleading mcpCapabilities from initialize response
- dispose old session wrapper before replacing on load/resume
- fix object stringification lint error in convert.ts
- add acp-v2 to expected CLI sub-command list in test

* fix(acp): use enqueue for prompt submission, stop advertising unimplemented builtins

- replace IAgentPromptService.inject with enqueue so onBeforeSubmitPrompt
  hooks (prompt-blocking policy) are not bypassed
- stop advertising builtin slash commands (/help, /status, etc.) until
  builtin command execution is implemented
- add comment explaining appendText stays local (ACP has no append RPC)
- update skills test to match new availableCommands behavior

* fix(acp): filter turn events by turnId, surface auth failures as auth_required

- track turnId in driveTurn and ignore events from unrelated turns,
  preventing queued prompts from settling on the running turn
- reject prompt requests with auth_required when turn fails with an
  auth-related error code, enabling ACP client re-auth flow

* fix(acp): gate acp-v2 behind experimental flag, filter sessions by cwd

- add acp-v2 experimental flag (KIMI_CODE_EXPERIMENTAL_ACP_V2) and gate
  CLI command registration behind it
- filter session/list results by requested cwd instead of returning
  sessions from all workspaces
- detect hook-blocked prompts via PromptHandle.state and add TODO for
  streaming block messages once the hook context exposes them

* refactor(acp-server): rewire ACP server onto the klient facade

- replace direct agent-core-v2 scope/service access (ISessionLifecycleService,
  ISessionIndex, IEventBus, ISessionInteractionService, etc.) with the Klient
  facade: klient.global.sessions / klient.session(id) / agent('main') handles
- drive turns via agent.prompt() + session-level agent event subscriptions
  instead of per-prompt IEventBus wiring; settle on turn.ended
- route approval/question bridging through session.interactions events
- hide the thinking config option and skill catalog behind KLIENT-GAP markers
  until klient exposes those surfaces
- acp-fs: pass realpath through to the local inner backend
- klient: session.restore() rejects both null and undefined handles

* feat(agent-core-v2): add session delete and ephemeral per-session MCP servers

- add ISessionLifecycleService.delete: close a live session first, then
  remove its persisted data, evict the index read-model entry, and append
  a deleted tombstone to session_index.jsonl; unknown ids raise
  session.not_found
- add CreateSessionOptions/ResumeSessionOptions.mcpServers: session-owned
  MCP overlay merged over the workspace manager via
  MergedMcpConnectionView (an ephemeral name shadows a workspace server),
  never persisted, released when the session scope tears down
- return PromptLaunchResult from activateSkill so callers get the
  launched turn id and activation failures (unknown skill, busy) surface
- add ISessionSkillCatalog.list() as a wire-friendly catalog snapshot
- add ISessionIndex.remove for read-model eviction on delete

* feat(klient): expose session delete, per-session MCP, skills, and stream events

- session lifecycle contract: delete, resume/restore options, and
  CreateSessionOptions.mcpServers (ephemeral per-session MCP servers)
- add the session skills contract and facade accessors for the
  wire-friendly skill catalog snapshot
- register tool.call.delta, tool.progress, and compaction.* agent stream
  events so consumers can subscribe with typed payloads

* feat(acp-server): align ACP v2 server with acp-adapter capabilities

- complete the klient-facade rewire: ACP client connection holder and
  the terminal/* reverse-RPC runner routed through the Agent scope
- negotiate the protocol version on initialize instead of pinning v1
- compress oversized prompt images at the ACP ingestion point with a
  format gate, caption, and persisted originals; a cancel arriving
  mid-compression settles the prompt as cancelled without a turn
- stream tool call args via tool.call.delta (lazy pending create,
  cumulative replace, started upgrade) and refresh titles via
  tool.progress status updates
- report compaction progress and results after /compact via the
  compaction.* events
- answer unknown slash commands locally instead of sending them to the
  model
- accept legacy "<id>,thinking" model ids and legacy approve /
  approve_for_session approval option ids
- keep sessions without cwd metadata in cwd-filtered session/list
- sanitize wire errors: auth codes map to auth_required, turn.agent_busy
  to invalid_request, everything else to a fixed internal-error message
- bump @agentclientprotocol/sdk to ^1.3.0

* fix(cli): drop stale registerServerCommand call and sherif ACP SDK split

- commands.ts called registerServerCommand, which no longer exists on
  current main (the deprecated `kimi server` shim is registered via
  registerWebCommand), breaking typecheck, build, and every CLI test
  that builds the program
- sherif rejects the @agentclientprotocol/sdk major split between
  acp-adapter (^0.23.0, production kimi acp) and acp-server (^1.3.0,
  experimental); the two hosts legitimately target different SDK
  majors, so ignore the dependency in the sherif invocation

* test: update fixtures for acp-v2 flag and domain rename, refresh nix deps hash

- kap-server origin.test: two CORS cases still used foo.com after the
  whitelist moved to foo.example.com, so the origin was no longer
  whitelisted and the expected CORS headers were withheld
- node-sdk config.test: expect the new acp-v2 experimental flag in the
  harness feature metadata
- flake.nix: update the fetchPnpmDeps hash for the
  @agentclientprotocol/sdk 1.3.0 lockfile change

* fix(acp): widen the ACP v2 auth gate beyond OAuth-only providers

The gate consulted only auth.summarize(), which iterates providers
declaring an oauth section — configurations that authenticate with a
plain apiKey or provider env-bag credentials (no OAuth at all) were
rejected with auth_required even though the default model is fully
usable.

- klient: expose authSummaryService.ensureReady on the global auth
  facade (the contract already declared it)
- acp-server: gate on the engine's own readiness probe for the default
  model — config apiKey / env-bag / OAuth token all count, matching how
  the model is actually used — and fall back to "any logged-in OAuth
  provider" (the legacy adapter's first branch)
- test: an apiKey-only config passes the gate with auth enforcement on;
  the OAuth logout regression is unchanged

* fix(acp): reject concurrent prompts instead of displacing the in-flight turn

A second session/prompt while a turn is running overwrote the session's
only TurnDriver: the engine quietly queues plain prompts submitted
during an active turn (the launch resolves undefined, indistinguishable
from a hook-blocked launch), so the first prompt never settled and both
turns' events went unattributed.

Guard both model-bound launch paths (plain prompt and skill activation)
with a synchronous in-flight check and reject with invalid_request
(turn.agent_busy), matching the legacy adapter's busy semantics. Local
slash handling (builtins, unknown-command answers) is unaffected.
2026-08-04 10:20:24 +08:00
Haozhe
21185447fe
feat(agent-core-v2): add tokenCounting service with strategy config and measured anchors (#2563)
* feat(agent-core-v2): add tokenCounting service with strategy config and measured anchors

- add IAgentTokenCountingService as the single owner of token counts:
  context size, full-request size, and estimate primitives, replacing
  the scattered contextSize/tokenEstimate/fullCompaction paths
- add [token_counting] config section with strategy = measured+estimated
  (default) / measured / estimated, plus the KIMI_TOKEN_COUNTING_STRATEGY
  env override; measured zeroes all estimates, estimated ignores anchors
- keep a live measured-anchor ledger in TokenCountingModel: each LLM
  exchange writes a real anchor, undo truncates the ledger so the
  surviving prefix restores its REAL measured size instead of a
  re-estimate, and compaction rebases to a single anchor that blends the
  compaction exchange's measured summary output tokens
- skip writing an anchor when the stream reports no usage event instead
  of anchoring emptyUsage() zeros, which zeroed the context size and
  silenced compaction for providers without usage reporting
- return the strategy-resolved size (not measured) from rpc getContext
  so the tokenCount contract stays correct under the estimated strategy
- migrate all consumers (contextMemory, fullCompaction, llmRequester,
  rpc, mirrorAgentRun, sessionLegacy, kap-server legacyStatus, node-sdk,
  kimi-inspect) to the new service; edge bridges no longer read the wire
  model directly
- document [token_counting] and KIMI_TOKEN_COUNTING_STRATEGY in the
  bilingual config reference

* fix(kap-server): omit maxContextTokens instead of pushing 0 when unknown

- readLegacyStatus falls back to the default model's context limit when no
  model is bound, and omits maxContextTokens entirely when the limit is
  unknown (0 is the engine's UNKNOWN_CAPABILITY marker, not a real limit)
- profileService no longer emits maxContextTokens in agent.status.updated
  when the bound model alias does not resolve

* fix(agent-core-v2): resolve token_counting strategy only at the reporting edge

- keep measured anchors and heuristic estimates both recorded and feeding
  internal logic (compaction triggers, budgets, overflow backoff) regardless
  of the configured strategy
- add IAgentTokenCountingService.statusSize() as the single strategy-resolved
  outward reading and route the WS/REST/RPC status surfaces through it
- fix the context-size display falling back to provider-reported usage under
  the estimated strategy
- fix compaction overflow backoff retrying identical messages until failure
  under the measured strategy (the strategy-gated estimator read as 0)
2026-08-04 09:44:21 +08:00
7Sageer
c27a9f93a6
feat(agent-core-v2): add AGENTS.md discovery reminder (#2545)
Some checks are pending
CI / typecheck (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(agent-core-v2): add AGENTS.md discovery reminder behind experimental flag

When a tool call touches a directory whose AGENTS.md was not part of the
injected instructions (the init-time load only covers the project-root to
cwd chain), append a system reminder to the tool result suggesting the
model read it, at most once per file per agent.

The new agentsMdReminder domain (Agent scope, gated by the
agents-md-reminder experimental flag, default off) hooks
toolExecutor.onDidExecuteTool: Read/Edit/Write contribute their path,
Glob/Grep their search root, and Bash its structured cwd plus literal
directory operands extracted from the command's syntax tree via the
in-repo bash parser (listing commands only, bare names, conservative
skips). Probing walks projectRoot to the touched dir with the same
candidate rules as the init-time load (shared helpers in
profile/context), skipping fully-known directories and blank files.
The known-set is seeded by profileService after each successful
bind/apply/refresh and by sessionInit after /init, claimed
synchronously per discovered file, and published as a whole value only
after the reminder is attached, so parallel calls never duplicate a
reminder and failures leave files eligible for the next touch.

* fix(agent-core-v2): keep the AGENTS.md reminder on visible results and seed restored agents

A same-step duplicate vetoed by toolDedupe carries a placeholder result
that the dedupe hook swaps for the original's deferred result; attaching
the reminder there discarded it while the file was already counted as
reminded, and the telemetry still claimed it was shown. Skip the
placeholder (the call id sits in toolDedupe.syntheticCallIds until the
dedupe hook consumes it) so the reminder, telemetry, and known-set only
advance on results that reach the model; the original call then carries
the reminder for both by the time the deferred resolves.

Session resume and forks commit an already-rendered system prompt
(AGENTS.md content included) without going through bind/apply/refresh,
so no seed point fired and the known-set lagged behind the injected
set, producing false "not part of the injected instructions"
reminders. The first qualifying call of a never-seeded agent now
re-runs the init-time discovery with the same inputs (agent cwd, os
home, brand home) and seeds from it, once per agent; a discovery
failure leaves the agent unseeded so the next touch retries.

* refactor(agent-core-v2): fold agentsMdReminder inline rationale into file headers

The package comment convention keeps rationale in the top-of-file block
only; move the inline blocks' unique increments there (hook-order
fallback mechanics, synthetic-key existence condition, frozen-vs-live
Bash base, operand-less vs failed-resolution listings) and derive
AGENTS_MD_BASENAMES from AGENTS_MD_PLAIN_NAMES so the candidate names
stay single-sourced.

* fix(agent-core-v2): use resolved accesses for AGENTS reminders

* feat(agent-core-v2): drop the agentsMdReminder experimental gate

* fix(agent-core-v2): harden AGENTS reminder tool outcomes

* fix(agent-core-v2): preserve actual tool execution outcomes

* Persist AGENTS.md paths across profile restoration

* test(agent-core-v2): update useProfile snapshot for agentsMdPaths
2026-08-03 22:02:01 +08:00
7Sageer
98ef0f0b2f
fix(agent-core): replay v2 profile.bind records so resumed sessions keep their tools (#2567)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(agent-core): replay v2 profile.bind records so resumed sessions keep their tools

Sessions created by the v2 engine (CLI 0.31+, wire protocol 1.5) persist
the profile binding, including the tool allowlist, as a profile.bind
record. The v1 replay path had no branch for it and silently dropped the
record, so a session resumed by a v1 host (e.g. the VS Code extension via
kimi-code-sdk) never called setActiveTools and sent requests with no
tools at all (observed server-side as tools_count=0; the model emits
reasoning only and stops with empty content).

v1 replay now maps profile.bind onto config.update + setActiveTools when
activeToolNames is an array, skips the record otherwise so the
resume-time default-profile fallback still applies, and treats
tools.reset_active_tools as a no-op.

* fix(vis): handle v2 profile records in context projection

* fix(agent-core): avoid synthetic replay for v2 profile binds

* fix(vis): render v2 profile wire records
2026-08-03 20:59:46 +08:00
Haozhe
6ba75a173b
feat(config): add deprecation mechanism and rename loop retry limit (#2572)
* feat(config): add deprecation mechanism and rename loop retry limit

- agent-core-v2 config: declarative section `deprecations` (deprecated TOML
  keys are ignored and report a warning diagnostic; the file is never
  rewritten) and env binding `deprecatedEnv` (old var still resolves as a
  fallback with a warning), surfaced via the new
  `IConfigService.onDidChangeDiagnostics` event
- loop_control: rename `max_retries_per_step` to `max_attempts_per_step` and
  `KIMI_LOOP_MAX_RETRIES_PER_STEP` to `KIMI_LOOP_MAX_ATTEMPTS_PER_STEP`;
  `max_steps_per_run` moves onto the same mechanism (no longer silently
  mapped)
- kap-server: push the global `event.config.warning` WS event to every
  connection whenever the config warning set changes
- TUI: show config diagnostics in warning yellow at startup instead of the
  dim startup notice
- docs: config-files/env-vars (en+zh), regenerated config manifest, and the
  agent-core-dev config guide

* feat(cli): validate config.toml against v2 section registry in doctor

- add v2/validate-config.ts: validate config.toml with the agent-core-v2
  ConfigRegistry, reporting registered-section schema failures as errors
  and unknown top-level keys / deprecated keys and env vars as non-fatal
  warnings
- route `kimi doctor` config validation through the v2 validator when the
  KIMI_CODE_EXPERIMENTAL_FLAG master switch is on (lazy dynamic import,
  keeping the v2 module graph off the default path)
- let doctor checks surface non-fatal warning messages on OK results

* chore: downgrade loop-control changeset to patch
2026-08-03 20:17:01 +08:00
Haozhe
071b6a50d9
refactor(kap-server): own v1 message history and snapshot assembly (#2562)
- move the v1 message protocol and projection out of the engine into
  kap-server and delete the engine-side messageLegacy edge adapter
- add a shared message history loader that folds the main agent's wire
  journal into full history across compactions, backing both the
  messages routes and the snapshot endpoint
- drop the disk-reading SnapshotReader fast path; assemble snapshots
  from engine services for cold and live sessions, removing the
  KIMI_SNAPSHOT_READER, KIMI_SNAPSHOT_TIMEOUT_MS and
  KIMI_SNAPSHOT_CACHE_LIMIT knobs
- collect persisted wire record rebuild helpers in the transcript
  service
2026-08-03 17:55:28 +08:00
Haozhe
75395f6abb
feat(agent-core-v2): add lifecycle hook events and enrich hook payloads (#2558)
* feat(agent-core-v2): add lifecycle hook events and enrich hook payloads

New hook events:
- TurnStarted: fired from the turn.started bus event, covering queued
  turns, stop-hook continuations, and background/system turns that
  UserPromptSubmit misses
- UserPromptQueued: fired when a prompt cannot launch immediately,
  carrying the queue length
- TaskStarted: fired from the existing task.started bus event, so
  background tasks no longer only produce a completion-time Notification
- SessionHeartbeat: per-session 60s liveness beat, armed only when the
  event has hooks registered, letting hook consumers distinguish a
  session hanging on a long permission wait from a crashed one

Payload enrichment:
- client_type (host platform identity) on every event
- session_title on every session/agent-scoped event
- model and profile on SessionStart
- SessionEnd reason is now 'exit' or 'archive' instead of a hardcoded
  'exit'
- SubagentStart/SubagentStop now carry session_id/cwd like every other
  event

* fix(agent-core-v2): re-sync SessionHeartbeat timer on hook-index reloads

The heartbeat timer was armed once after the runner's initial load, so a
SessionHeartbeat hook contributed later by a plugin reload never produced
beats for existing sessions. The runner now exposes onDidReload (fired
after every index build), and the session adapter re-syncs on it: arming
when a heartbeat hook appears, disarming when none remains.

* fix(node-sdk): keep the v1 PluginInfo contract assignable with v2-only hook events

The v2 hook-event union is now a superset of v1's, which broke the
node-sdk type projection in two places:

- the klient contract's hookDefSchema rejected plugin manifests using
  the new events (TurnStarted, UserPromptQueued, TaskStarted,
  SessionHeartbeat) at validation time — accept them
- getPluginInfo returned the v2 PluginInfo where the SDK contract
  promises the v1 shape — project manifest.hooks through the v1-known
  event list (read from the legacy HookDefSchema), mirroring how the
  config mapper drops domains v1 does not know
2026-08-03 17:14:34 +08:00
qer
dfc55a5c97
fix(tui): make the /login already-logged-in notice visible (#2559)
The "Already logged in. Model configuration refreshed." confirmation was
rendered with the default dim text color, so users easily missed it and
assumed /login did nothing. Render it with the theme's success color,
matching the success styling used by the login spinner's "✓ Logged in."
line.

Co-authored-by: Mira Bot <mira-bot@moonshot.cn>
2026-08-03 16:37:55 +08:00
7Sageer
29c9e2ab20
docs: clarify secondary model default binding and override precedence (#2553)
The secondary_model section did not state whether spawned subagents are
forced onto the secondary model or only default to it, nor the full
override precedence. Make the semantics explicit in both locales:

- spawning resolves the model in order: explicit tool-call model ->
  profile model_preference -> configured secondary model (default)
- the tool's model parameter accepts only "primary" / "secondary"
- "primary" means the model the main agent is currently running, not
  necessarily default_model
- the user has no per-spawn switch; overriding is the main agent's
  decision or a profile setting

Also unify secondary-model terminology and the [models] alias wording
across the config-files, agents, slash-commands, and env-vars pages.
2026-08-03 15:42:09 +08:00
Haozhe
e6a655e101
feat(agent-core-v2): add lifecycle ledger, dynamic registry, and cascade engine (#2551)
* feat(agent-core-v2): add lifecycle ledger, dynamic registry, and cascade engine

- add `_base/lifecycle` Ledger: ordered registrations with strict reverse
  serial teardown, sync/async dual-track disposers, uninterruptible rollback,
  effect return forms, child ledgers, introspection tree, and teardown-reason
  propagation
- delegate Disposable/DisposableStore/MutableDisposable, Scope.dispose, and
  InstantiationService.dispose to the ledger; retire _constructionOrder
- ServiceCollection: entries carry uid/pinned/recipe, delete(), and a
  per-token availability event; add provide/unprovide to IInstantiationService
- add a persistent dependency graph recording constructor-injection edges
  with affectedSet/topo queries, plus a per-container cascade engine
  (contagion teardown/rebuild transactions, five unit states, pending index,
  abort hook, history ring); TestInstantiationService.set routes via provide

* fix(agent-core-v2): cascade instance replacements, tolerate abort rejections

- provide/unprovide of pre-materialized instances now runs as a cascade
  transaction too, so live dependents are torn down and rebuilt instead of
  holding a stale dependency; re-affirming the live instance stays a no-op
- a rejecting onWillCascade promise is logged and the cascade proceeds
  (best-effort), matching the synchronous-throw handling

* feat(agent-core-v2): propagate cascades across scopes along instance edges

Implements the revised D9 (the firewall design was dropped): instance edges
are scope-tagged on both ends and point child -> parent only, the dependency
graph is shared by the whole scope tree, and each change runs as one
tree-wide transaction orchestrated by the submitting scope's engine —
contagion computed over the global graph, teardown in global reverse
topological order (deepest first), rebuild in global topological order, with
each scope's engine executing its own units. Descendant scopes dying
mid-transaction are skipped idempotently; the request queue, in-flight set,
and settle waiters are tree-shared so cross-tree transactions are serialized
by the orchestrator; shadowed tokens stay outside the contagion set.
2026-08-03 15:40:40 +08:00
Haozhe
3e425212b6
refactor(agent-core-v2): code all domain failure modes as Error2 (#2552)
* refactor(agent-core-v2): code all domain failure modes as Error2

- wrap bare throws across agent/session/app/workspace/os/wire/kosong/mcpCore domains in coded Error2, keeping messages verbatim and moving structured data into details with the original error as cause
- add new wire codes (agent.already_exists/already_running/not_a_subagent/not_owned/type_not_allowed/max_tokens_exceeded, task.limit_exceeded, cron.expression_invalid, web.invalid_url/private_address/fetch_failed, mcp.oauth_failed, skill.parse_failed/nested_too_deep, wire.migration_missing) to the protocol KimiErrorCode union and the kap-server zod schema; register shell.git_bash_not_found and session.plan_mode_invalid
- re-base domain error classes onto Error2 (SkillParseError, UnsupportedSkillTypeError, HostFolder*, AgentFileParseError, NestedSkillTooDeepError, AlreadyAuthorizedError, HttpFetchError) keeping class names and instanceof consumers intact
- convert caller-bug and unreachable guards outside _base to BugIndicatingError
- fix the agent tool's task-limit remap never firing by branching on the task.limit_exceeded code instead of a stale message string

* refactor(kosong): make ChatProviderError family born-coded via Error2

- move the provider/context code string constants to kosong/contract/errors.ts
  and compute each class's wire code at construction (status code / finish reason)
- move sanitizeStatusErrorMessage to the contract and fold status details
  (statusCode / requestId / traceId) into Error2 details at birth
- slim translateProviderError down to the abort guard plus the foreign-error
  fallback; ProtocolErrors keeps registering the domain via re-exported constants
- update errors.md conventions and tests for the pass-through behavior

* feat(storage): add permission_denied and disk_full error codes

- extend StorageErrors with storage.permission_denied / storage.disk_full
  (both non-retryable, with user-facing actions)
- map errno at the backend boundary in toStorageIoError: EACCES/EPERM,
  ENOSPC, unexpected ENOENT → not_found, everything else io_failed; the
  message now carries the mapped reason
- register the two codes in the KimiErrorCode protocol union and the
  kap-server zod schema, and document the mapping in errors.md

* fix(protocol): mirror all KimiErrorCode values in kimiErrorCodeSchema

the zod enum lagged the type union by 35 codes (agent.*, os.fs.*,
os.process.*, storage.*, wire.*, skill/task/mcp/cron/web additions), so
protocol consumers could type the new codes but rejected them at runtime
validation; spotted by Codex review on #2552
2026-08-03 15:31:57 +08:00
liruifengv
e22479a62e
feat(kap-server): expose effective experimental flags in /meta (#2417)
Some checks failed
CI / build (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test (5) (push) Has been cancelled
CI / test-pi-tui (push) Has been cancelled
CI / test-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / typecheck (push) Has been cancelled
Release / Release (push) Has been cancelled
Nix Build / Check flake.nix workspace sync (push) Has been cancelled
Release / Deploy docs (push) Has been cancelled
Release / Native release artifact (push) Has been cancelled
Release / Publish native release assets (push) Has been cancelled
Nix Build / nix build .#kimi-code (push) Has been cancelled
2026-08-01 10:22:23 +08:00
Sampson
a5960b3905
test(agent-core-v2): disables commit.gpgsign (#2475) 2026-08-01 10:16:51 +08:00
StaR4y
bfa00807c9
fix(web): correct dark monochrome composer styling (#2083)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
Co-authored-by: StaR4y <star4y@origin.pw>
Co-authored-by: qer <wbxl2000@outlook.com>
2026-07-31 22:38:45 +08:00
qer
7648874730
docs(changelog): sync 0.31.1 from apps/kimi-code/CHANGELOG.md (#2470) 2026-07-31 20:21:53 +08:00
Mangesh Raut
eaab2b6f28
fix(cli): fall back to built-in models.dev catalog when fetch fails (#2416)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
When the remote models.dev catalog cannot be fetched, fall back to the
built-in catalog so CLI/TUI model selection keeps working offline or
under network failure. Import the shared helper via the #/utils alias.
2026-07-31 19:54:09 +08:00
github-actions[bot]
6b56c11697
ci: release packages (#2403)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-31 19:29:29 +08:00
qer
326e1fb6ce
fix(web): restore chat code block styling after the markstream upgrade (#2459)
* fix(web): restore chat code block styling after the markstream upgrade

markstream-vue 1.0.9 renders shiki code blocks through stream-diffs
(code-editor-container) instead of <pre>, and drives font size, line
height, and font family from monacoOptions applied as inline styles.
With only lineNumbers passed, blocks fell back to 12px/18px in the
inherited proportional UI font, and the pre/copy-button/content CSS
overrides in Markdown.vue no longer matched anything.

Pass fontSize/lineHeight/fontFamily/padding through codeBlockProps
monacoOptions (the only channel that reaches the shiki renderer),
retarget the dead overrides to code-block-shell-content and
code-action-btn, and hide the loading fallback's hardcoded line-number
gutter so the highlight upgrade no longer shifts layout.

* fix(web): keep code block metrics in sync

* fix(web): pin chat code font to 13px and align the loading fallback

Tying the code font size to the Appearance UI font size setting changed the
default rendering from the 13px design token to 14px and broke the 15/14/13
type scale; revert to the fixed token size. Keep the fallback ↔ settled
alignment: the restored monaco padding option feeds the fallback pre's
inline padding (12px, ignored by the shadow-root renderer), and a relative
1.65 line-height with !important beats the inline 1.5x default upstream
stamps on the fallback.

* chore(web): pin markstream-vue and its renderer stack to exact versions

The markstream family ships breaking rendering changes in patch releases
(1.0.8 swapped the code-block engine and renamed its DOM classes), so a
floating caret range hands merge control to upstream. Pin markstream-vue,
stream-diffs, stream-markdown and stream-monaco exactly — upgrades become
deliberate actions with a visual check, same posture as @chenglou/pretext
in the same file.
2026-07-31 19:14:02 +08:00
7Sageer
1f3f5dadaa
feat(agent-core-v2): interruption reminder for user-cancelled turns (#2400)
* feat(agent-core-v2): interruption reminder for user-cancelled turns

When the user interrupts a turn with Esc, append a durable
<system-reminder> (origin: injection/interruption) to the agent context
via a new loop aspect watching turn.ended, so the model learns the
previous turn was deliberately cut off. The marker persists to the
wire, replays on resume, stays hidden from transcripts, skips non-user
aborts and steer, and does not stack on repeated cancels.

Two supporting fixes:

- An aborted LLM stream now persists its accumulated partial
  text/thinking as content.part loop events instead of dropping every
  produced token; gated on the turn signal so retried or
  step-cancelled attempts keep their partial output out of the record.
- The turn.cancel wire op carries an optional reason
  ('user_cancelled' | 'aborted') so cold readers can tell deliberate
  interrupts from programmatic aborts. Goal-lifecycle cancels now pass
  an explicit programmatic reason to keep that field honest.

* feat(transcript): mark user-cancelled turns with an interruption marker

Project the deliberate user interrupt onto the transcript timeline: the
live projector emits an 'interruption' marker when a turn ends with
interruptReason 'user_cancelled', and the cold fold consumes the
persisted turn.cancel reason into the same marker. Programmatic aborts
keep surfacing through their own outlets (errors, goal/task state), and
queued cancels that left no visible residue are skipped.

* fix(agent-core-v2): make user-turn cancellation idempotent and reconcile interruption reminders on restore

* fix(transcript): dedupe user-cancelled interruption markers by turn in the cold fold

* chore(agent-core-v2): regenerate state manifest after merging main

* refactor(agent-core-v2): split interruptionReminder out of the loop domain

The loop domain owns turn execution mechanics; whether an interrupted turn
should produce a model-visible reminder is a model-context policy. Move it
into its own L4 domain with its own wire model that cross-reduces the
loop's turn.cancel fact, and rename the op to interruptionReminder.recorded.

---------

Signed-off-by: Haozhe <yanghaozhe@moonshot.ai>
Co-authored-by: Haozhe <yanghaozhe@moonshot.ai>
2026-07-31 18:17:14 +08:00
Rick
302b2cd680
fix(vscode): all AskUserQuestions should be answered and added to the context (#2326)
Co-authored-by: rickgao <rickgao@tencent.com>
Co-authored-by: qer <wbxl2000@outlook.com>
2026-07-31 17:40:12 +08:00
Haozhe
44d34bbd56
refactor(agent-core-v2): move host runtime args onto IBootstrapService (#2460)
* fix(agent-core-v2): resolve package self-references in check-import-boundaries

Imports spelled @moonshot-ai/agent-core-v2/<path> (the legal `./*` export
self-reference) were treated as external packages, letting kosong layer
violations through that spelling pass the checker.

* refactor(agent-core-v2): move host runtime args onto IBootstrapService

- Add HostArgs under BootstrapInput.args / IBootstrapService.args
  (agentFiles, skillDirs, requestHeaders, displayName, replyStyleGuide),
  mirroring VS Code's NativeParsedArgs on the environment service
- Remove the narrow per-domain runtime-options services and their seed
  functions: IAgentCatalogRuntimeOptions, ISkillCatalogRuntimeOptions,
  IHostIdentity
- Reduce IHostRequestHeaders to a pure kosong port contract and bridge it
  from bootstrap args via a new app/kosongConfig adapter, keeping kosong
  free of app-layer imports
- Pass host args through bootstrap() at the composition roots (kap-server,
  v2 print CLI, node-sdk) instead of seeding services
- Persist SDK provider removal as one atomic multi-section config replace

* fix(config): persist provider refresh updates atomically

- expose atomic multi-section config replacement through klient and SDK
- stage provider removals before one atomic write in TUI refresh
- briefly drain startup refresh during shutdown
2026-07-31 16:47:34 +08:00
Haozhe
4c4df1bb05
feat(agent-core-v2): persist the terminal turn.ended wire record (#2457)
* feat(agent-core-v2): persist the terminal turn.ended wire record

- add a persisted turn.ended op (turnId, reason, error, durationMs)
  dispatched from the loop's runTurn finally block, alongside the event
- fold the record back in the transcript cold rebuild: terminal state
  (blocked folded into failed, mirroring the live wire edge), durationMs,
  error message and endedAt; journals without the record keep the
  grouping default
- restrict the test harness's snapshot waiters to emit entries so the
  same-named wire record no longer shadows the turn.ended event

* chore(agent-core-v2): stabilize unique-symbol keys in the state manifest

The checker names a unique symbol key __@name@NNNN, where NNNN is a
compilation-global counter that shifts with unrelated type additions and
churns the generated manifest. Render the stable __@name form instead.

* fix(transcript): map turn.ended around hidden turns in the cold fold

RetryStepRequest opens a real engine turn with origin 'retry' but
contributes no context messages, and a queued-then-cancelled
reservation consumes an engine id without starting. Both make engine
turn ids drift from the grouping ordinals, so matching turn.ended by
ordinal could stamp a later visible turn with the wrong terminal state.
Replay the loop's turn-clock records (turn.prompt / turn.cancel) and
map engine ids to ordinals past the hidden ids; the hidden turns' own
end records map nowhere and are dropped.
2026-07-31 16:25:43 +08:00
liruifengv
32d693f644
feat(tui): ask for workspace trust on startup with the v2 engine (#2453)
* feat(node-sdk): expose workspace trust state and trust grant on the v2 client

* feat(tui): ask for workspace trust on startup with the v2 engine
2026-07-31 14:21:04 +08:00
Haozhe
071d56940f
refactor(agent-core-v2): remove the L0-L7 domain layering and clean up comment conventions (#2451)
* refactor(agent-core-v2): move workspace-domain internals into internal/ dirs

- move sessionLifecycle/addressing into internal/
- move workspaceFs errors/fsProcess/fsSearch/rgLocator/runRg into internal/
- move workspaceMcpConfig/config-loader into internal/
- update import paths in services, index.ts, and tests

* docs(agent-core-v2): strip non-header comments, make headers file-local

- remove all symbol JSDoc and inline // and /* */ comments under src/;
  file-top header comments and directive comments (eslint, @ts-*) are kept
- rewrite file headers to describe only what the file itself does, dropping
  references to other files and see-X / lives-in / consumed-by relationships
- no code changes

* refactor(agent-core-v2): remove the L0-L7 domain layering and its lint guard

- replace check-domain-layers.mjs with check-import-boundaries.mjs,
  keeping only the v1-import ban and the kosong subtree rules
- rename the lint:domain package script to lint:imports
- drop the (Ln) layer label from every file-header identity line
- update AGENTS.md, package docs, and the agent-core-dev skill to match
2026-07-31 13:45:33 +08:00
Bowen Liang
e111c878fd
fix(web): unify permission mode order and risk colors across settings surfaces (#2125)
* web: reorder default permission options in Agent settings

Align the Agent settings default-permission segmented control with the
Composer toolbar order, arranging modes from safest (manual) to most
permissive (auto).

* web: reorder mobile permission cycle from safest to most permissive

The mobile settings sheet still cycled manual → auto → yolo, jumping from
the safest mode straight to the most permissive one on a single tap. Align
the tap-to-cycle order with the Composer menu and Agent settings
(manual → yolo → auto).

* web: align permission risk colors with the Composer's progression

Both the desktop status panel and the mobile settings sheet mapped
yolo → danger and auto → warning, the inverse of the Composer menu
(yolo → warning, auto → danger). Since auto is the most permissive
mode, it should carry the danger color everywhere.

---------

Co-authored-by: qer <wbxl2000@outlook.com>
2026-07-31 13:28:05 +08:00
7Sageer
95a656ca61
fix(agent-core): strip the no-op subagent model parameter while the secondary-model experiment is off (#2449)
The Agent/AgentSwarm tool schemas always advertised a \`model\` choice
parameter, so the secondary-model concept entered the prompt even with
the experiment disabled. Gate the advertised JSON schema on the flag in
both engines: off (the default) drops the parameter, on keeps it, and
spawn-time resolution already falls back to the caller's model either
way.

Also scrub ambient KIMI_CODE_EXPERIMENTAL_* env vars in both packages'
vitest setup so flag-dependent tool schemas in llm.tools_snapshot stay
deterministic regardless of the developer shell.
2026-07-31 13:09:56 +08:00
Haozhe
ed7a4cc095
feat(kap-server): add session-less POST /workspace/fs:search route (#2437)
* feat(kap-server): let fs:search resolve a workspace ref for draft sessions

- fs:search accepts a workspace id or absolute root in the session_id slot
  so the @ file mention works before the session exists
- kimi-web searchFiles falls back to the active workspace id in draft state

* fix(agent-core-v2): report empty thinking level for unbound main agent

- sessionLegacyService.status returns thinking_level '' when the main
  agent has no bound model (mirroring model: undefined), so clients
  fall back to the catalog default instead of folding in the wire
  model's 'off' zero value
- add regression test for a never-bound main agent status
- add web changesets: draft @ file mention, new-session thinking level

* perf(minidb): make text index rebuilds async and non-blocking

- TextIndex.build() yields to the event loop during tokenization and
  batches postings writes (~1 MiB), so large rebuilds no longer
  hard-block the host process
- writes landing mid-build are queued and replayed onto the new base at
  swap time, keeping the rebuilt index exact
- PostingsFile.rebuildSync renamed to async rebuild with a synchronous
  commit section (beforeRename hook + atomic rename)
- onCompacted hook is now awaited (sync or async); open-time compaction
  runs in the background so open() returns without blocking on the
  snapshot rewrite and postings rebuild
- compaction skips the postings rebuild when the index's write buffer is
  clean (needsRebuild)
- createTextIndex registers before building so concurrent writes feed
  the build queue; dropTextIndex throws while a build is in flight

* refactor(agent-core-v2): rename workspaceHandler to sessionLifecycle

- rename IWorkspaceHandlerService to ISessionLifecycleService and move
  src/workspace/workspaceHandler/ to src/workspace/sessionLifecycle/;
  update all consumers (gateway, sessionExport, sessionLegacy,
  sessionLookup, kap-server, klient, node-sdk, kimi-inspect, kimi-code)
- rename IStateService to IAppStateService and add the Workspace-scope
  IWorkspaceStateService, so the state domain spans all four scope tiers
- add cascading StateRegistry.inspect(): each tier injects the parent
  tier's registry and folds App to current scope into one StateInspection
  tree; check-domain-layers gains a Rule 2b exemption for state-on-state
  imports

* feat(kap-server): add session-less POST /workspace/fs:search route

Carry the workspace reference (registered id or absolute root) in the
request body and resolve it to the same Workspace-scope fs service the
session route uses, so clients no longer borrow the session route's
{session_id} slot. kimi-web's @ file mention now calls this route with
the workspace ref instead of a session id; the session-route fallback
stays for wire compatibility.

* refactor(agent-core-v2): register workspace-scope service state into IWorkspaceStateService

- move workspaceDirs / workspaceInstructions / workspaceSkillCatalog / workspaceTrust
  runtime state from bare instance fields into the workspace state container
- extend gen-state-manifest.mts to scan app/workspace scopes, emitting
  AppStateSnapshot / WorkspaceStateSnapshot alongside Session/Agent
- regenerate docs/state-manifest.d.ts and update AGENTS.md + agent-core-dev skill
- update affected tests to register the state services and assert the new state keys
2026-07-31 12:11:26 +08:00
liruifengv
bb2919eb81
fix(tui): reduce frequent full-screen redraws (#2442)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
2026-07-31 11:35:39 +08:00
Haozhe
17dfd49768
feat(agent-core-v2): introduce the Workspace domain and the agent-profile registry extension point (#2366)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(agent-core-v2): insert Workspace lifecycle scope and remove mutable cwd paths

- Insert LifecycleScope.Workspace between App and Session
- Delete session/workspaceCommand domain (addAdditionalDir) and node-sdk RPC
- Remove profile cwd mutation; cwd is fixed at creation
- Make ISessionWorkspaceContext read-only; seed additionalDirs at creation
- Remove TUI and vscode /add-dir commands (to return workspace-scoped)

* feat(agent-core-v2): add Workspace scope with handler-owned session lifecycle

- Add IWorkspaceLifecycleService (App scope): handler registry,
  create-or-get handlerFor with inflight join
- Add workspace/workspaceContext seed and workspace/workspaceHandler
  (session create/resume/fork as handler child scopes)
- Delete App-level ISessionLifecycleService; callers compose
  index -> handlerFor -> handler via sessionLookup helpers
- Slim IBootstrapService; persistence addressing via handler chain
  (disk layout byte-identical)
- kap-server routes rewire internally; /api/v1 wire unchanged,
  debug surface gains workspace addressing
- Pin red line in domain lint: session/agent must not import
  workspace domains

* feat(agent-core-v2): collect workspace resources into the handler scope

- Add Workspace-scope catalogs for skills and agent profiles,
  instructions service, and a shared MCP connection manager
  (built at materialization, refreshed by watch/plugin events)
- Session catalogs keep their APIs but read seeded snapshots and
  refresh via change events; ISessionMcpService removed
- Session create options carry no mcpServers; MCP sources are
  config file (wins on name conflicts) and plugins only
- Agent profile/mcp consume the seeded providers

* feat(agent-core-v2): restore add-dir as a workspace-level capability

- Add workspace/workspaceDirs: shared additional-dir set with
  addDir({path, persist}); persist=true writes .kimi-code/local.toml,
  local.toml watch drives cross-process refresh
- ISessionWorkspaceContext becomes a live read view fed by the
  ISessionWorkspaceInfo seed contract and change events
- Restore Session.addAdditionalDir in kimi-code-sdk 1:1, mapping to
  the workspace service; restore TUI/vscode /add-dir verbatim

* feat(agent-core-v2): collect os-level services into the workspace scope

- Move fs service, fs watch (shared subscription fan-out), process
  runner, and a git facade to Workspace scope; sessionFs domain removed
- Add IWorkspaceToolPolicy with workspace veto wired through tool
  activation, execution guard, composed evaluation, and profile
  prompt projection; injected via ISessionToolPolicyGate seed
- kap-server fs routes and fs.watch bridge remap to the workspace
  services; wire unchanged

* refactor(agent-core-v2): clean up workspace-domain leftovers and docs

- Drop dead code: v2 mergeCallerMcpServers, the transitional
  ISessionContext.additionalDirs field, an unreachable guard
- Fix stale domain references in comments; correct test names
- Give the fs-watch refresh test a realistic wait budget under load
- Document the four-scope model and workspace domain in AGENTS.md,
  agent-core-v2 docs, and the agent-core-dev skill

* test(node-sdk): wait for the initial MCP connect to settle in the parity list test

v1 connects in the background after create resolves while v2 awaits it
inside create, so an immediate list can catch either side still pending
under CI load

* refactor(agent-core-v2): extract git work-tree discovery into the git domain

- add the pure findGitWorkTree probe in app/git/workTree and expose it
  as IGitService.findWorkTree
- switch the git permission policies off the local
  findLocalGitWorkTreeMarker helper to the DI service
- reuse findGitWorkTree for AGENTS.md project-root discovery in
  agent/profile/context.ts
- add findWorkTree coverage to gitService.test.ts

* feat(kimi-inspect): add Workspace Services view

- add WorkspaceServicesView rail view with a workspace picker on top;
  proxies resolve workspace-scope Services on the /workspace/:id route
- extend ChannelScope, ServiceTarget, and ServicePanelDef scope with
  'workspace', routed via client.workspace(id).service
- wire the new view into NavRail and App

* refactor(agent-core-v2): extract mcpCore and workspaceMcpConfig domains

- move the scope-agnostic MCP connection layer (stdio/http/sse clients,
  connection manager, oauth, config schema, tool naming) from agent/mcp
  to the new mcpCore domain
- move the [mcp] config section to app/mcpConfig and OAuth credential
  persistence to app/mcpConfig/oauthStore
- introduce the workspace/workspaceMcpConfig domain owning the effective
  MCP server set (mcp.json files + plugin contributions, refreshed by
  fs watch); workspaceMcp keeps pure connection orchestration
- update the plugin domain, session MCP handle, klient/node-sdk
  contracts, and tests accordingly

* refactor(agent-core-v2): remove the fault-injection experimental feature

- delete the faultInjection domain (flag definition, IFaultInjectionService
  contract, FaultInjectionService implementation)
- drop the requester-side take() injection point and the constructor
  dependency from llmRequester
- remove the flag-gated test cases and the IFlagService stub they needed
- regenerate the state manifest without the faultInjection state keys

* feat(agent-core-v2): gate project-level MCP config behind workspace trust

Add the Workspace-scope IWorkspaceTrust service: an explicit, per-workspace
trust marker persisted under the home (IAtomicDocumentStore, keyed by
encodeWorkDirKey(root)) so a checked-out tree cannot pre-trust itself.
While a workspace is untrusted, workspaceMcpConfig skips the project-level
.mcp.json and .kimi-code/mcp.json files (user-level config and plugin
contributions still load); a trust flip reuses the reload path, so project
servers connect on trust and disconnect on untrust.

Expose the state over kap-server REST: GET /workspaces/{id}/trust,
POST /workspaces/{id}/trust, POST /workspaces/{id}/untrust.

* feat(kimi-inspect): replace the workspace picker with a directory browser

The Workspace Services view now keeps a server-side directory browser in a
left sidebar (over IHostFolderBrowser) instead of a <select> of registered
workspaces. Entries that are registered workspaces carry a workspace badge
plus their IWorkspaceTrust trust state; selecting an unregistered folder
registers it on demand via IWorkspaceService.createOrTouch.

* fix(agent-core-v2): resolve the effective cwd into the profile binding

A default-bound agent recorded no cwd in its profile.bind payload, and no
caller configures ProfileServiceOptions.cwd, so the profile service's cwd
getter fell through to '' and refreshSystemPrompt() rebuilt the prompt
from the server process's cwd: an AGENTS.md edit dropped the workspace
instructions (or swapped in unrelated ones).

bind() now persists the resolved effective cwd (the input's, or the
session's when the input omits it) into profile.bind — the Model's cwd
stays creation-fixed and is always set. The getter's last resort is the
session's own cwd (the value legacy bindings resolved against) instead of
a bare ''.

* refactor(agent-core-v2): introduce the contribution/registry/catalog extension point for agent profiles

- App-scope IAgentProfileRegistry: any scope can register an
  AgentProfileContribution keyed by (sourceId, workspaceKey); dedup per
  source id, change events drive catalog re-projection
- workspaceAgentProfileLoader domain owns agent-file discovery end to end
  (parse / roots / SYSTEM.md / explicit runtime files) with five
  Workspace-scope loaders (workspace / user / plugin / extra / explicit)
  tagged with the handler's workspaceId; internals live under internal/
- SessionAgentProfileCatalog projects the registry directly (name dedup,
  priority adjudication, builtin override rule, inspect()); the
  workspace-catalog + sessionData seed relay is gone
- builtin code contributions register as the 'builtin' entry via
  BuiltinAgentProfileLoader; plugin agent roots are provided by the
  plugin domain as PluginAgentRoot
- remove cwd from the profile binding chain (BindAgentInput /
  ProfileBindingSnapshot / AgentConfigData / ProfileModelState /
  profile.bind op) — it is always the session's frozen cwd; legacy
  wire.jsonl records replay fine (the schema strips the field)
- share markdown frontmatter parsing via _base/text/frontmatter

* fix(agent-core-v2): reconcile the workspace refactor with main

- restore the branch's klient workspaceId scope extension lost to a
  file-level conflict resolution (main had no further changes there)
- stub the plugin system-prompt dependencies main added to the profile
  service in the profileOps / skillCatalog tests
- correct PLUGIN_SKILL_SOURCE_ID to the App skillSource domain (Agent
  scope must not import the Workspace domain)
- kap-server workspaceLayout test supplies the now-required hostIdentity
- regenerate wire/state/config manifests

* fix(agent-core-v2): export the agent-file parse primitives the v2 print CLI consumes

The internal/ split kept parseAgentFileText / resolveAgentPath off the
package entry, but apps/kimi-code's v2 print runner imports them from
@moonshot-ai/agent-core-v2 for --agent-file. Export the two symbols by
name; everything else under internal/ stays domain-private.

* feat(agent-core-v2): return cwd listing for empty fs:search query

An empty fs:search query used to fail request validation (query had a
minimum length of 1), so @-mention pickers had no starting set right
after typing "@". The workspace fs service now answers an empty query
with the workspace root's top-level entries — directories first,
hidden entries excluded, gitignore and exclude_globs honored — mapped
into the search-hit shape (score 1, empty match positions) and capped
by limit. The mirrored protocol wire schema is relaxed in sync.

* test: cover cron-fired steer context and titled session creation

- agent-core-v2: e2e asserting a cron-fired steer turn carries earlier
  tool results (the CronCreate job id) into the provider request
- klient: conformance case creating a titled session through implicit
  workspace materialization
2026-07-31 00:38:11 +08:00
qer
5c0ec2938a
fix(web): upgrade markstream-vue to 1.0.9-beta.1 and enable Monaco code highlighting (#2415)
* fix(web): upgrade markstream-vue to 1.0.9-beta.1 and enable Monaco code highlighting

* fix(nix): update pnpm deps hash for markstream-vue 1.0.9-beta.1
2026-07-30 20:01:57 +08:00
Haozhe
f1a3475ad5
fix(agent-core-v2): write refresh results in one atomic config transition (#2410)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(agent-core-v2): write refresh results in one atomic config transition

- add IConfigService.replaceSections: applies several domains in a single
  state transition — one disk write, one effective rebuild, change events
  fire only after all domains took effect
- rework ProviderDiscoveryService to absorb the orchestrator's two-phase
  removeProvider/setConfig host contract into one replaceSections write, so
  the kosong registries never pass through a halfway-removed catalog
- stop writing the env-synthesized __kimi_env__ slice to config; the
  bridge's event-driven sync carries it into the registries on its own
- fixes sporadic "model is not configured" errors when starting kimi web,
  caused by the background refresh transiently clearing the model catalog
  while the first session was being created

* fix(agent-core-v2): stage replaceSections writes before mutating raw config

Validate and strip every domain into a staged copy of the raw/memory layer
first, then swap it in only after the whole batch succeeds — previously a
later domain failing validation left earlier domains already applied to
this.raw/this.memory while the call reported failure, exposing a partially
applied user layer to inspect() and future merges.
2026-07-30 18:42:20 +08:00
liruifengv
d8f455d694
docs(changelog): sync 0.31.0 from apps/kimi-code/CHANGELOG.md (#2404)
* docs(changelog): sync 0.31.0 from apps/kimi-code/CHANGELOG.md

* docs(changelog): polish 0.31.0 entries and note secondary model as experimental
2026-07-30 15:39:17 +08:00
qer
479403e701
chore(vscode): release 0.6.6 (#2401)
* chore(vscode): release 0.6.6

* chore(vscode): release 0.6.7

* chore(vscode): fold the sign-in wording fix into 0.6.6

* chore(vscode): backfill the 0.6.5 changelog entry
2026-07-30 15:33:19 +08:00
qer
0f3b106c42
fix(vscode): reword the sign-in waiting message to authentication (#2402) 2026-07-30 15:13:57 +08:00
liruifengv
ea81c9a3c5
feat(kap-server): expose the managed-account profile at GET /oauth/userinfo (#2363)
* feat(kap-server): expose the managed-account profile at GET /oauth/userinfo

* refactor(oauth): serve the userinfo profile as the camelCase domain type end to end

* style(agent-core-v2): drop the method-local comment on getManagedUserInfo

* chore: drop the userinfo endpoint changeset

* test(kap-server): pass the required host identity in the userinfo route test
2026-07-30 15:05:55 +08:00
github-actions[bot]
bc28e9d802
ci: release packages (#2342)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-30 14:56:30 +08:00
qer
6d0a046488
fix(vscode): keep sign-in reachable from the no-models screen (#2393)
* fix(vscode): keep sign-in reachable from the no-models screen

* fix(vscode): stop flagging bundled this.require(...) calls as VSIX runtime dependencies
2026-07-30 14:47:37 +08:00
Haozhe
d36f4c58f6
refactor(agent-core-v2): remove the fault-injection experimental feature (#2399)
- delete the faultInjection domain (IFaultInjectionService, its Agent-scope
  implementation, and the fault-injection experimental flag)
- drop the llmRequester's per-attempt fault consumption point and the
  faultToError helper
- remove the fault-injection test cases and DI wiring from the requester
  service tests, and the domain's layer-registry entry
- regenerate the state manifest without the faultInjection.* state keys
2026-07-30 14:45:31 +08:00
Haozhe
d10b1c1308
fix(agent-core-v2): treat cache entries missing required fields as cold misses (#2395)
* fix(agent-core-v2): treat cache entries missing required fields as cold misses

- normalize `archived` to a boolean when mirroring session metadata to the
  read model, so entries for pre-`archived` sessions no longer lose the key
  during JSON serialization
- add a runtime shape check on read-model cache hits; entries missing
  required fields are rebuilt from disk and overwritten, self-healing
  poisoned entries written before the fix
- log a warning when the TUI session picker fails to fetch sessions instead
  of silently showing "No sessions found."

* chore: add changeset for session index cold-miss fix
2026-07-30 14:28:28 +08:00
liruifengv
40172c7ca9
feat: unify the host identity across OAuth, telemetry, and kap-server (#2382)
* refactor(oauth): make X-Msh-Platform an explicit host identity field

X-Msh-Platform was hardcoded to kimi_code_cli in createKimiDeviceHeaders,
so non-CLI hosts could not state their own platform and the desktop had
to patch the header after the fact. KimiHostIdentity now carries a
required platform (every host declares its own value; the CLI constant
stays the fallback only for direct createKimiDeviceHeaders callers), and
userAgentProduct is renamed to productName so the transport identity
uses one name everywhere.

All in-repo identity constructions pass platform explicitly; the wire
value for CLI and VS Code hosts is unchanged (kimi_code_cli).

* feat(agent-core-v2): carry the host identity in the bootstrap snapshot

Replace the flat clientVersion field with a required clientIdentity
(KimiHostIdentity) so every consumer reads the same host identity
object: OAuthToolkitService now passes it to the OAuth toolkit, which
means the OAuth device-flow endpoints (device authorization, token
polling, refresh) on the kap-server path finally send the full X-Msh-*
device headers instead of none, and the telemetry cloud appender reads
client_version from the same source. A built-in CLI fallback keeps bare
bootstrap() calls in tests working; composition roots must pass their
own identity.

The session export manifest grows an optional desktopVersion field
(payload plumbed through; filled by kap-server in a follow-up).

* feat(agent-core): thread the host identity into the managed auth facades

The v1 managed auth facade constructed its OAuth toolkit without an
identity, so token refreshes from inside the core went out without any
X-Msh-* device headers. createManagedAuthFacade now takes an optional
KimiHostIdentity and every call site supplies one:
CoreProcessService._defaultOAuthTokenResolver forwards the core
process's options.identity (the same source _defaultKimiRequestHeaders
uses), and the DI-held services (oauth / auth summary / model catalog)
read it from a new optional identity field on IEnvironmentService. The
library-level "no identity, no device headers" contract is unchanged.

* feat(kap-server)!: require the host identity and derive request headers from it

ServerStartOptions.hostIdentity is now a required ServerHostIdentity
(KimiHostIdentity + optional prompt display fields), replacing both the
old optional HostIdentityOverrides (renamed to PromptIdentityOverrides,
its productName field now displayName) and the version option (renamed
to serverVersion — it is the engine version reported as server_version,
while the host product version travels in hostIdentity.version).

The server now feeds bootstrap's clientIdentity from hostIdentity and
derives the default outbound headers (User-Agent + X-Msh-*) from it via
createKimiDefaultHeaders, so kap-server-hosted OAuth flows and model /
WebSearch requests carry the real host identity instead of a hardcoded
kimi-code-cli fallback UA. Explicit header seeds still win as an escape
hatch.

Session export manifests record the host product version: kimiCodeVersion
now carries hostIdentity.version (the engine version no longer appears),
and desktop exports (desktop: true) are additionally stamped with a
desktopVersion field. The instance registry keeps its host_version wire
field for compatibility (kimi-inspect reads it); only the in-memory name
changed to serverVersion.

* feat(cli): wire the CLI host identity into the kimi web server

kimi web now passes createKimiCodeHostIdentity(version) as the server's
hostIdentity, so web-UI OAuth flows and the engine's outbound requests
carry the explicit CLI identity (productName + version + platform). The
explicit hostRequestHeadersSeed is dropped — kap-server derives the same
headers from hostIdentity — and buildKimiDefaultHeaders goes away with
its only consumer.

* test(klient): drop clientVersion from the bootstrap contract parity list

* chore: add changesets for the host identity unification

* feat(cli): tag kimi web requests with a (web) User-Agent suffix

kimi web shares the CLI product token and platform, so its outbound
requests were indistinguishable from direct CLI runs upstream. Its host
identity now carries userAgentSuffix 'web', putting web-UI traffic at
kimi-code-cli/<version> (web) while X-Msh-Platform stays kimi_code_cli.

* fix(klient): keep the env() clientVersion wire field after the bootstrap identity switch

The bootstrap snapshot replaced the flat clientVersion scalar with
clientIdentity, which broke klient's env() fan-out (RPCError: method not
found). The wire surface keeps clientVersion — now sourced from
clientIdentity.version — and bootstrapService gains a clientIdentity
read (registered in envContract with an object schema) for consumers
that want the full identity.

* feat(oauth): send the product User-Agent on OAuth requests

The OAuth endpoints used to receive only the X-Msh-* device headers
(undici's default UA otherwise), which left the OAuth host unable to
distinguish runtime surfaces — notably kimi web, whose platform matches
the CLI and whose only distinguishing mark is the (web) UA suffix. The
toolkit now feeds the full identity headers (User-Agent + X-Msh-*) into
every device authorization, token polling, and refresh request; the
request-header type widens from DeviceHeaders to OAuthRequestHeaders.

* feat(vscode): report kimi_code_vscode as the extension's platform

The VS Code extension inherited the CLI's hardcoded X-Msh-Platform value;
with platform now an explicit identity field it declares its own, so the
managed endpoints and OAuth host can tell extension traffic apart from
CLI runs.

* refactor(agent-core-v2)!: require the client identity at the composition root

The bootstrap fallback identity fabricated a kimi-code-cli/unknown host
for any caller that forgot to pass one — the same silent-misreport
pattern this series set out to remove, and it made "required" a lie.
BootstrapInput.clientIdentity is now required, so a missing identity
fails at compile time instead of being papered over. Test and example
callers pass a shared fixture (klient examples and test engines get one
each); the node-sdk v2 client asserts its host identity with the oauth
helper. Also folds DeviceHeaders from an interface into a type alias so
it stays assignable to the widened OAuthRequestHeaders record.

* feat(oauth)!: require and validate the platform in device headers

Drops the quiet CLI fallback in createKimiDeviceHeaders (the same
silent-misreport pattern removed from the bootstrap identity): platform
is now a required option, validated with the same required-ASCII rule as
the version — empty or all-non-ASCII values throw instead of emitting a
blank X-Msh-Platform, and header-unsafe characters are stripped rather
than sent raw.

* fix(node-sdk): seed the host request headers on the v2 client path

The interactive v2 engine path (experimental flag) bootstrapped without
a hostRequestHeaders seed, so managed vendor calls went out with the
SDK's default User-Agent (OpenAI/JS) and no X-Msh-* at all — v1 passes
the full identity headers on the same requests. The v2 client now seeds
the headers from its asserted host identity, and a test pins the seed.

* chore: simplify the CLI changeset wording
2026-07-30 13:45:41 +08:00
Kai
691ec4679e
fix: remove the blocking wait from the TaskOutput tool (#2379)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix: remove the blocking wait from the TaskOutput tool

The block/timeout parameters let a model stall the whole turn waiting
for a background task (up to 3600s), even though completion already
arrives via automatic notification. Remove both parameters from the v1
and v2 engines (kept in model-facing parity), simplify retrieval_status
to success/not_ready, and update the tool, Bash, and Agent prompt
wording plus user docs accordingly. Stale callers passing block are
silently treated as a non-blocking snapshot.

* fix: align background-task prompts with the non-blocking TaskOutput

The compaction reminder promised TaskOutput could fetch a task's result
for tasks that are still running, where it now returns not_ready —
reword it to snapshot semantics and point at the completion
notification. Also list AskUserQuestion(background=true) as a task
source in the TaskOutput description.

* test: exercise stale TaskOutput args through the runtime validator

A stale block/timeout argument never reaches the tool: the executor's
preflight validates args against the closed tool schema and rejects
them immediately, so the old test documented silent-tolerance semantics
the runtime never exhibits. Assert the real behavior through
compileToolArgsValidator/validateToolArgs instead, and drop
statement-adjacent comments to match the package's header-only comment
convention.
2026-07-30 01:26:56 +08:00
7Sageer
fa2c5ce18b
feat: support plugin-contributed custom agents (#2365)
* feat: support plugin-contributed custom agents

* fix: await plugin loading before agent catalog

* fix: refresh plugin agents on v1 reload

* test(agent-core-v2): add enabledSystemPrompts to the plugin service stub
2026-07-29 21:59:48 +08:00
7Sageer
1896d1a13a
Revert "fix(kosong): match Kimi's standalone "Unsupported image." rejection (…" (#2368)
This reverts commit dbb69a2678.
2026-07-29 20:56:41 +08:00
7Sageer
02d77b20d9
feat(agent-core-v2): let plugins contribute system prompt instructions via the manifest systemPrompt field (#2314)
* feat(agent-core-v2): let plugins contribute system prompt instructions via the manifest systemPrompt field

* feat(agent-core-v2): add systemPromptPath to load plugin system prompt from a file

* docs: explain plugin system prompt templates

* fix(agent-core-v2): refresh plugin system prompts after changes

* fix(agent-core-v2): freeze restored profile bindings and converge plugin contributions at session scope

- restore no longer re-renders or re-persists prompts: a resumed agent
  keeps its replayed profile binding (prompt and tool set) as persisted
- a new Session-level convergence point reloads plugin skills into the
  session skill catalog before fanning out to every live agent prompt,
  and every catalog-kind plugin mutation awaits the whole pipeline;
  MCP-only toggles carry a distinct change kind and skip it
- live refreshes after a restart re-resolve the bound profile by name
  and rebind the full slice (prompt, disallowed tools, active tools)
  atomically, warning and keeping the persisted state when the profile
  is gone; renders reuse the first-render timestamp and unchanged
  prompts are not re-persisted, so convergence never churns the wire
- cap plugin system-prompt contributions (32 KB per field/file, 64 KB
  aggregate per prompt build) with manifest diagnostics and warnings
- bump the changeset to minor: this is a new user-facing capability

* fix(agent-core-v2): register the new session domain and dedupe the missing-profile warning

- add sessionPluginContribution to the domain-layer registry so
  lint:domain stays green
- emit system-prompt-refresh-profile-missing once per profile name,
  matching the service's other deduped warnings
- document the convergence timeout escape hatch and the klient
  exclusion of enabledSystemPrompts

* fix(agent-core-v2): dedupe the plugin budget warning and surface section read failures

- emit plugin-sections-oversized once per skipped-plugin signature
- let enabledSystemPrompts failures propagate to the refresh catch
  (keeps the current prompt and warns) instead of silently rendering
  and persisting a prompt without plugin instructions
- cover the convergence timeout cut-off with a fake-timers test
- clarify that the first-render timestamp anchors per process

* fix(agent-core-v2): serialize session convergence and restore onDidReload timing

- run at most one convergence per session and bound each change's wait
  by the timeout, so a fan-out emitter never interleaves deliveries
  after a timed-out convergence
- fire onDidReload as soon as the reload commits again, keeping hook
  reloads independent of prompt convergence
- sign the plugin budget warning with an unambiguous key

* docs(agent-core-v2): align convergence wording with the serialized semantics

- the timeout retry promise only holds once stalled work clears
- note the per-session serial delivery cost model on the plugin change
  contract and the dual-queue invariant on the service

* fix(agent-core-v2): keep empty plugin sections byte-neutral in the prompt template

- place ${plugin_sections} on the same template line as
  ${skills_section} so prompts without either block render exactly as
  before this feature
- note on the change contract that waitUntil work must not call back
  into plugin mutations, and spell out the per-session convergence
  order in the user docs

* fix(agent-core-v2): pin a fork's profile so refresh triggers never rebind it

- applyBindingSnapshot left the fork with no pinned profile, which
  routed in-process forks into the post-restart catalog rebind and
  could reset an inherited tool set; forks now inherit the source
  agent's pinned profile object
- pin the first-render timestamp reuse with a ${now}-embedding test
  and document the anchored ${now} semantics
- tighten the plugin docs budget and resume-refresh wording

* fix(agent-core-v2): join in-flight convergence during agent bootstrap

- an agent created while a plugin convergence is in flight now waits
  for it, and a restored agent refreshes once after it, so a plugin
  mutation never straddles an agent's bootstrap
- warn on a non-string systemPrompt field and strip a UTF-8 BOM from
  systemPromptPath files before trimming
- correct the consumption-surface wording (every CLI surface on the
  experimental flag, not just kimi -p), the per-session queueing note,
  and the single-plugin combined budget clause

* fix(agent-core-v2): bound the bootstrap convergence join by the timeout

A permanently wedged convergence kept convergeTail pending forever,
and the unconditional settled() wait in bindBootstrap would have
blocked every later agent creation in that session; the join now
races the shared convergence timeout and continues (a restored agent
still refreshes once, which never touches the tail), and the timeout
constant moves to the contract for reuse

* fix(agent-core-v2): close the convergence race against in-progress restores

- a convergence fan-out could land while an agent's wire log is still
  replaying, dispatching a replay-visible config record whose effect
  the rest of the replay then overwrites; refreshSystemPrompt now
  skips while the wire restore is in progress
- convergence completion is tracked by a generation counter; bootstrap
  compares it (after a bounded join) and refreshes a restored agent
  exactly once when a round completed after its creation began,
  replacing the wasConverging flag that could miss both windows

* fix(agent-core-v2): bound each convergence so a wedged participant cannot stop the pipeline

- the fan-out now races the convergence timeout, so convergeTail always
  settles: a permanently hung refresh delays its round (blocked entries
  drain oldest-first on later changes) instead of killing the session's
  convergence for good
- warn when agent bootstrap stops waiting on a stalled convergence
- diagnose a blank systemPromptPath and pin the plugin-root escape
  guard with traversal, absolute-path, and symlink tests

* fix(agent-core-v2): bound the skill reload, preserve user-tool overlays, roll the prompt clock daily

- the convergence's skill-reload segment now races the same timeout as
  the fan-out, so no segment of the pipeline can wedge a session for
  good; it continues with the previous catalog and retries next change
- a cold rebind that resets the tool set replays session-added user
  tools onto the new base instead of dropping them for the rest of the
  process
- the rendered timestamp re-anchors when the UTC date rolls over, so
  long-lived processes keep a fresh clock while steady-state renders
  stay byte-stable within a day
- the plugin budget warning dedupes per plugin id, and the docs note
  that systemPromptPath content is frozen until the next reload

* feat(agent-core-v2): converge cold plugin changes on resume through a drift-free gate

- restore replays the persisted binding untouched, then bootstrap
  refreshes only when drift-free inputs changed while the session was
  cold: the catalog profile's tool set/denylist, or the plugin-sections
  baseline persisted alongside the prompt on the existing bind/update
  payloads; directory-listing and date drift wait for live triggers,
  so quiet resumes append no replay-visible records
- the rendered timestamp is day-precision (UTC date at 00:00,
  re-anchored on rollover), keeping steady-state renders byte-stable
  across resumes and sessions on the same day
- consolidate both timeout helpers onto a shared raceOutcome, and drop
  the generation counter the gate supersedes
- align the plugin-sections precedence prose with the AGENTS.md
  disclaimer (no self-granted authority, system instructions win on
  conflict)

* fix(agent-core-v2): bound the restored-prompt gate and land the sections baseline

- the gate's plugin-sections read now races the convergence timeout, so
  agent creation never blocks behind an unrelated plugin mutation
- refreshes serialize per agent through a tail, so overlapping triggers
  cannot write prompts out of order
- when plugin sections change but a plugin-free custom prompt does not,
  the new baseline lands as a sections-only update instead of making
  every later resume re-render in vain
- align the system prompt's Date and Time paragraph with the
  day-precision anchored timestamp

* Update plugin system-prompt instructions in changeset

Live sessions pick up plugin changes, while the default TUI and `kimi -p` paths ignore these fields.

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* refactor(agent-core-v2): keep plugin skill reload user-driven

Plugin mutations still converge live agent prompts, but the session
skill catalog goes back to refreshing only on explicit plugin reload,
as before: the prompt feature does not need skill convergence, and the
pre-existing manual-reload semantics stay uniform across all plugin
contributions. Removes the convergence-driven skill reload, the
reloadSource de-privatization, and their tests; restores the
PluginSkillSource onDidReload forwarding and its catalog tests.

* refactor(agent-core-v2): apply plugin system-prompt changes only on explicit reload

Drop the live convergence machinery (the plugin onDidChange barrier,
the sessionPluginContribution fan-out, the restored-prompt drift gate,
and the day-precision render clock) so plugin system-prompt sections
take effect at the same point as every other plugin contribution:
/plugins reload or a new session. The profile now refreshes when the
session skill catalog re-pulls its plugin source on reload, reading
both the skill list and the prompt sections fresh.

* feat(agent-core): let plugins contribute system prompt instructions via the manifest systemPrompt field

* chore(agent-core-v2): remove inline implementation comment

* docs: clarify plugin prompt refresh semantics

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-07-29 20:30:07 +08:00
STAR-QUAKE
dbb69a2678
fix(kosong): match Kimi's standalone "Unsupported image." rejection (#2362)
Some checks are pending
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
isImageFormatError missed the production phrasing "Unsupported image.
Please try another one." — the existing pattern requires a url/format/
type suffix, so the deterministic image rejection never triggered the
media-stripped resend, and the session failed on every later request.

Add a standalone-sentence pattern (punctuation- or end-terminated) to
both the kosong and agent-core-v2 classifiers, keeping the deliberate
boundary that count/size phrasings must not match.

Co-authored-by: fengchenchen <fengchenchen@moonshot.ai>
2026-07-29 18:54:07 +08:00
wenhua020201-arch
f8ec3d1656
docs: fix dead anchor links in en/zh docs (#2348)
* docs: fix dead anchor links in en/zh docs

- #loop_control -> #loop-control (heading slug uses hyphens)
- #secondary_model -> #secondary-model
- env-vars model section anchors: kimi_model -> kimi-model
- provider credential section anchors: configtoml -> config-toml
- /provider management anchors: point at the renamed heading in each locale
- hooks: point the stale config-files#hooks reference at the local Configuration section
- en files: replace two leftover Chinese anchors with their English targets

* docs: add missing .md extension to themes page links

---------

Co-authored-by: qer <wbxl2000@outlook.com>
2026-07-29 15:57:48 +08:00
7Sageer
b850c5f8f5
fix(node-sdk): wire applyPersistedSecondaryModel to agent-core-v2 (#2345)
* test(node-sdk): drop v1-only subagentNames from the resume parity projection

Custom agent files made v1's resumed agent config carry the bound
profile's delegatable subagent roster; v2's resumed agent state has no
equivalent field, so the resume parity cases fail on main. Project the
engine-owned field away instead of pinning it as a resume-data gap.

* fix(node-sdk): wire applyPersistedSecondaryModel to agent-core-v2

On the v2 engine route the /secondary_model command persisted the recipe
but failed to apply it to the current session: the SDK method fell
through to the base class's not_implemented getRpc().

v1 pushes a reloaded config snapshot into the session because its spawn
binding, tool descriptions, and cached startup warning all read that
snapshot. agent-core-v2 resolves the secondary model live against
IConfigService at spawn time and rebuilds the tool description per read,
so the setConfig write already takes effect session-wide. The override
keeps the rest of v1's contract: config reload, the same loud
validations (session lookup, persist-first recipe check, pointed-model
resolution wrapped at [secondary_model].model), and a warning-cache
refresh via a new recheckSecondaryModelWarning on the session warning
service. getSessionWarnings also surfaces the v2 secondary-model warning
next to the AGENTS.md one, matching v1's aggregate.

* fix(agent-core-v2): surface the subagent's bound model on status events

The v2 model slice rides only the bind-time agent.status.updated, which
precedes subagent.spawned and is dropped by clients that key child events
off the spawn, so subagent cards never learned the model — and a
single-step run emits no usage/context slice until it ends, so the model
only appeared at completion. Re-affirm the binding right after the spawn
announcement via a new IAgentProfileService.republishStatus, and fold a
consistent usage/context/model snapshot into every status event at both
v1 edges (kap-server's broadcaster and the in-process SDK session
wiring, resolving the secondary-model derived id to a readable display
name.
EOF
)

* Delete .changeset/subagent-card-model.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* style(agent-core-v2): remove inline implementation comments

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-07-29 15:40:42 +08:00
liruifengv
37d9bdc585
docs(changelog): sync 0.30.0 from apps/kimi-code/CHANGELOG.md (#2343) 2026-07-29 12:13:20 +08:00
7Sageer
efac96c8a9
feat(agent-core): custom agent files and secondary model on the v1 engine (#2232)
* feat(agent-core): custom agent files and secondary model on the v1 engine

Migrate the custom agentfile and secondary-model capabilities from
agent-core-v2 to the v1 engine so they work in the TUI and plain
kimi -p sessions:

- discover Markdown agent files from user/project/extra/explicit
  directories with the v2 precedence rules, a merged session profile
  catalog replacing the hardcoded builtin profile lookups, SYSTEM.md
  main prompt override, and ${base_prompt} backed by the effective
  default
- --agent/--agent-file now work in print mode on the default engine;
  CreateSessionOptions gains agentProfile/agentFiles
- [secondary_model] config + KIMI_SECONDARY_MODEL/EFFORT bind newly
  spawned subagents to a cheaper model behind the secondary-model
  experiment flag, with primary/secondary model params on Agent and
  AgentSwarm and upfront session warnings
- full disallowedTools deny semantics (exact names + mcp__ globs)
  evaluated by the tool manager and persisted in the agent wire

* fix(cli): guard optional agentFiles in the prompt runner

runPrompt is also driven programmatically (headless goal flow) with
options that never pass through the CLI parser defaults, so agentFiles
can be undefined; mirror the addDirs optional-chaining pattern. Also
extend the SDK experimental-feature assertion with the secondary-model
flag.

* fix(agent-core): preserve custom agent bindings on v1

* fix(agent-core): narrow secondary model error hints

* fix(agent-core): persist custom agent profile bindings

* Delete .changeset/sdk-agent-profile-options.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* Update v1-custom-agent-files.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* Update v1-secondary-model.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* Update v1-custom-agent-files.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* fix(agent-core): keep SYSTEM.md a prompt-only overlay for delegation

* docs: update agent file and secondary model availability wording

* fix(cli): reject --agent-file combined with session resume

The resume path only forwards the agent file's name for the bound-profile
assertion; the file's content is never re-applied (the session keeps its
creation-time catalog snapshot). Previously the combination was silently
accepted, so an edited file (or a same-named one) appeared to apply but did
not. Reject it at option validation and document the constraint.

* refactor(agent-core): share prompt-section prose and note v2 twins in agentfile headers

The Windows notes, additional-dirs and skills prose blocks existed twice:
inline in the builtin default template (system.md) and as constants in the
agent-file renderer (from-file.ts). Extract them to profile/prompt-sections.ts
as the single source: system.md renders them through injected KIMI_* template
variables and from-file.ts imports the same constants. Rendered prompts are
byte-identical for all four builtin profiles across macOS/Windows and
skills/dirs on/off; a new test pins system.md to the shared constants.

Also mark each profile/agentfile file with the path of its agent-core-v2
counterpart so format/semantics changes land in both engines.

* feat(cli): add /secondary_model command for the subagent model

Mirror /model: a picker with a thinking-effort step that persists [secondary_model] and live-applies to the current session via a new Session.setSecondaryModel RPC (node-sdk wrapper included), so newly spawned subagents bind the new model right away. The /model picker now hides the synthesized __secondary__ derived entry; docs and the update-config builtin skill mention the section.

* feat(tui): show the bound model in subagent run stats

Subagents report their model alias via agent.status.updated after spawn; resolve it to a display name and surface it in tool-call subagent stats and agent-group rows.

* fix(agent-core): validate agent profile before session persistence

* fix(agent-core): refresh subagent tools after model switch

* fix(agent-core): show subagent model preferences

* fix(agent-core): preserve secondary model recipe on live apply

* fix(agent-core): make secondary model apply explicit

* fix(tui): refresh secondary model display state

* chore: merge secondary model changesets into one

* Add /secondary_model command for subagent configuration

Show each subagent's model in the subagent card header and agent-group rows. Requires the secondary-model experiment (KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL=1); run /secondary_model to pick a model and thinking effort, applied to the current session immediately.

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* fix(agent-core): align explicit agent file precedence

* fix(agent-core): let disallowedTools deny select_tools

* chore(cli): drop engine mention from --agent/--agent-file help text

* feat(cli): support --agent/--agent-file in the interactive TUI

Bind the selected agent profile to the startup session when launching
the TUI with --agent/--agent-file, including the session created after
an OAuth login at startup. Sessions created later in the process (/new)
keep the default profile.

Make both flags creation-only in every mode: combining them with
--session/--continue is now rejected in print mode too, since resume
restores the bound agent from the session automatically.

* fix(agent-core): persist new secondary-model selections under env overrides

stripSecondaryModelConfig restored secondary_model.model/default_effort
from raw whenever KIMI_SECONDARY_MODEL/KIMI_SECONDARY_EFFORT was set, so
a /secondary_model pick made under the env vars was silently discarded
on write. Restore from raw only when the value being written still
equals the env value (an overlay round-trip), mirroring the pointer
check in stripEnvModelConfig; a genuinely different selection now
reaches config.toml.

* fix(cli): report the effective secondary model when env overrides the pick

/secondary_model toasted the picked alias even when
KIMI_SECONDARY_MODEL/KIMI_SECONDARY_EFFORT made the session bind a
different model. Read the effective binding back from the reloaded
config (as /model does from session status) and warn with the
env-overridden values instead.

* feat(tui): show the bound model name in the AgentSwarm panel header

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-07-29 12:06:26 +08:00
github-actions[bot]
16c7189bd5
ci: release packages (#2244)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-29 11:35:59 +08:00
liruifengv
973e2a008c
chore: prune non-user-facing changesets before release (#2335)
* chore: prune non-user-facing changesets before release

- Drop changesets for changes users cannot perceive: internal bash
  parser groundwork, host-identity server start option, and the
  structured managed-usage refactor
- Drop the duplicate agent-core-v2 repeat-breaker entry and remap the
  v1 fix to @moonshot-ai/kimi-code with user-facing wording
- Remove the untouched @moonshot-ai/kimi-code-sdk entry from the
  quota-exhausted fail-fast changeset
- Downgrade the upload size-cap removal to patch and drop the
  implementation detail from its wording

* chore: simplify verbose changeset entries

* chore: shorten changeset entries to single statements
2026-07-29 11:27:45 +08:00
Yufeng He
67dd03149f
feat(tui): customizable footer status line via status_line config (#2255)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(tui): customizable footer status line via status_line config

The bottom status bar was a fixed layout. Add a [status_line] section
to tui.toml covering the two established models:

- items: codex-style composition. Pick and order the built-in slots
  (mode, goal, model, tasks, cwd, git, tips); unset keeps today's
  layout, unknown ids are skipped with a warning, and an empty list
  blanks line 1.
- command: claude-code-style custom line. The footer runs the command
  with a JSON snapshot on stdin (model, cwd, git branch, permission
  and plan mode, context usage, session id, version) and renders the
  first stdout line. Runs are throttled to one per second and capped
  at 300ms; nonzero exit, empty output, or a timeout falls back to the
  built-in layout.

Line 2 (context readout) stays built-in in every mode. Resolve #2116.

* feat(tui): apply status_line on /reload-tui and document it

The reload command pushes reloaded tui.toml fields into AppState; the
new statusLine field joins that list so edits go live without a
restart. The config files reference (EN/ZH) documents the new section.

* fix(tui): round-trip active status_line on save and harden the runner

Codex review on #2255 caught a real one: saveTuiConfig rewrites the
whole tui.toml, so changing any other preference dropped an active
[status_line] section. Render it live when set (items and command),
commented-out guide when unset.

Also: spawn the command through ComSpec/cmd.exe on Windows instead of
assuming sh.exe, and take the whole process tree down on timeout
(process-group kill on POSIX, taskkill /T on Windows) so a script that
spawned children cannot leak them.

* fix(tui): address status_line review: runner lifecycle, capture cap, tips slot

- recreate the command runner when a reload swaps status_line.command;
  the old runner kept executing the previous script until restart
- schedule a trailing refresh instead of dropping updates that arrive
  inside the throttle window, so the last state change always lands
- stop accumulating stdout once the first line is complete (and cap a
  missing-newline stream at 64KB); only the first line is ever rendered
- honor the configured position of the tips slot in items instead of
  always pinning tips to the far right
- route unknown status_line.items warnings through the TUI status area
  on reload instead of raw stderr, which could corrupt the display

Changeset text tightened per maintainer note.

---------

Co-authored-by: Kai <me@kaiyi.cool>
2026-07-28 22:37:52 +08:00
Haozhe
ceaa96942b
feat(kap-server): add global message search with literal and live-session modes (#2321)
* feat(kap-server): add the /api/v1/search global message search endpoint

Cross-session full-text search over user messages, assistant text and
session titles, backed by a minidb index under <home>/search-index with a
single-writer lock election and read-only WAL catch-up for other
processes. Hits carry transcript anchors (turn ordinal and step id) so
clients can jump straight to the matching turn or step.

* feat(kimi-inspect): add a search view with chat-timeline navigation

The left rail gains a Search view over the global search endpoint, with
role and sort filters and cursor pagination. Clicking a hit switches to
the chat view and navigates to its session, agent, turn and step — the
channel pages the turn into the loaded window, scrolls it into view and
flashes the target briefly.

* chore(kimi-code): start the dev server without built web assets

The repo's dev server scripts (dev:server, dev:kap-server,
dev:kap-server:multi, dev:server:restart) now set KIMI_CODE_DEV_SERVER=1.
When it is set and dist-web/index.html is missing, kimi web starts the
API server without the bundled web UI instead of failing at startup,
so backend dev no longer requires a kimi-web build.

* feat(kap-server): add literal substring search and a live session route

- minidb: text indexes accept an injectable tokenizer/queryTokenizer,
  and a hashed 2/3-gram tokenizer (NFKC + lowercase, code-point
  windows) backs substring search; tokenizer names persist in
  db.textindexes.json with backward-compatible defaults
- /api/v1/search gains mode: 'literal' — n-gram candidates confirmed
  against the original text (zero false positives), with an
  'candidate_cap' incomplete flag when the candidate set truncates
- container.session_id queries against a session live in this process
  scan the in-memory transcript store instead of the index (both
  modes); the response's source: live|index field names the serving
  route and rides in the page-token fingerprint
- kimi-inspect: exact-match toggle and source badge in the search
  view, plus an in-chat session search bar with jump-to-hit navigation

* test(kimi-inspect): avoid stringifying BodyInit in search api tests
2026-07-28 21:47:50 +08:00
Haozhe
f79fde2b90
feat(node-sdk): migrate the SDK method surface to agent-core-v2 (#2262)
* feat(node-sdk): add agent-core-v2 backed SDKRpcClientV2 harness

- add SDKRpcClientV2 wiring the v2 engine (DI x Scope) in-process via the
  klient memory transport, with getExperimentalFeatures migrated to
  klient.global.flags.list() and unmigrated methods failing fast
- export createKimiHarnessV2 / SDKRpcClientV2 from the SDK index
- wire the experimental v2 gate into the CLI interactive shell (run-shell)
- extend build-dts to bundle agent-core-v2 and klient declarations

* feat(node-sdk): migrate listWorkspaceSkills to agent-core-v2

- add engineAccessor escape hatch exposing the in-process engine's app-scope
  service accessor for SDK methods the klient facade does not cover yet
- implement listWorkspaceSkills via ISkillDiscovery plus the v2 user/project
  root helpers and BUILTIN_SKILLS (plugin skills and skillDirs still gaps)
- add a v1-v2 parity test pinning identical return values per migrated
  method, with understood gaps listed explicitly in KNOWN_DIFFS

* feat(node-sdk): migrate the SDK method surface to agent-core-v2

- implement the remaining SDKRpcClientBase methods on SDKRpcClientV2,
  routed through the klient facade where covered, the engineAccessor
  escape hatch where the engine has a service, or SDK-side rebuilds on
  v2 primitives where only primitives exist (config shape mapping,
  global mcp.json store, MCP OAuth flows, importContext, session
  warnings, print background policy)
- translate the v2 event stream into the v1 Event union and bridge
  approval/question/user_tool interactions per live session
- rebuild resume replay by folding the v2 wire.jsonl through the v1
  agent restore pipeline, so resumed sessions render history again
- keep deleteSession as not_implemented; the v2 engine has no delete
  capability
- extend the v1-v2 parity suite to every migrated method, pinning
  understood engine differences in KNOWN_DIFFS
- add the dev:cli:v2 root script to launch the TUI on the v2 engine

* fix(node-sdk): await the v2 undo and compaction-cancel agent calls

* test(cli): spread the real oauth module in the telemetry test mock

* feat(node-sdk): forward v2 engine telemetry to the host telemetry client

* feat(cli): gate the v2 TUI route behind a dedicated KIMI_CODE_TUI_V2 switch

* fix(node-sdk): honor skillDirs on the agent-core-v2 SDK route

The v2 SDK client accepted KimiHarnessOptions.skillDirs (the CLI's
--skills-dir) but never seeded it into the engine, so explicit skill
dirs were silently dropped on the v2 TUI route and the Skill tool
could not find skills from them. Seed skillCatalogRuntimeOptions at
bootstrap and let listWorkspaceSkills resolve the explicit dirs as
the user source, matching the engine's session skill catalog.

* refactor(cli): gate the v2 TUI route behind the master experimental flag again

Drop the dedicated KIMI_CODE_TUI_V2 switch: the TUI v2 harness route is
gated by KIMI_CODE_EXPERIMENTAL_FLAG, the same master switch as the
kimi -p v2 route. The gate tests are kept with updated assertions, and
dev:cli:v2 sets the master flag again.
2026-07-28 20:29:45 +08:00
7Sageer
d88b3775c9
fix(agent-core-v2): count validation-rejected tool calls toward the repeat breaker (#2317)
* fix(agent-core-v2): count validation-rejected tool calls toward the repeat breaker

* style(agent-core-v2): remove inline implementation comments
2026-07-28 19:32:46 +08:00
STAR-QUAKE
de0ba9d065
fix(agent-core): count validation-rejected tool calls toward the repeat breaker (#2313)
* fix(agent-core): count validation-rejected tool calls toward the repeat breaker

Args-rejected calls returned before prepareToolExecution, so the breaker
never counted them and the model could re-issue the same invalid call
until maxSteps. Register them in finalizeToolResult so reminders fire at
3/5/8 and the turn force-stops at 12.

* fix(agent-core): key parse-failed repeats on raw argument text

Malformed JSON arguments normalize to {} on parse failure, which keyed
every malformed-but-different attempt identically and could force-stop a
turn whose calls were evolving rather than identical. Register skipped
calls on the raw arguments text when parsing failed.

---------

Co-authored-by: fengchenchen <fengchenchen@moonshot.ai>
2026-07-28 18:39:26 +08:00
Haozhe
d03a4886fd
feat(server): remove the 50 MiB upload size cap and stream uploads to disk (#2312)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
- add writeStream/putStream to the storage and blob store interfaces so
  large values are written incrementally (tmp + fsync + rename) instead of
  being buffered in memory
- FileService.save now streams the request body straight to the blob store
  and only counts bytes for FileMeta.size
- drop the multipart fileSize limit and the file.too_large error from the
  /files upload path (41301 stays in the wire protocol for session export)
2026-07-28 17:01:45 +08:00
liruifengv
b0f43aea28
feat(oauth): return structured managed usage rows (#2300)
* feat(oauth): return structured managed usage rows

Stop formatting plan-usage labels and reset hints into English strings
at the oauth layer. The parser still absorbs backend field drift, but
now emits a stable structured row (name / window{duration,unit} / used
/ limit / resetAt) that kap-server passes through and clients localize
themselves:

- window: normalized from duration/timeUnit (minute/hour/day/week),
  whole-hour minute windows fold to hours, unnamed summaries are the
  weekly limit
- resetAt: absolute ISO timestamp; relative reset_in/ttl seconds are
  converted at parse time
- TUI /usage panel formats labels and reset hints locally

* refactor(oauth): parse managed usage strictly to the current payload shape

Drop the defensive drift tolerance (alternate reset-time spellings,
reset_in/ttl seconds, remaining-derived used, title/scope names,
top-level duration/timeUnit, fuzzy time-unit matching) and parse only
what the platform actually sends: numeric strings, resetTime, nested
detail/window records, TIME_UNIT_* enums.

* fix(node-sdk): update managed usage smoke example and facade tests for structured rows
2026-07-28 15:03:25 +08:00
Petrichor
cdbd33c13c
fix(kosong): fail fast on quota-exhausted 429 instead of retrying (#1857)
* fix(kosong): fail fast on quota-exhausted 429 instead of retrying

A 429 caused by an exhausted account quota or insufficient balance
(Moonshot error.type "exceeded_current_quota_error", OpenAI
"insufficient_quota") can never succeed on retry, yet it was classified
as APIProviderRateLimitError and silently retried for the whole budget
(10 attempts, ~3 minutes of backoff) with no UI feedback — the session
appeared frozen on every request.

Introduce APIProviderQuotaExhaustedError, minted in
normalizeAPIStatusError from the structured body error.type/error.code
forwarded by convertOpenAIError, with billing-anchored message patterns
as a fallback for gateways that flatten the body to text. The new class
is excluded from isRetryableGenerateError (fail fast, even when a
retry-after header is present) and from isProviderRateLimitError (no
swarm requeue/suspend). toKimiErrorPayload and translateProviderError
map it to provider.api_error (retryable: false) instead of
provider.rate_limit, and classifyApiError reports it as
quota_exhausted in telemetry. agent-core-v2 mirrors the same fix.

Transient rate-limit 429s keep the existing retry, backoff, and
Retry-After behavior (verified end-to-end against a mock provider:
quota body fails after attempt 1/10; rate-limit body still walks the
full 10-attempt ladder).

Behavior changes to note: quota-failed swarm subagents now fail
instead of suspending indefinitely as "Rate limited...", and quota
errors cross the wire as provider.api_error rather than
provider.rate_limit.

* fix(kosong): classify quota exhaustion in OpenAI Responses stream errors

Responses response.failed / error SSE events carry no HTTP status and
were minted by errorFromOpenAIResponsesEvent as either a rate-limit
error (rate_limit_exceeded / embedded status_code=429) or a base
ChatProviderError — and the base class falls into the retryable
unclassified-failure fallback, so an insufficient_quota event still
burned the whole retry budget on the openai_responses path. Route the
event code and message through the same quota-exhausted check before
the rate-limit branch, in kosong and the agent-core-v2 mirror. Covers
all three entry paths (error events, response.failed, nested gateway
frames) since they share the single converter.

* style(agent-core-v2): drop inline comments per AGENTS.md header-only rule

agent-core-v2 comments live solely in the top-of-file block, never
beside functions or statements; the kosong twins keep the full
rationale.

* refactor(kosong,agent-core-v2): move quota-429 checks to vendor hook

Per review on #1857: the knowledge of how a backend signals quota
exhaustion is vendor-specific and must not run for every
OpenAI-compatible provider from the shared conversion layer.

- Add a convertError hook: ProtocolTrait.convertError in agent-core-v2
  (single-value, last-declarer-wins, bound by composeOpenAIChatHooks /
  composeAnthropicHooks / traitConvertError) and an equivalent optional
  hook parameter on convertOpenAIError / convertAnthropicError. Bases
  consult it with the raw failure (SDK error on HTTP paths, raw event on
  the Responses in-stream path) after the abort guard, before their own
  rules.
- Declare Moonshot's quota signals (exceeded_current_quota_error,
  billing wordings) on the Kimi side: kimiOpenAITrait and
  kimiAnthropicTrait in v2, the KimiChatProvider and KimiFiles catch
  sites in kosong, all through the new classifyKimiQuotaError.
- Drop the options parameter from normalizeAPIStatusError and the
  shared quota code/pattern tables: the contract layer keeps only the
  vendor-neutral APIProviderQuotaExhaustedError type and its retry /
  rate-limit / wire-mapping semantics.
- The OpenAI bases keep recognizing only OpenAI's own documented
  insufficient_quota code (HTTP and Responses stream events) as
  protocol knowledge of that wire.

Behavior: kimi and openai provider types classify exactly as before;
an unregistered vendor speaking Moonshot billing wordings through a
plain openai transport now stays a retryable rate limit by design.

* fix(kosong,agent-core,agent-core-v2): wire kimi quota hook fully

Follow-up to the second review round on #1857, all four findings:

- Kimi-over-Anthropic (legacy engine): AnthropicOptions gains the same
  optional convertError hook as the OpenAI bases, threaded through
  AnthropicStreamedMessage and every catch site, and the provider
  manager's anthropic route now passes classifyKimiQuotaError for
  provider type kimi — a quota-exhausted 429 over this transport
  previously still burned the retry budget. classifyKimiQuotaError now
  also walks error -> .error -> .error.error for the code/type, since
  the Anthropic SDK keeps the full body on .error instead of hoisting.
- v2 telemetry: ApiErrorKind gains 'quota_exhausted' and
  classifyApiError checks APIProviderQuotaExhaustedError before the
  generic 429 branch, matching the legacy engine's reporting.
- Hook contract: converted ChatProviderErrors now pass through before
  the vendor hook is consulted in convertOpenAIError /
  convertAnthropicError (both engines), so the hook sees each raw
  failure exactly once even when a stream-minted error crosses an
  outer catch; tests assert the single consult.
- protocolTrait: the convertError member doc shrinks to the concise
  style and the consult contract moves into the file header's
  composition rules.

* test(kosong,agent-core,agent-core-v2): lock quota hook assembly paths

Third review round on #1857:

- Fix the v2 anthropic base header and AnthropicHooks doc still claiming
  withThinking is the only hook.
- Drop the two remaining non-header JSDoc blocks in protocolTrait.ts per
  the AGENTS.md header-only rule; the consult contract already lives in
  the file header.
- Update the ProtocolTrait contract test to the seventeen-hook shape
  (convertError included) and cover the traitConvertError binding.
- Add real-assembly regression probes: the v2 registry composes a
  (kimi, anthropic) provider whose mocked SDK client throws a Moonshot
  quota 429 and generate rejects with the non-retryable
  APIProviderQuotaExhaustedError (a plain anthropic composition keeps
  the same 429 retryable); the legacy ProviderManager routing test
  asserts convertError is classifyKimiQuotaError on the kimi-anthropic
  route and absent for plain anthropic; the legacy provider threads
  options.convertError to its generate catch.

* test(kosong,agent-core-v2): cover KimiFiles quota 429 and drop stale docs

Fourth review round on #1857:

- Drop the AnthropicHooks member JSDoc (its content already lives in the
  anthropic.ts and anthropicHooks.ts file headers) and fix the anthropic
  contrib header still calling the hook set single-hook.
- Add the missing KimiFiles regression in both engines: a mocked files
  client rejecting with a Moonshot quota 429 makes uploadVideo reject
  with the non-retryable APIProviderQuotaExhaustedError, locking the
  classifyKimiQuotaError argument at the upload catch sites.
2026-07-28 14:35:12 +08:00
Haozhe
e556088458
feat(tree-sitter-bash): add a pure-TypeScript bash parser and an agent-core-v2 bashParser service (#2016)
* feat(tree-sitter-bash): scaffold pure-TypeScript bash parser package

Add packages/tree-sitter-bash with the SyntaxNode tree model (UTF-16
code-unit offsets, tree-sitter-bash named-node type names), a parse
budget (deadline + node cap) with an aborted ParseResult variant, and
a placeholder parse() to be replaced by the real lexer/parser.

* feat(tree-sitter-bash): add lexer and core recursive-descent parser

Cover the permission-analysis grammar subset: lists, pipelines,
commands, words, quotes, expansions, command/process substitution,
subshells, the full redirect operator set, heredocs and comments,
with tree-sitter-bash named-node type names. Long scan loops check
the parse deadline via budget.progress() so large literals cannot
exhaust the node cap; parse depth is bounded on all recursion
chains.

* feat(tree-sitter-bash): support the full bash grammar

Add compound commands (if/while/until/for/c-style-for/select/case/
function/compound/do_group), test commands with reference-exact
extglob/regex right-hand-side rules, a Pratt expression engine for
arithmetic expansion shared across arith/c-for/test modes, arrays
and subscripts, declaration/unset commands, ansi-c/translated
strings and brace expressions. Reserved words are recognized only
in statement position. Case-aware balanced scanning keeps command
substitutions intact around case items, expression leftovers are
kept as ERROR nodes instead of dropped, and recursion depth is
bounded per chain (substitution 150, parse 500, lexer scan 1024).

* test(tree-sitter-bash): add differential fixtures, corpus, fuzz and perf suites

Turn the ad-hoc wasm comparison work into permanent infrastructure:
a differential helper pinning reference-equivalent and
known-difference samples against the real tree-sitter-bash wasm
(478 match / 79 known-diff fixtures plus the official v0.25.0
corpus), a three-way consistency check between fixtures, the
known-difference registry and the README, deterministic seeded
fuzz with tree-integrity assertions, and performance smoke tests.
Converges 15 further divergence groups found by systematic probing
and documents the rest; the full suite is 853 tests in ~4s.

* feat(agent-core-v2): add App-scope bashParser service

Wrap the pure @moonshot-ai/tree-sitter-bash package as
IBashParserService (L1, no dependencies): parse(source, options)
returns a wire-safe BashParseResult whose nodes drop the cyclic
parent link so trees can cross the RPC boundary. Budget exhaustion
surfaces as { ok: false, reason: 'aborted' }, malformed input as
hasError — never a throw.

* chore: add changeset for the bash parser service

* chore: update pnpmDeps hash after adding tree-sitter-bash package

* fix(agent-core-v2): register bashParser service with ScopeActivation

The registration was written against the removed InstantiationType API
(#/_base/di/extensions); switch to ScopeActivation.OnDemand from
#/_base/di/scope so the package typechecks and the service registers.

* feat(kimi-inspect): add Bash Parser view

Add a fourth icon-rail tab that exercises the App-scope bash parser
service over the debug RPC surface: a source textarea with a parse
budget (timeoutMs / maxNodes), a dropdown of curated examples adapted
from the tree-sitter-bash differential fixtures, and an expandable
syntax tree with per-node type, UTF-16 range and leaf text, plus
hasError / aborted / node-count badges.

* fix(agent-core-v2): snapshot bash syntax trees iteratively

A long left-associative chain (e.g. an arithmetic expression with a
few thousand operands) parses into a tree thousands of levels deep
while still within budget; the recursive DTO conversion then overflowed
the JS call stack and made parse throw RangeError, breaking the
never-throws contract. Convert the tree with an explicit stack, the
same approach as the parser's own materialize.

* feat(kimi-inspect): add a deep-arithmetic example to the Bash Parser view

A thousand-operand left-associative chain fills the textarea with a
thousand-level binary_expression tree — the shape that once overflowed
the DTO conversion. Deeper chains still parse in-process but cannot
cross the JSON RPC transport (V8 call-stack limit in serialization),
so the example stays within the wire limit.

* chore: update pnpmDeps hash for the rebased lockfile

The rebase onto main merged pnpm-lock.yaml, invalidating the recorded
fetchPnpmDeps hash; use the hash CI computed for the merged lockfile.
2026-07-28 14:29:08 +08:00
qer
425cfdf53f
chore(web): upgrade markstream-vue to 1.0.7 (#2294)
* chore(web): upgrade markstream-vue to 1.0.7

Fix garbled code-block line numbers reported on 0.29.2: the async
code-block loading fallback rendered unstyled (proportional font,
code overlapping the line-number gutter) and with an over-estimated
reserved height that clipped leading lines. markstream-vue 1.0.6 adds
self-contained fallback styles and 1.0.7 fixes the height estimate.

* chore(nix): update pnpmDeps hash for markstream-vue 1.0.7

* chore(web): scope lockfile update to the markstream-vue graph

Regenerate pnpm-lock.yaml with a plain install so only markstream-vue
and its transitive deps move (markdown-it-ts, stream-markdown-parser,
etc.); unrelated toolchains (e.g. lightningcss in kimi-inspect) stay
pinned as before. Addresses review feedback.

* chore(nix): update pnpmDeps hash after lockfile scoping

* chore(changeset): shorten release note to one user-facing sentence
2026-07-28 13:25:38 +08:00
Haozhe
7e30add445
feat(kap-server): add global fs:mkdir endpoint (#2281)
* feat(kap-server): add global fs:mkdir endpoint

Add POST /api/v1/fs:mkdir to create a directory on the host filesystem
by absolute path, backing the folder picker's "new folder" action.
Implemented directly on node:fs/promises.mkdir in the transport layer
for now, non-recursive by design, with wire errors mapped to the
existing fs.* codes (40001/40409/40411/40919).

* test(kap-server): update api surface snapshot for fs:mkdir

* test(kap-server): stop export tests from holding server.close() open

The export download tests reused pooled undici keep-alive connections,
so afterEach's server.close() could wait out fastify's 72s default
keepAliveTimeout and die on the 10s hook timeout (flaky on CI). Send
connection: close on the streamed export requests, matching the
fs:content tests.
2026-07-28 10:50:39 +08:00
7Sageer
77618e38c3
feat(kap-server): wire cloud telemetry for engine events (#2230)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(kap-server): wire cloud telemetry for engine events

The v2 engine registers a full telemetry event catalog, but kap-server
never attached an appender, so events from web-hosted sessions were
dropped to the null appender. Add an opt-in `telemetry` start option
that attaches a CloudAppender (app_name kimi-code-cli, ui_mode web,
matching the v1 `kimi web` host conventions), still gated by the
config `telemetry` toggle, with periodic flush and a bounded flush on
close. The option defaults off so tests never post to the real
endpoint; the CLI's `kimi web` host enables it.

* fix(kap-server): honor telemetry disable environment

* fix(agent-core-v2): isolate session telemetry context

* fix(kap-server): seed telemetry client version

* fix(cli): share telemetry shutdown deadline

* fix(telemetry): make shutdown ownership durable

* fix(kap-server): keep telemetry shutdown best-effort
2026-07-27 17:18:16 +08:00
qer
29783e471a
feat(cli): add plugin quota and update notices (#2147)
* feat(cli): add plugin quota and update notices

- Show "Note: This plugin consumes your quota." after installing
  quota-consuming official plugins (currently Kimi Datasource).
- Show a one-time update notice after invoking an outdated plugin (a
  plugin MCP tool call or a /<plugin>:<command> turn); the last
  notified version is persisted so each new marketplace version
  reminds once.
- Skip the third-party trust prompt for loopback sources that mirror
  the official plugin CDN path, so the dev plugin marketplace no
  longer prompts when installing official plugins.

* feat(cli): report plugin update notices at turn end

Buffer plugin MCP tool usage during the turn and report it together
with plugin command usage when the turn's output has fully ended,
instead of firing the check mid-turn at tool result time. Cancelled
turns no longer trigger the notice.

* fix(cli): refresh plugin MCP map on miss and serialize notice writes

Address review findings on the plugin update notifier:

- The memoized MCP server-to-plugin map is reused across /reload,
  /new, and session switches, so plugins installed or enabled later in
  the same app run never resolved. Refresh the map once on a lookup
  miss, and never pin an empty map when there is no session.
- Concurrent notices (a turn that used two outdated plugins) raced on
  the read-modify-write cycle of the notice state file and could drop
  each other's entries. Serialize checks through a promise queue so
  each notified version is persisted exactly once.

* fix(cli): gate plugin notices on official provenance and survive tool-name truncation

Address review findings:

- The quota note and the update notice keyed on the plugin id alone,
  so a local/GitHub fork reusing a billed plugin's manifest id was
  treated as the official build. Both now require official provenance
  (a zip install from the official CDN plugin path or its loopback dev
  mirror) via a shared isOfficialPluginInstall check.
- Resolving plugin MCP tools by splitting on the '__' separator broke
  for qualified names core truncates to 64 chars, which can cut the
  separator. Match known server names by longest prefix with a name
  boundary instead, which survives truncation as long as the server
  part itself is intact.

* revert(cli): drop the dev marketplace trust relaxation

The loopback carve-out let any local service bypass the third-party
trust prompt by serving a zip under the official path shape, which
does not prove official provenance (review P1). Revert to the single
rule — only https://code.kimi.com/kimi-code/plugins/official/* is a
trusted official source — and restore the stock dev marketplace
server. Installing official plugins from the dev marketplace shows
the trust prompt again.

* fix(cli): restrict update notices to the official catalog and settle tests

- Skip the update check when the loaded marketplace is not the default
  official catalog, so a custom KIMI_CODE_PLUGIN_MARKETPLACE_URL can
  no longer produce a notice that claims to come from the Official
  Marketplace.
- Return a never-rejecting promise from the notifier entry points so
  tests await the serialized queue directly instead of relying on
  zero-delay timers for ordering.
2026-07-27 17:00:42 +08:00
Haozhe
a77ee03829
feat(agent-core-v2): add hostIdentity domain for host-overridable system prompt identity (#2144)
* feat(agent-core-v2): add hostIdentity domain for host-overridable system prompt identity

- add app-scope hostIdentity domain (L3) with productName / replyStyleGuide
  overrides and hostIdentitySeed for composition roots
- render ${product_name} and ${reply_style_guide} in the base system prompt,
  falling back to CLI defaults when the host provides no override
- seed the variables from AgentProfileService via IHostIdentity
- expose hostIdentity on kap-server's ServerStartOptions

* chore: add changeset for host identity system prompt

* fix(agent-core-v2): adapt hostIdentity registration to ScopeActivation

The DI refactor on main removed _base/di/extensions (InstantiationType);
register with ScopeActivation.OnScopeCreated instead, and regenerate the
state manifest for the new SystemPromptContext fields.

---------

Co-authored-by: liruifengv <liruifeng1024@gmail.com>
2026-07-27 16:55:41 +08:00
7Sageer
086769bfad
chore: drop the changelog entry for the experimental secondary-model API (#2243)
The secondary-model feature is still experimental (gated by
KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL and ignored by the interactive
TUI), so the config API support merged in #2228 should not produce a
user-facing changelog entry yet.
2026-07-27 16:47:43 +08:00
7Sageer
3b017821cf
feat(kap-server): accept secondary_model in the config API (#2228)
* feat(kap-server): accept secondary_model in the config API

POST /api/v1/config now accepts secondary_model, persisted to the
[secondary_model] config section via the generic per-domain dispatch.
GET /config also hides the synthesized __secondary__ derived entry
from the models view, matching the GET /models listing.

* Update config-api-secondary-model.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-07-27 16:45:18 +08:00
liruifengv
a9af42e698
docs(changelog): sync 0.29.2 from apps/kimi-code/CHANGELOG.md (#2236) 2026-07-27 15:04:24 +08:00
2637 changed files with 152878 additions and 92758 deletions

View file

@ -14,7 +14,7 @@ description: Use when developing in packages/agent-core-v2 (the DI × Scope agen
```text
Orient → Design → Implement → Test → Verify
│ │ │ │ │
│ │ │ │ └─ lint:domain · typecheck · test · dep graph · red lines
│ │ │ │ └─ lint:imports · typecheck · test · dep graph · red lines
│ │ │ └─ test.md
│ │ └─ implement.md (+ errors.md · flags.md · permission.md)
│ └─ design.md
@ -46,7 +46,7 @@ End-to-end procedures that span the stages. Reach for these before reading the s
- Topic: [Permission](permission.md) — risk-only chain-of-responsibility kernel, harness constraints and product reviews as domain `onBeforeExecuteTool` veto listeners (`veto` / `allow` / `pass` / cold `waitUntil` factories), shared `toolApproval` round-trip, policy registry + composer, `modes`/`agentTypes` metadata, `resolveExecution`/`accesses`.
- Topic: [Telemetry](telemetry.md) — emitting events via `ITelemetryService`, context propagation, and appender destinations (`ConsoleAppender` / `CloudAppender`).
- [Stage 4 — Test](test.md): resolve the system under test by interface, pick `TestInstantiationService` vs `createScopedTestHost`, shared stubs, service groups, teardown.
- [Stage 5 — Verify & submit](verify.md): `lint:domain`, `typecheck`, `test`, and the pre-submit checklist.
- [Stage 5 — Verify & submit](verify.md): `lint:imports`, `typecheck`, `test`, and the pre-submit checklist.
## How to use this skill
@ -67,4 +67,4 @@ Invariants that hold across every stage. Each is expanded in the stage file note
9. Throw coded errors; register codes centrally; branch on `code` across the wire, never `instanceof`. (errors.md)
10. Gate unreleased behavior behind a flag contributed via `registerFlagDefinition` and resolved through `IFlagService.enabled(id)`; no ad-hoc env toggles. (flags.md)
11. Tests resolve the SUT by interface; shared stubs live under `test/`, never `src/`. (test.md)
12. Config is the preference registry: only preferences that are persistable, schema'd, and user/operator-facing go in `IConfigService`. Domain-specific config (including env-only operational toggles) goes through `registerSection` + `envOverlay`. Facts → `IBootstrapService` (kept domain-agnostic — never add cron/flags/model state); session state → Session scope; constants → code. Business domains never call `IBootstrapService.getEnv()` directly. (config.md)
12. Config is the preference registry: only preferences that are persistable, schema'd, and user/operator-facing go in `IConfigService`. Domain-specific config (including env-only operational toggles) goes through `registerConfigSection` + `envOverlay`. Facts → `IBootstrapService`, and host invocation arguments (CLI flags, host identity headers, prompt identity) → `BootstrapInput.args` / `IBootstrapService.args` — never new per-domain runtime-options services; domain runtime state (cron/flags/model) never goes onto `IBootstrapService`; session state → Session scope; constants → code. Business domains never call `IBootstrapService.getEnv()` directly. (config.md)

View file

@ -14,7 +14,7 @@ v1 is a **VSCode-style singleton container**: services self-register with `regis
|---|---|---|
| Registration | `registerSingleton(IX, X, InstantiationType.Delayed)` | `registerScopedService(LifecycleScope.X, IX, X, ScopeActivation.OnDemand, 'domain')` |
| DI import | `from '../../di'` | `from '#/_base/di/scope'` / `'#/_base/di/instantiation'` / `'#/_base/di/lifecycle'` |
| Lifetime | implicit singleton-per-container | explicit `LifecycleScope` (App/Session/Agent) — see orient.md |
| Lifetime | implicit singleton-per-container | explicit `LifecycleScope` (App/Workspace/Session/Agent) — see orient.md |
| Domain granularity | coarse (`session`, `tool`, `loop`) | fine, split by scope + responsibility |
| Test import | `from '@moonshot-ai/agent-core/di/test'` | `from '#/_base/di/test'` |
| Resolve SUT in tests | `ix.createInstance(Impl)` (common) | `ix.get(IX)` by interface — see test.md |
@ -63,7 +63,7 @@ Worked example — v1 `ISessionService` (one class, ~600 lines) holds:
- this session's metadata → **per-session** unit → v2 `sessionMetaStore` (`ISessionMetaStore`, Session);
- this session's activity / status → **per-session** unit → v2 `sessionActivity`;
- this session's context projection → **per-session** unit → v2 `sessionContext`;
- child-agent lifecycle driven by a session → **per-session** unit → v2 `agentLifecycle`; create/close/archive/fork of the session itself → **global** unit → v2 `sessionLifecycle` (App).
- child-agent lifecycle driven by a session → **per-session** unit → v2 `agentLifecycle`; create/close/archive/fork of the session itself → **per-workspace** unit → v2 `sessionLifecycle` (Workspace, one per live workspace handler).
A v1 class that maps cleanly to one v1 decorator often becomes **three to five** v2 Services. That is expected and correct — do not try to keep the v1 class shape.
@ -143,7 +143,7 @@ Re-wire the dependencies you inventoried in step 1, now across the new v2 Servic
- **Domain direction** — foundational layers must not know upstream ones. A cycle means a v1 relative import is now pointing the wrong way; extract a third Service or invert the notification into an event.
- **Durable facts** — state changes that must be recorded / replayed / projected across agents go on the wire (`wireRecord`), not a direct call alone.
Run `lint:domain` (verify.md) as soon as the dependencies compile — it catches direction violations early.
Run `lint:imports` (verify.md) as soon as the dependencies compile — it catches v1 imports and kosong boundary violations early.
### 7. Port the business logic
@ -157,7 +157,8 @@ import { InstantiationType, registerSingleton } from '../../di';
registerSingleton(IXxxService, XxxService, InstantiationType.Delayed);
// v2
import { LifecycleScope, ScopeActivation, registerScopedService } from '#/_base/di/scope';
import { LifecycleScope } from '#/app/scopes';
import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
registerScopedService(LifecycleScope.Session, IXxxService, XxxService, ScopeActivation.OnDemand, 'xxx');
```
@ -188,7 +189,7 @@ import { KimiError, type ErrorCode } from '#/_base/errors';
Red lines:
- Do not copy a v1 file and "fix imports". Re-split first (steps 26); a straight copy carries v1's implicit-singleton assumptions into v2 and creates the `Map<sessionId, …>`-at-`App` anti-pattern.
- Do not leave v1 relative imports (`from '../x/...'`) in v2 — use the `#/...` alias and respect the domain layers.
- Do not leave v1 relative imports (`from '../x/...'`) in v2 — use the `#/...` alias.
- Do not preserve a v1 behavior just because it exists; if the split reveals it was a workaround for the missing scope tree, drop it.
### 8. Port the tests
@ -218,7 +219,7 @@ const svc = ix.get(IXxxService);
Before submitting a port:
- [ ] Every piece of v1 state landed in a v2 Service whose scope matches its identity (no `Map<sessionId, …>` at `App`).
- [ ] Each v1 dependency now points in the right scope and domain direction; `lint:domain` passes.
- [ ] Each v1 dependency now points in the right scope direction; `lint:imports` passes.
- [ ] Registrations use `registerScopedService` with an explicit scope and domain name; no `registerSingleton` remains.
- [ ] Imports use the `#/...` alias; no v1 relative (`../../di`, `../../errors`) imports remain.
- [ ] Errors are co-located coded errors; flags go through `IFlagService`.

View file

@ -57,7 +57,7 @@ Keep the recommendation to the commit's footprint. If it keeps growing, that is
### 6. Verify
Point at the checks that cover the fix, per [verify.md](verify.md): `lint:domain`, `typecheck`, and the relevant `test`. Note the expected outcome rather than asserting you ran it if you did not.
Point at the checks that cover the fix, per [verify.md](verify.md): `lint:imports`, `typecheck`, and the relevant `test`. Note the expected outcome rather than asserting you ran it if you did not.
## Output shape

View file

@ -2,7 +2,7 @@
How the `config` domain works and how a domain owns its configuration section. Covers the section-registry model, the App vs Session split, the TOML on-disk format, and the recipe for adding or migrating a config section.
The `config` domain is a thin registry + loader: it does **not** know the shape of any individual section. Each domain owns the schema (and, where needed, the TOML transform) for the config it consumes, registers the section into `IConfigRegistry`, and reads it through `IConfigService`. There is no whole-config object passed around.
The `config` domain is a thin registry + loader: it does **not** know the shape of any individual section. Each domain owns the schema (and, where needed, the TOML transform) for the config it consumes, contributes the section (statically at module load via `registerConfigSection`, or at runtime as a `ConfigSectionContribution` collection record), and reads it through `IConfigService`. There is no whole-config object passed around.
## What belongs in Config
@ -33,7 +33,7 @@ A value belongs in Config **iff** it satisfies all of:
If it fails any rule, it is not Config:
- **Fact** (CI, platform, proxy, `HOME`) → a structured fact on
`IBootstrapService` (the L1 startup snapshot), not Config.
`IBootstrapService` (the startup snapshot), not Config.
- **Derived convention** (`configPath`, `logsDir`) → `IBootstrapService` / code.
- **Session runtime state** (active model, plan mode) → a Session-scoped
service in the owning domain (e.g. `IProfileService`), not `config`.
@ -42,9 +42,16 @@ If it fails any rule, it is not Config:
**`IBootstrapService` is domain-agnostic.** It holds only generic facts shared by
all domains — the env bag, resolved paths, and host facts (`platform`, `arch`,
`cwd`, `osHomeDir`, `isCI`, …). It must **never** hold state tied to a specific
upper domain (no `cron`, no `flags`, no feature-specific fields): that couples
the foundational layer to an upstream one.
`cwd`, `osHomeDir`, `isCI`, …) — plus the host's process-level invocation
arguments in `args` (explicit `agentFiles` / `skillDirs`, `requestHeaders`,
prompt identity). `args` mirrors VS Code's `NativeParsedArgs` on the
environment service: the host states them once via `BootstrapInput.args` at
the composition root, and downstream services read them from
`IBootstrapService.args` instead of through per-domain runtime-options
services (do not add new `IXxxRuntimeOptions` services or seed functions for
host parameters). What must **never** land on `IBootstrapService` is state
tied to a specific upper domain (no `cron`, no `flags`, no feature-specific
fields): that couples the foundational layer to an upstream one.
Any value that belongs to a specific domain — including env-only operational
toggles (`KIMI_CRON_*`, `KIMI_CODE_EXPERIMENTAL_*`), model parameters, or feature
@ -86,13 +93,15 @@ pass `ConfigTarget.Memory` for a per-run override that is never written to disk.
## Layout
- `src/config/config.ts``IConfigRegistry` / `IConfigService` tokens, `ConfigSection`, `ConfigEffectiveOverlay`, event types.
- `src/config/configService.ts``ConfigRegistry` + `ConfigService` impl; self-registers at App scope.
- `src/config/toml.ts` — generic snake_case ↔ camelCase machinery plus the registry-aware `transformTomlData` / `applySectionToToml` entry points. Per-domain normalization lives in the section owner's `configSection.ts` (registered as `fromToml` / `toToml`); this module stays free of any other domain's semantics.
- `src/profile/thinking.ts` (owner domain, not `config`) — the `resolveThinkingEffort` helper; uses the authoritative `ThinkingConfig` from `configSection.ts`.
- `src/config/configPure.ts``isPlainObject`, `deepMerge`, `omitUndefined`, `describeUnknownError`.
- `src/app/config/config.ts``IConfigRegistry` / `IConfigService` tokens, `ConfigSection`, `ConfigEffectiveOverlay`, event types.
- `src/app/config/configService.ts``ConfigRegistry` + `ConfigService` impl; self-registers at App scope. The registry is also the fold of the `ConfigSectionContribution` collection: it drains the module-level contributions at construction, then refolds incrementally (`added``registerSection`, `removed``unregisterSection`).
- `src/app/config/configSectionContributions.ts` — the `ConfigSectionContribution` collection token (the runtime channel: a unit contributes with `this.provide(ConfigSectionContribution, …)`) plus the module-level `registerConfigSection` collector (the static channel, import = register).
- `src/app/config/configOverlayContributions.ts` — the module-level `registerConfigOverlay` collector for `ConfigEffectiveOverlay`s (drained at construction like the sections).
- `src/app/config/toml.ts` — generic snake_case ↔ camelCase machinery plus the registry-aware `transformTomlData` / `applySectionToToml` entry points. Per-domain normalization lives in the section owner's `configSection.ts` (registered as `fromToml` / `toToml`); this module stays free of any other domain's semantics.
- `src/kosong/model/thinking.ts` (owner domain, not `config`) — the `resolveThinkingEffort` helper and the authoritative `ThinkingConfig` type (the `thinking` section itself registers from `src/app/kosongConfig/configSection.ts`).
- `src/app/config/configPure.ts``isPlainObject`, `deepMerge`, `omitUndefined`, `describeUnknownError`.
A domain that owns a section keeps the schema in its own `configSection.ts` (e.g. `src/flag/flag.ts` for `experimental`, `src/loop/configSection.ts` for `loopControl`). Exception: kosong-owned sections (`providers`, `models`, `thinking`) — kosong is a pure, persistence-free abstraction layer that defines only the types (`src/kosong/{provider,model}`); the section constants, the zod schemas (re-derived from those types and compile-time pinned via `AssertExact<Equal<z.infer<typeof Schema>, Type>>`, see `_base/utils/typeEquality.ts`), the registrations, env bindings, and TOML transforms all live in the persistence wrapper `src/app/kosongConfig/configSection.ts`. (`modelCatalog` and `secondaryModel` have no kosong-side type at all — their sections are fully self-contained in `app/kosongConfig`, types derived from the schemas.) A cross-section env overlay (e.g. the `KIMI_MODEL_*` synthesis) lives in the wrapper too (`src/app/kosongConfig/envOverlay.ts`; the `[secondary_model]` derived-entry synthesis in `secondaryModelOverlay.ts`) and is registered via `IConfigRegistry.registerEffectiveOverlay`. The two-way sync between config sections and kosong's in-memory registries is owned by `IKosongConfigService` (`src/app/kosongConfig/kosongConfigService.ts`).
A domain that owns a section keeps the schema in its own `configSection.ts` (e.g. `src/app/flag/flag.ts` for `experimental`, `src/agent/loop/configSection.ts` for `loopControl`). Exception: kosong-owned sections (`providers`, `models`, `thinking`) — kosong is a pure, persistence-free abstraction layer that defines only the types (`src/kosong/{provider,model}`); the section constants, the zod schemas (re-derived from those types and compile-time pinned via `AssertExact<Equal<z.infer<typeof Schema>, Type>>`, see `_base/utils/typeEquality.ts`), the registrations, env bindings, and TOML transforms all live in the persistence wrapper `src/app/kosongConfig/configSection.ts`. (`modelCatalog` and `secondaryModel` have no kosong-side type at all — their sections are fully self-contained in `app/kosongConfig`, types derived from the schemas.) A cross-section env overlay (e.g. the `KIMI_MODEL_*` synthesis) lives in the wrapper too (`src/app/kosongConfig/envOverlay.ts`; the `[secondary_model]` derived-entry synthesis in `secondaryModelOverlay.ts`) and is registered via module-level `registerConfigOverlay`. The two-way sync between config sections and kosong's in-memory registries is owned by `IKosongConfigService` (`src/app/kosongConfig/kosongConfigService.ts`).
## Scope
@ -110,10 +119,15 @@ A config section is identified by a camelCase domain key (`'providers'`, `'think
- `fromToml?: ConfigFromToml` — read-path transform (snake_case file value → in-memory shape). Defaults to a plain key-casing pass; owners register one when the on-disk shape needs custom normalization (record key preservation, nested object conversion, array entries, key renames, reshapes).
- `toToml?: ConfigToToml` — write-path transform (in-memory value → snake_case file value). Defaults to a plain camelCase→snake_case key mapping.
Two contribution channels:
- **Static (import = register)** — the owning domain calls `registerConfigSection(domain, schema, options)` at the top level of its `configSection.ts`; `ConfigRegistry` drains the collected contributions when it is constructed. Every in-repo section uses this channel.
- **Runtime (collection record)** — a unit contributes `this.provide(ConfigSectionContribution, { domain, schema, options })` (e.g. a feature assembled through `IFeatureManager`); the `ConfigRegistry` fold registers the section when the record lands and unregisters it when the record is withdrawn (provider disposed). User TOML values survive a withdrawal — they just stop being validated and effective.
Ownership rules:
- **One owner per section.** `registerSection` throws if a domain is registered twice.
- **The domain that consumes a config owns its schema.** This is what keeps `config` (L2) from importing higher domains: `config` must not import `externalHooks` / `permissionRules` / `provider` / `kosong` / etc. for a section's schema. If a schema needs a domain's types, the schema lives in that domain.
- **One owner per section.** `registerSection` throws if a domain is registered twice — the static channel fails fast when `ConfigRegistry` drains it; a conflicting runtime record is reported through `onUnexpectedError` and the first registration wins (the fold is an event path and never throws).
- **The domain that consumes a config owns its schema.** This is what keeps `config` from depending on its consumers: `config` must not import `externalHooks` / `permissionRules` / `provider` / `kosong` / etc. for a section's schema. If a schema needs a domain's types, the schema lives in that domain.
- **Demand-driven.** Do not register sections for config that no domain reads yet; a section appears (with its schema in the owning domain) only when a consumer appears.
## Env bindings
@ -124,7 +138,7 @@ Declare the bindings with `envBindings(schema, { … })` — the field names are
type-checked against the schema (no magic strings), and nested schemas recurse:
```ts
registerSection('thinking', ThinkingConfigSchema, {
registerConfigSection('thinking', ThinkingConfigSchema, {
env: envBindings(ThinkingConfigSchema, {
effort: 'KIMI_MODEL_THINKING_EFFORT',
}),
@ -132,7 +146,7 @@ registerSection('thinking', ThinkingConfigSchema, {
// nested / record section — outer key is a runtime constant, inner fields are
// checked against the value schema:
registerSection('providers', ProvidersSectionSchema, {
registerConfigSection('providers', ProvidersSectionSchema, {
env: envBindings(ProvidersSectionSchema, {
[ENV_MODEL_PROVIDER_KEY]: envBindings(ProviderConfigSchema, {
apiKey: 'KIMI_MODEL_API_KEY',
@ -145,15 +159,18 @@ registerSection('providers', ProvidersSectionSchema, {
```
Each field is an `EnvBinding` — a string (env var name) or
`{ env, parse?, default? }`. IConfig resolves every field by
`{ env, deprecatedEnv?, parse?, default? }`. IConfig resolves every field by
`env > config.toml > default`, sets it on the effective value, and validates the
section. Empty nested entries (no field resolved) are omitted, so a synthetic
entry like `__kimi_env__` only appears when at least one of its env vars is set.
When `deprecatedEnv` is set and `env` itself is absent or fails `parse`, the
deprecated var still supplies the value and a warning diagnostic is reported —
use it to rename an env var without breaking existing setups.
`stripEnv(value, raw?, getEnv?)` removes env-derived fields before `set`/`replace`
persists, so env overrides never leak into `config.toml`. `raw` is the section's
env-free camelCase base (already `fromToml`-normalized, so legacy key renames
are honored), and `getEnv` reads the live env bag. For fields that are **both
env-free camelCase base (already `fromToml`-normalized), and `getEnv` reads the
live env bag. For fields that are **both
user-persistable and env-overridable**, register
`stripEnv: stripEnvBoundFields(sectionEnvBindings)` (from `#/app/config/config`)
— it derives the guard from the same bindings the read path uses: while a
@ -174,21 +191,27 @@ never write their own env-merge logic.
export const MySectionSchema = z.object({ /* ... */ });
export type MySection = z.infer<typeof MySectionSchema>;
```
2. In the domain's service constructor, inject `IConfigRegistry` and register:
2. Register it at the top level of the same module (import = register):
```ts
constructor(@IConfigRegistry registry: IConfigRegistry) {
registry.registerSection(MY_SECTION, MySectionSchema, { defaultValue: {} });
}
// src/<domain>/configSection.ts
import { registerConfigSection } from '#/app/config/configSectionContributions';
registerConfigSection(MY_SECTION, MySectionSchema, { defaultValue: {} });
```
Pick a service whose scope matches when the config is first needed. Registering from an Agent-scope service is fine — see "Late registration".
3. Read it anywhere via `IConfigService`:
`ConfigRegistry` drains module-level contributions when it is constructed, so the section exists before any consumer resolves `IConfigService` — no owning Service needs to be constructed first. Make sure `src/index.ts` imports the leaf so the top-level call runs.
3. (Runtime variant) a dynamically loaded unit (e.g. one assembled through `IFeatureManager`) contributes the section as a collection record instead:
```ts
this.provide(ConfigSectionContribution, { domain: MY_SECTION, schema: MySectionSchema, options: { defaultValue: {} } });
```
The `ConfigRegistry` fold registers it incrementally and unregisters it when the unit is retracted (user TOML values survive) — see "Late registration".
4. Read it anywhere via `IConfigService`:
```ts
constructor(@IConfigService private readonly config: IConfigService) {}
// ...
const value = this.config.get<MySection>(MY_SECTION);
```
4. React to edits by subscribing `IConfigService.onDidChange` and filtering on `e.domain === MY_SECTION` (see `FlagService`).
5. Write it only through `IConfigService.set(domain, patch)` (merge) or `.replace(domain, value)` (wholesale). Never write `config.toml` directly.
5. React to edits by subscribing `IConfigService.onDidChange` and filtering on `e.domain === MY_SECTION` (see `FlagService`).
6. Write it only through `IConfigService.set(domain, patch)` (merge) or `.replace(domain, value)` (wholesale). Never write `config.toml` directly.
## Reads vs writes
@ -212,11 +235,11 @@ So `configure(...)` never overwrites the local file. Treat `config.toml` as the
## Late registration
`ConfigService` loads in its constructor (first `get(IConfigService)`). Domain services that register sections may be constructed later (especially Agent-scope services). To keep validation and defaults correct:
`ConfigService` loads in its constructor (first `get(IConfigService)`). Static sections are drained before that, but a runtime-contributed section (a `ConfigSectionContribution` record) can register at any later moment. To keep validation and defaults correct:
- `IConfigRegistry` emits `onDidRegisterSection` whenever a section is registered.
- `ConfigService` subscribes and, on registration, re-validates the already-loaded raw value for that domain, applies the default if the raw value is absent, re-runs the env overlay, and fires `onDidChange` if the effective value changed.
- Before a section is registered, `get(domain)` returns the raw (transformed, unvalidated) value; consumers that need validated values should read after the owning service is constructed, or react to `onDidChange`.
- `IConfigRegistry` emits `onDidRegisterSection` whenever a section is registered (and `onDidUnregisterSection` when a runtime record is withdrawn).
- `ConfigService` subscribes and, on registration, re-validates the already-loaded raw value for that domain, applies the default if the raw value is absent, re-runs the env overlay, and fires `onDidChange` if the effective value changed. On unregistration it devalidates the domain — `get(domain)` falls back to the raw value.
- Before a section is registered, `get(domain)` returns the raw (transformed, unvalidated) value; consumers that need validated values should read after the section lands, or react to `onDidChange`.
This means registration order is never a correctness concern — you do not need an eager bootstrap.
@ -224,7 +247,7 @@ This means registration order is never a correctness concern — you do not need
`config.toml` stores keys in **snake_case**; in-memory values are **camelCase**. `ConfigService` converts both ways by dispatching to each section's registered transform:
- **Read**: `transformTomlData(fileData, registry)` maps each top-level key to a domain and applies that domain's `fromToml` hook (or a plain key-casing pass when none is registered). Owner domains register their own normalization — e.g. provider `oauth`/`env`/`customHeaders`, permission `deny/allow/ask``rules`, `loop_control.max_steps_per_run``maxStepsPerTurn`, `experimental` keys preserved verbatim. When a section registers after the initial load, `ConfigService` re-applies its `fromToml` against the preserved snake_case raw value (see "Late registration"), so registration order is never a correctness concern.
- **Read**: `transformTomlData(fileData, registry)` maps each top-level key to a domain and applies that domain's `fromToml` hook (or a plain key-casing pass when none is registered). Owner domains register their own normalization — e.g. provider `oauth`/`env`/`customHeaders`, permission `deny/allow/ask``rules`, `experimental` keys preserved verbatim. When a section registers after the initial load, `ConfigService` re-applies its `fromToml` against the preserved snake_case raw value (see "Late registration"), so registration order is never a correctness concern.
- **Write**: `applySectionToToml(rawSnake, domain, value, registry)` applies the domain's `toToml` hook (or a plain camelCase→snake_case mapping) into a raw clone of the file, preserving unknown top-level keys and unknown sub-fields (lossless round-trip).
`ConfigService` keeps four views:
@ -234,13 +257,30 @@ This means registration order is never a correctness concern — you do not need
- `validated` — validated `raw`, env-free; the base every live env re-application starts from, so a degraded or removed env value falls back to the file instead of a stale overlay.
- `effective``validated` plus the env overlay, recomputed on load/set; `get()`/`getAll()` re-apply the overlay on a fresh `validated` copy per read rather than caching it.
### Renaming config keys and env vars (deprecations)
Renames are declared once on the section, never hand-rolled in `fromToml`:
```ts
registerSection(MY_SECTION, MySectionSchema, {
deprecations: [{ key: 'old_key', replacement: 'new_key' }], // snake_case, on-disk
env: envBindings(MySectionSchema, {
newKey: { env: 'KIMI_NEW_KEY', deprecatedEnv: 'KIMI_OLD_KEY', parse },
}),
});
```
- A deprecated TOML key is **ignored** (its value no longer applies — the schema only knows the new key) and reports a warning `ConfigDiagnostic` while present; the file is never rewritten, so the warning is the migration guide. Diagnostics are recomputed on every load/reload and surface to clients via `IConfigService.diagnostics()` and `onDidChangeDiagnostics` (kap-server republishes them as the global `event.config.warning` WS event).
- A deprecated env var still **resolves** as a fallback (new var first), with the same warning treatment, and `stripEnvBoundFields` treats it as env-owned for writes.
- See `src/agent/loop/configSection.ts` for a worked example (`max_retries_per_step``max_attempts_per_step`).
### `KIMI_MODEL_*` env overlay
When `KIMI_MODEL_NAME` is set, the `kosongConfig` wrapper's `kimiModelEnvOverlay` (`src/app/kosongConfig/envOverlay.ts`) injects a reserved model alias (`__kimi_env_model__`) into `effective`, points `defaultModel` at it, and merges the request `modelOverrides`; the reserved provider (`__kimi_env__`) comes from the `providers` section env bindings. The overlay is registered via `IConfigRegistry.registerEffectiveOverlay` and applied **only to `effective`**, never to `rawSnake`, so it is never persisted. Its `strip` (plus the providers section `stripEnv`) is the final guard so a caller that read `effective` (with the overlay) cannot write the reserved entries or the shell API key back to disk. `config` itself only runs registered overlays — it does not know the `KIMI_MODEL_*` semantics.
When `KIMI_MODEL_NAME` is set, the `kosongConfig` wrapper's `kimiModelEnvOverlay` (`src/app/kosongConfig/envOverlay.ts`) injects a reserved model alias (`__kimi_env_model__`) into `effective`, points `defaultModel` at it, and merges the request `modelOverrides`; the reserved provider (`__kimi_env__`) comes from the `providers` section env bindings. The overlay is registered via module-level `registerConfigOverlay` and applied **only to `effective`**, never to `rawSnake`, so it is never persisted. Its `strip` (plus the providers section `stripEnv`) is the final guard so a caller that read `effective` (with the overlay) cannot write the reserved entries or the shell API key back to disk. `config` itself only runs registered overlays — it does not know the `KIMI_MODEL_*` semantics.
## Owner-owned sections
`config` holds no monolithic config schema and no whole-config object. Every section is owned by the domain that consumes it: the schema (and any `fromToml` / `toToml` normalization and `stripEnv`) lives in that domain's `configSection.ts`, and the domain registers it via `IConfigRegistry.registerSection`. Cross-section env behavior (e.g. `KIMI_MODEL_*`) lives in an owner-registered `ConfigEffectiveOverlay`. To add a section, follow "Add a config section" above in the owning domain — never add schema or normalization to `config` itself.
`config` holds no monolithic config schema and no whole-config object. Every section is owned by the domain that consumes it: the schema (and any `fromToml` / `toToml` normalization and `stripEnv`) lives in that domain's `configSection.ts`, and the domain contributes it via module-level `registerConfigSection` (or a runtime `ConfigSectionContribution` record). Cross-section env behavior (e.g. `KIMI_MODEL_*`) lives in an owner-registered `ConfigEffectiveOverlay` (module-level `registerConfigOverlay`). To add a section, follow "Add a config section" above in the owning domain — never add schema or normalization to `config` itself.
## Ownership map (generated)
@ -251,22 +291,22 @@ The authoritative, always-current list of registered sections — rendered in th
`config` must not import from any of these owner domains; that is the whole reason the schemas, TOML normalization, and env overlays live with their owners.
## Layering & scope
## Scope & dependencies
- `config` is **L2**. Domains that own sections import `config` (for `IConfigRegistry` / `IConfigService`) and must be at L2 or higher; lower layers need an entry in `ALLOWED_EXCEPTIONS` (e.g. `kosong>config`, `kosong>provider`).
- Cross-domain type sharing for a config type may need an exception too (e.g. `plugin>mcp` for `McpServerConfig`). Prefer importing the type from the owning domain over re-declaring it.
- `config` is a low-level capability: domains that own sections import `config` (for `IConfigRegistry` / `IConfigService`), never the reverse — section schemas live in the owning domain.
- Cross-domain type sharing for a config type: prefer importing the type from the owning domain over re-declaring it (e.g. `plugin` imports `McpServerConfig` from the MCP config schema).
- `IConfigRegistry` / `IConfigService` are **App**. Agent scope services may inject App services via ancestor lookup.
- `config` never imports a higher domain and holds no section schemas of its own; if a section needs a type from another domain, that schema lives in that domain.
## Red lines (this topic)
- One owner per section; `registerSection` throws on duplicate domains.
- `config` (L2) never imports a higher domain — keep section schemas in the owning domain.
- One owner per section: a duplicate static registration throws when `ConfigRegistry` drains it; a conflicting runtime record is logged (`onUnexpectedError`) and the first registration wins.
- `config` never imports the domains that consume it — keep section schemas in the owning domain.
- Config is the **preference registry**: register only values that are preferences, persistable, schema'd, and user/operator-facing. Facts → `IBootstrapService`; session state → Session scope; constants → code.
- Business domains read `config.get(...)` or structured `IBootstrapService` facts; never call `IBootstrapService.getEnv()` directly — only `config` reads the raw env bag to build overlays.
- Keep `IBootstrapService` domain-agnostic: never add state tied to a specific upper domain (cron, flags, model params, …). Domain-specific config goes through `registerSection` + `envBindings`, read via `config.get(...)`.
- Keep `IBootstrapService` domain-agnostic: host invocation arguments (CLI flags, host identity headers, prompt identity) go into `BootstrapInput.args` / `IBootstrapService.args` — never into new per-domain runtime-options services; domain runtime state (cron, flags, model params, …) never goes onto `IBootstrapService` at all. Domain-specific config goes through `registerConfigSection` + `envBindings`, read via `config.get(...)`.
- Do not pass a whole config bag via options; read each section through `IConfigService`. There is no `KimiConfig` object — config is a registry of owner-owned sections.
- `config.toml` is snake_case on disk, camelCase in memory — never write camelCase keys to disk, and never write to `config.toml` except through `IConfigService.set/replace`.
- Reading config / calling `configure(...)` / switching model at runtime must not rewrite `config.toml`; runtime state lives in memory and the session wireRecord, not the file.
- Never persist env overlays (`__kimi_env__` / `__kimi_env_model__` / shell API key / experimental env); overlays live only in `effective` / `Memory`.
- Registering from an Agent-scope service is fine — the late-registration mechanism keeps validation correct; do not add an eager bootstrap.
- Runtime contribution (a `ConfigSectionContribution` record from a unit at any scope) is fine — the late-registration mechanism keeps validation correct; the static channel needs no eager bootstrap (import = register, drained at `ConfigRegistry` construction).

View file

@ -20,6 +20,7 @@ A Service = a bundle of **state** + a set of **behaviors**, bound to a **lifetim
| Scope | State identity (keyed by) | Lifetime |
|---|---|---|
| `App` | none (single global instance) | the process |
| `Workspace` | `workspaceId` | one workspace handler (materialized once per workspace, never closed — dies with the process) |
| `Session` | `sessionId` | one session |
| `Agent` | `agentId` | one agent |
@ -33,6 +34,7 @@ A Service = a bundle of **state** + a set of **behaviors**, bound to a **lifetim
**Q2. What is the identity of that state?**
- one global instance → **`App`**
- one per workspace (shared by every session of that workspace) → **`Workspace`**
- one per session → **`Session`**
- one per agent → **`Agent`**
- a mix (a global registry *and* per-instance state) → **split it** (see §3).
@ -70,7 +72,7 @@ The standard split is "global registry / factory" + "per-instance":
| Tier | Role | Naming tends to |
|---|---|---|
| `App` | global registry / catalog / factory — knows "all of them" and how to create one | `XxxStore` / `XxxRegistry` / `XxxCatalog` |
| `Session` / `Agent` | one instance — only the state of "this one" | `XxxService` / `ISessionXxx` / `IAgentXxx` |
| `Workspace` / `Session` / `Agent` | one instance — only the state of "this one" | `XxxService` / `IWorkspaceXxx` / `ISessionXxx` / `IAgentXxx` |
Canonical splits in the codebase:
@ -128,6 +130,8 @@ The three mechanisms above are also where a domain accepts new behavior without
| Step into an operation in order / veto | a **hook** (`onWill`/`onDid`, `OrderedHookSlot`) | the owning scope |
| Swap a backend (File ↔ DB ↔ S3) | a **Store / Storage token** at the byte layer (see persistence.md) | `App` (composition root) |
The standard shape of a "registry / catalog the domain queries" row is an L3 contribution point: the target domain owns a `collection<T>` token, contributors call `this.provide(token, record)` from a unit, and a fold service in the target domain injects the `CollectionView` (incremental `onDidChange`; provider death withdraws the record). The four in-repo seams are `ConfigSectionContribution``ConfigRegistry`, `AgentToolContribution``AgentToolActivationService`, `AgentProfileContribution``IAgentProfileRegistry`, and `WireModelContribution``WireService` (file-level pointers: `packages/agent-core-v2/AGENTS.md` §Units and contribution points).
Closed-for-modification means: the domain's own file is not where new scenarios branch. If a new scenario forces an edit here, an extension point is missing or misplaced.
## 5. Dependency direction
@ -145,25 +149,17 @@ Add one anti-rot heuristic to keep the graph from collapsing into a clique:
Once a foundational component knows about an upstream scenario, it can no longer be reused by other scenarios and will almost always create a cycle.
### The natural layers of this repo
### The boundaries of this repo
`agent-core-v2` is stratified into eight dependency layers, **L0L7** (the `Ln` number in file headers — see orient.md for the full table and the representative domains). A domain at layer `L` may import only domains at layer `<= L`; lower layers never reach upward. `lint:domain` enforces this from the `DOMAIN_LAYER` map in `scripts/check-domain-layers.mjs`.
`agent-core-v2` has no mechanical domain-layer numbering — dependency direction is the judgment rule above, applied per domain. What remains enforceable is a small set of specific boundaries (`lint:imports`, `scripts/check-import-boundaries.mjs`):
The tiers, from lowest to highest:
- v2 never imports v1 (`@moonshot-ai/agent-core`).
- The kosong subtree keeps its strict internal order (`contract ← protocol ← provider/model`, purity bans, the `provider/bases` registration boundary).
- **L0 — base infrastructure** (`_base`, errors, wire types).
- **L1 — bridges & low-level capabilities** (logging, telemetry, event bus, environment, storage).
- **L2 — data & cross-cutting capabilities** (records, config, providers, auth, workspace registry).
- **L3 — registries & capabilities** (tools, permissions, flags, skills, plugins).
- **L4 — agent behaviour** (turn, loop, prompt, profile, context, goal, plan, swarm).
- **L5 — async lifecycle** (background, MCP, cron, sub-agent tools).
- **L6 — coordination** (session, agent/session lifecycle, interactions, terminal).
- **L7 — boundary / edge** (`gateway`, `rpc`, approval/question, the `*Legacy` v1 adapters).
Two standing red lines on top of that:
Red lines:
- The **L0/L1 substrate** never imports a higher business layer.
- Business logic never depends on the **L7 edge** layer — business code should not know REST / WebSocket exist.
- The **base substrate** (`_base`, errors, wire types) never depends on any business domain.
- Business logic never depends on the **edge** (`gateway`, `rpc`, the `*Legacy` v1 adapters) — business code should not know REST / WebSocket exist.
- A cycle means knowledge was placed backwards: extract a third, more foundational Service, or invert the "notification" half into an event.
> Capability → orchestrator (e.g. `prompt → turn`) is allowed and present in this repo; the real red line is *inverted reuse* — a foundational / lower Service depending on a specific / upper one.
@ -189,8 +185,9 @@ domain: `<name>` (owning scope: <Scope>)
│ └─ (accessor) <ConsumerDomain> @<Scope><what they use me for>
├─ exposes (interfaces I provide, by scope)
│ ├─ App : <IXxxRegistry><role>
│ ├─ Session : <ISessionXxx><role>
│ └─ Agent : <IAgentXxx><role>
│ ├─ Workspace : <IWorkspaceXxx><role>
│ ├─ Session : <ISessionXxx><role>
│ └─ Agent : <IAgentXxx><role>
└─ depends (what I inject) tag = calling style
└─ <DepDomain> @<Scope> direct/event/hook — <what for>
```
@ -228,42 +225,49 @@ Read it as:
Worked example — `sessionLifecycle`:
```text
domain: `sessionLifecycle` (owning scope: App)
domain: `sessionLifecycle` (owning scope: Workspace)
├─ serves (who uses me)
│ ├─ (inject) — (none yet)
│ ├─ (inject) — (none)
│ └─ (accessor)
│ ├─ sessionLegacy @App(edge) — v1-compatible create/fork/archive/…
│ └─ gateway / rpc @App(edge) — native v2 session lifecycle actions
├─ exposes (interfaces I provide, by scope)
│ ├─ App : ISessionLifecycleService — owns the live session scope tree
│ ├─ Workspace : ISessionLifecycleService — owns this workspace's live session scope tree
│ ├─ Session : — — (per-session state lives in sessionMetadata / agentLifecycle / …)
│ └─ Agent : — — (per-agent state lives in agentLifecycle)
└─ depends (what I inject)
├─ bootstrap @App direct — addresses session storage
├─ hostEnvironment @App direct — gates scope creation on the probe
├─ sessionIndex @App direct — persisted read model for cold resumes
├─ storage @App direct — atomic docs + append logs
├─ workspace @App direct — resolves a session's workspace
└─ event @App direct — broadcasts session-level facts (e.g. archived)
├─ workspaceContext @Workspace seed — handler identity + persistence scope
├─ bootstrap @App direct — addresses session storage
├─ hostEnvironment @App direct — gates scope creation on the probe
├─ sessionIndex @App direct — persisted read model for cold resumes
├─ storage @App direct — atomic docs + append logs
├─ workspaceDirs / workspaceSkillCatalog / workspaceMcp / …
@Workspace direct — the handler's shared resource services
└─ event @App direct — broadcasts session-level facts (e.g. archived)
```
Cross-scope borrow for `sessionLifecycle`:
```text
App scope
SessionLifecycleService ──holds──┐
GatewayService ───────────holds──┼──► IScopeHandle(sessionId)
│ accessor.get(ISessionMetadata) …
│ └── resolve runs inside the Session scope
Session scope (sessionId)
sessionMetadata / agentLifecycle / … ← per-session services live here
WorkspaceLifecycleService ──holds──► IScopeHandle(workspaceId) (one per live handler)
│ accessor.get(ISessionLifecycleService)
│ └── resolve runs inside the Workspace scope
Workspace scope (workspaceId)
SessionLifecycleService ──holds──► IScopeHandle(sessionId)
│ accessor.get(ISessionMetadata) …
│ └── resolve runs inside the Session scope
Session scope (sessionId)
sessionMetadata / agentLifecycle / … ← per-session services live here
```
How the three lenses shaped it:
- **Scope (§2)** → the live registry of session scopes is process-wide, so it is App-scoped; per-session data stays in Session-scoped services, reached through the handle's `accessor`.
- **Scope (§2)** → the live registry of one workspace's session scopes is per-handler, so it is Workspace-scoped; the process-wide handler registry lives in the App-scoped `workspaceLifecycle`; per-session data stays in Session-scoped services, reached through the handle's `accessor`.
- **Dependency direction (§5)**`sessionLifecycle` is consumed by the edge via `accessor` borrows; it never imports the edge. Every downward arrow lands on a peer or a more foundational Service.
- **Extension points (§4)** → new per-session behavior plugs into the Session-scoped services (`sessionMetadata`, `agentLifecycle`, `sessionActivity`); new transports stay at the edge. Neither edits `sessionLifecycle`.

View file

@ -82,7 +82,7 @@ The `session` domain owns only Session-level identity, metadata, lifecycle comma
|---|---|---|
| `sessionId`, `workspaceId`, `sessionDir`, `metaScope` | `sessionContext` | Seeded facts; no IO |
| `SessionMeta` | `sessionMetadata` | Durable atomic document; entity-like |
| Open session scope registry | `sessionLifecycle` | App-scope live handles; not the persisted entity table |
| Open session scope registry | `sessionLifecycle` | Workspace-scope live handles, one registry per workspace handler (the process-wide handler registry is `workspaceLifecycle`); not the persisted entity table |
| Session commands such as `archive()` | `session` | Orchestrates metadata, agent teardown, and events |
| Persisted session list / get / count | `sessionIndex` | Backend-neutral read model |
| Running / idle / awaiting status | `sessionActivity` | Derived from interactions and active turns; owns no state |

View file

@ -6,10 +6,11 @@ The transport (`/api/v2` over HTTP + WS) lives in the **edge** layer (`gateway`/
## 1. The edge model
Three scopes, three URL shapes, one dispatcher:
Four scopes, four URL shapes, one dispatcher:
```text
GET|POST /api/v2/:sa Core
GET|POST /api/v2/workspace/:workspace_id/:sa Workspace
GET|POST /api/v2/session/:session_id/:sa Session
GET|POST /api/v2/session/:session_id/agent/:agent_id/:sa Agent
```
@ -26,9 +27,10 @@ GET|POST /api/v2/session/:session_id/agent/:agent_id/:sa Agent
```ts
// actionMap — the allowlist; hides internal domain names.
const actionMap = {
core: { 'sessions:list': { service: ISessionIndex, method: 'list' }, ... },
session: { 'session:read': { service: ISessionMetadata, method: 'read' }, ... },
agent: { 'profile:getModel': { service: IProfileService, method: 'getModel' }, ... },
core: { 'sessions:list': { service: ISessionIndex, method: 'list' }, ... },
workspace: { 'skills:list': { service: IWorkspaceSkillCatalog, method: 'list' }, ... },
session: { 'session:read': { service: ISessionMetadata, method: 'read' }, ... },
agent: { 'profile:getModel': { service: IProfileService, method: 'getModel' }, ... },
};
```
@ -53,9 +55,9 @@ Read = `GET`, write = `POST`. `sid` = `session_id`, `aid` = `agent_id`.
| resource | action | Service.method | verb |
|---|---|---|---|
| `sessions` | `list` | ISessionIndex.list | GET |
| `sessions` | `listRecent` | ISessionIndex.listRecent | GET |
| `sessions` | `get` | ISessionIndex.get | GET |
| `sessions` | `countActive` | ISessionIndex.countActive | GET |
| `sessions` | `count` | ISessionIndex.count | GET |
| `workspaces` | `list` | IWorkspaceService.list | GET |
| `workspaces` | `get` | IWorkspaceService.get | GET |
| `workspaces` | `createOrTouch` | IWorkspaceService.createOrTouch | POST |
@ -90,7 +92,7 @@ Read = `GET`, write = `POST`. `sid` = `session_id`, `aid` = `agent_id`.
| `questions` | `answer` | IQuestionService.answer | POST |
| `interactions` | `listPending` | IInteractionService.listPending | GET |
| `interactions` | `respond` | IInteractionService.respond | POST |
| `workspace` | `setWorkDir` / `addAdditionalDir` / `removeAdditionalDir` / `resolve` | IWorkspaceContext.* | GET/POST |
| `workspace` | `workDir` / `additionalDirs` / `resolve` | ISessionWorkspaceContext.* | GET |
### Agent (`/api/v2/session/:sid/agent/:aid/:resource:action`)
@ -103,7 +105,7 @@ Read = `GET`, write = `POST`. `sid` = `session_id`, `aid` = `agent_id`.
| `tasks` | `list` / `get` / `readOutput` | IBackgroundService.* | GET |
| `tasks` | `stop` / `detach` | IBackgroundService.* | POST |
| `usage` | `status` | IUsageService.status | GET |
| `context` | `status` | IAgentContextSizeService.get | GET |
| `context` | `status` | IAgentTokenCountingService.get | GET |
| `swarm` | `isActive` | ISwarmService.isActive | GET |
| `swarm` | `enter` / `exit` | ISwarmService.* | POST |
| `permission` | `getMode` | IPermissionModeService.mode | GET |

View file

@ -6,12 +6,12 @@ Gate not-yet-public features behind `IFlagService.enabled(id)`, per the reposito
## Layout
- `src/flag/flagRegistry.ts` — `IFlagRegistry` token + `FlagDefinitionInput` / `FlagId` / `FlagSurface` types + `registerFlagDefinition` / `getContributedFlags` (import-time contribution queue).
- `src/flag/flagRegistryService.ts` — `FlagRegistryService` impl; in-memory catalog seeded from import-time contributions; App scope.
- `src/flag/flag.ts` — `IFlagService` token + resolver types (`ExperimentalFlagMap`, `ExperimentalFlagConfig`, `ExperimentalFlagSource`, `ExperimentalFeatureState`) + `ExperimentalConfigSchema` / `ExperimentalConfig` (zod).
- `src/flag/flagService.ts` — `FlagService` impl + `MASTER_ENV` (`KIMI_CODE_EXPERIMENTAL_FLAG`) + `EXPERIMENTAL_SECTION` (`experimental`); reads definitions from `IFlagRegistry`; self-registers at App scope.
- `src/flag/index.ts` — **removed (no barrel)**; `src/index.ts` imports the `flag` leafs precisely instead (e.g. `import './flag/flagService'`).
- `src/<domain>/flag.ts` — each domain that owns a flag declares it here and calls `registerFlagDefinition` at the module top level (e.g. `src/agent/toolSelect/flag.ts` or `src/agent/faultInjection/flag.ts`). The directory already names the domain, so the file is just `flag.ts`.
- `src/app/flag/flagRegistry.ts` — `IFlagRegistry` token + `FlagDefinitionInput` / `FlagId` / `FlagSurface` types + `registerFlagDefinition` / `getContributedFlags` (import-time contribution queue).
- `src/app/flag/flagRegistryService.ts` — `FlagRegistryService` impl; in-memory catalog seeded from import-time contributions; App scope.
- `src/app/flag/flag.ts` — `IFlagService` token + resolver types (`ExperimentalFlagMap`, `ExperimentalFlagConfig`, `ExperimentalFlagSource`, `ExperimentalFeatureState`) + `EXPERIMENTAL_SECTION` (`experimental`) / `ExperimentalConfigSchema` (zod) + the module-level `registerConfigSection(EXPERIMENTAL_SECTION, …)` call that owns the section.
- `src/app/flag/flagService.ts` — `FlagService` impl + `MASTER_ENV` (`KIMI_CODE_EXPERIMENTAL_FLAG`); reads definitions from `IFlagRegistry` and overrides from `IConfigService`; self-registers at App scope.
- `src/app/flag/index.ts` — **removed (no barrel)**; `src/index.ts` imports the `flag` leafs precisely instead (e.g. `import './app/flag/flagService'`).
- `src/<domain>/flag.ts` — each domain that owns a flag declares it here and calls `registerFlagDefinition` at the module top level (e.g. `src/agent/toolSelect/flag.ts`). The directory already names the domain, so the file is just `flag.ts`.
## Public surface
@ -33,9 +33,9 @@ Highest wins; env is read live on every call (nothing cached):
## Config integration
- `FlagService` registers the `[experimental]` section into `IConfigRegistry` at construction (`registerSection('experimental', ExperimentalConfigSchema)`) and reads overrides from `IConfigService`.
- The flag domain owns the `[experimental]` section: `src/app/flag/flag.ts` registers it at module load via `registerConfigSection(EXPERIMENTAL_SECTION, ExperimentalConfigSchema, { fromToml, toToml })` (import = register, drained by `ConfigRegistry` at construction); `FlagService` reads overrides from `IConfigService`.
- It subscribes `IConfigService.onDidChange` and refreshes overrides whenever the `experimental` domain changes, so config edits apply live.
- `IConfigRegistry.registerSection` throws if a domain is registered twice — `experimental` is owned exclusively by `FlagService`.
- `ConfigRegistry.registerSection` throws if a domain is registered twice — `experimental` is owned exclusively by the flag domain.
- `setConfigOverrides(overrides)` is an imperative escape hatch for tests and hosts without an `IConfigService`; hosts on `IConfigService` should set the `[experimental]` section instead.
Config shape:
@ -54,7 +54,7 @@ Declare the definition in the owning domain's `flag.ts` and call `registerFlagDe
`src/<domain>/flag.ts`:
```ts
import { type FlagDefinitionInput, registerFlagDefinition } from '#/flag';
import { type FlagDefinitionInput, registerFlagDefinition } from '#/app/flag/flagRegistry';
export const myFeatureFlag: FlagDefinitionInput = {
id: 'my_feature',
@ -94,8 +94,8 @@ if (!this.flags.enabled('my_feature')) return;
## Layering & scope
- Domain `flag` is registered at **L3**. It imports only `config` (L2) downward.
- It cannot live in `_base` (L0): registering/reading the config section requires importing `config`, and L0 must not import L2.
- Domain `flag` imports only `config` downward.
- It cannot live in `_base`: registering/reading the config section requires importing `config`, and `_base` is pure infrastructure that must not know any business domain.
- Scope: `IFlagRegistry` and `IFlagService` are both `App`. Env + config are process-global inputs, so there is no per-session/agent state. Flag definitions are contributed at **import time** (top-level `registerFlagDefinition` calls), so they are queued before any scope is created and drained when `FlagRegistryService` is first instantiated — before `IFlagService` is first resolved.
- Tests build `FlagService` + `FlagRegistryService` directly with a real `ConfigRegistry`/`ConfigService` and an injected env map, then `register` the flags they exercise.
@ -105,4 +105,4 @@ if (!this.flags.enabled('my_feature')) return;
- Contribute each flag from the **owning domain's** `flag.ts` (`src/<domain>/flag.ts`) via a top-level `registerFlagDefinition` call; there is no central catalog to edit. The directory names the domain, so the file is just `flag.ts`.
- `env` must start with `KIMI_CODE_EXPERIMENTAL_`, be unique, and not equal `KIMI_CODE_EXPERIMENTAL_FLAG`; `id` must not be `flag`.
- `FlagId` is `string` (decentralized registration) — do not reintroduce a central `FLAG_DEFINITIONS` array or a derived literal union.
- `flag` lives at L3 and `App` scope — never in `_base`, never per-session.
- `flag` lives at `App` scope — never in `_base`, never per-session.

View file

@ -31,7 +31,8 @@ export const IGreeter: ServiceIdentifier<IGreeter> = createDecorator<IGreeter>('
```ts
// greet/greetService.ts
import { LifecycleScope, registerScopedService, ScopeActivation } from '#/_base/di/scope';
import { LifecycleScope } from '#/app/scopes';
import { registerScopedService, ScopeActivation } from '#/_base/di/scope';
import { IGreeter } from './greet';
export class Greeter implements IGreeter {
@ -127,7 +128,8 @@ export class WSBroadcastService extends Disposable implements IWSBroadcastServic
- Extend `Disposable`, collect any `IDisposable` with `this._register(d)` (event subscriptions, `toDisposable(fn)`, etc.).
- The container calls `dispose()` automatically when the service is torn down; child resources release in turn.
- Disposal order is deterministic (orient.md): child scopes first, then reverse construction order within a scope.
- Disposal order is deterministic (orient.md): child scopes first; within a scope the Ledger (`src/_base/lifecycle/`) tears entries down in strict reverse registration order, serially — `Disposable` / `DisposableStore` delegate to it.
- Extend `Service` (from `#/_base/di/service`) instead when the unit needs capability calls on `this` (`provide` / `effect` / `on` / `get` / `ref`) — e.g. contributing a record to a `collection` token. `Service` extends `Disposable` (so `_register` is unchanged) and adds the two-phase construction protocol: `provide` / `on` / `effect` calls inside the constructor are buffered and flushed by the kernel after `Reflect.construct`; `get` / `ref` throw inside the constructor — dependencies stay constructor parameters. A manually `new`ed `Service` has no capabilities: every capability call throws.
## §5 Scope activation
@ -160,7 +162,7 @@ registerScopedService(
);
```
`ScopeActivation.OnScopeCreated` is the default fourth argument. Scope creation constructs every registration using this mode, after constructing its dependencies. If any constructor fails, scope creation fails. Use it for ordinary services and for constructor side effects that must exist when the scope becomes ready.
`ScopeActivation.OnScopeCreated` is the default fourth argument. Scope creation activates every registration using this mode, after constructing its dependencies. An eager constructor failure no longer fails scope creation: the unit lands in sticky `Failed` — scope creation succeeds, resolving the unit rethrows its error, and an explicit `update()` reloads it (see the bootstrap note below). Use it for ordinary services and for constructor side effects that must exist when the scope becomes ready.
`ScopeActivation.OnDemand` stores the descriptor without constructing the service. The first `get()` constructs and caches the real instance directly; later `get()` calls return that same instance. Use it only when construction should wait until the service is actually requested.
@ -168,6 +170,8 @@ Both modes use the same dependency graph and reject cycles with `CyclicDependenc
The complete registration signature is `registerScopedService(scope, id, ctor, activation = ScopeActivation.OnScopeCreated, domain?)`: activation is the fourth argument and domain is the fifth.
**Bootstrap shares the dynamic provide path.** Scope creation (`Scope.createApp` / `Scope.createChild` / `createScopedChildHandle` in `src/_base/di/scope.ts`) submits the scope kind's entire `registerScopedService` batch as ONE cascade transaction via `provideAll`: every token registers before the activation wave runs, so **registration order never matters**, and untracked transitive `createInstance` resolutions succeed inside the batch. A seed occupying a token (the `extra` tuple in `ScopeOptions`) overrides the static registration for that token. `activateScopeServices` is gone — there is no separate static activation path.
## §6 Using a service inside a plain function (`invokeFunction`)
When you do not want a new class and just need a service once, or when you expose a `ServicesAccessor` to the outside:
@ -198,7 +202,7 @@ class TurnRunner {
const runner = instantiation.createInstance(TurnRunner, 'hello', 1);
```
Static params come first (you pass them), service params follow (the container fills them), then `Reflect.construct` builds the instance. This object is **not** placed in any scope's singleton cache — every call is a fresh instance.
Static params come first (you pass them), service params follow (the container fills them), then `Reflect.construct` builds the instance. This object is **not** placed in any scope's singleton cache — every call is a fresh instance — and it is not tracked as a cascade unit either: `createInstance` products are cascade-exempt leaves that no cascade tears down or rebuilds; their owner disposes them.
> This is why service params must follow static params **for `createInstance`**: the container sorts by the parameter positions recorded via `@IX`. `_serviceBrand` lets the compiler tell the two kinds apart. Scoped services built by `registerScopedService` follow a different convention (`@IX` params first, optional static params after) — see service-authoring.md §constructor-conventions.
@ -234,7 +238,7 @@ Key points:
- `instantiation.createChild(collection)` builds a child container whose parent pointer is the current container — so the child resolves upward to `App` services (the visibility rule).
- Expose the child to the outside by wrapping it in a `ServicesAccessor` via `invokeFunction` (§6).
> Higher-level code usually calls `Scope.createChild(kind, id)` (it does the "filter descriptors + build child" for you). Drop to the manual `ServiceCollection` form only when you need explicit control.
> Higher-level code usually calls `Scope.createChild(kind, id)` (it does the "filter descriptors + build child" for you, then submits the whole batch through `provideAll` as one cascade transaction — see §5). Drop to the manual `ServiceCollection` form only when you need explicit control; to change bindings on an already-created container, prefer `provide` / `unprovide` / `update` over rebuilding a collection. Before the static batch lands, the scope-creation point runs the kernel's `ScopeUnits` fold (`_base/di/scopeUnits.ts` — materializes the recipes contributed to `ScopeUnits(kind)` as per-scope units) and then the `ScopeOptions.assemble` hook — the session domain uses the hook to construct its seed-adapter units (`session/sessionSeed/sessionSeedAdapters.ts`) so their provided tokens exist before the session services activate.
## §9 Cyclic dependencies (forbidden — refactor)
@ -246,7 +250,7 @@ If A needs B while being created and B needs A while being created, the containe
### Why cycles are disallowed
- Scope layering makes normal dependencies a DAG (Agent → Session → App, resolving upward); a cycle is almost always a design smell.
- Scope layering makes normal dependencies a DAG (Agent → Session → Workspace → App, resolving upward); a cycle is almost always a design smell.
- "Making the cycle happen to work" turns construction order into an implicit contract — hard to debug.
v2's stance: **the dependency graph must be acyclic.**
@ -276,6 +280,8 @@ Both `ScopeActivation.OnScopeCreated` and `ScopeActivation.OnDemand` construct t
| `Disposable` / `DisposableStore` / `IDisposable` | §4 | resource management and disposal |
| `Scope` / `LifecycleScope` | §3, §8 | the lifetime tree |
| `ScopeActivation` | §3, §5 | choose scope-created or first-`get()` construction |
| `Service` (`_base/di/service`) | §4 | unit base class — `this.provide/effect/on/get/ref` capabilities, two-phase construction |
| `collection<T>(name)` / `CollectionView<T>` (`_base/di/collection`) | §4 | contribution-point token + the fold's live view (provider death withdraws the record) |
| `SyncDescriptor` | (tests / low-level) | package a constructor + static args into a pending descriptor |
> Legacy export (not used in v2, just recognize it): `refineServiceDecorator` is a VS Code leftover DI helper. v2 src/test has zero references; always use `registerScopedService`.

View file

@ -12,27 +12,31 @@ When writing business code you declare three things; the container handles the r
Classes talk only to interfaces and never care how an implementation is constructed.
## The three `LifecycleScope` tiers
## The four `LifecycleScope` tiers
Lifetimes form a tree, from longest to shortest:
```text
App (0) process-wide, single global instance
└── Session (1) one session
└── Agent (2) one agent
App process-wide, single global instance
└── Workspace one workspace handler (a materialized workspace root)
└── Session one session
└── Agent one agent
```
```ts
// src/app/scopes.ts — the business layer declares the tiers and their order;
// the DI kernel only knows opaque string kinds plus the declared topology.
export enum LifecycleScope {
App = 0,
Session = 1,
Agent = 2,
App = 'app',
Workspace = 'workspace',
Session = 'session',
Agent = 'agent',
}
```
- A larger number = shorter life = closer to a leaf.
- Later in the topology = shorter life = closer to a leaf.
- "Singleton" means **one per scope**: `ILogService` is global once; each `Session` scope has its own `ISessionMetadata`.
- `kind` strictly increases along the parent→child direction.
- `kind` must advance along the declared topology in the parent→child direction.
### Visibility rule
@ -45,35 +49,30 @@ A child scope sees its ancestors; a parent never sees its children. Resolution w
### Disposal order
Deterministic: **child scopes die first; within one scope, instances dispose in reverse construction order** (last constructed, first disposed). Business code declares which tier it lives in and never disposes by hand.
Deterministic: **child scopes die first; within one scope, teardown runs in strict reverse registration order, one entry at a time.** The mechanism is the Ledger (`src/_base/lifecycle/`): ordered effect bookkeeping, dual-track (sync + async disposers), serial reverse-order teardown (never parallel), with the teardown reason (`'scope-close' | 'cascade' | 'unload'`) passed through to every disposer. `Disposable` / `DisposableStore` (`src/_base/di/lifecycle.ts`) delegate to it — "reverse construction order" is a Ledger property, not a container convention. Business code declares which tier it lives in and never disposes by hand.
## The `(Ln)` layer number in headers
## Dynamic DI: units and cascades
The `Ln` in a file-header identity line is the domain's **dependency layer** (L0L7), **not** its `LifecycleScope`. They are easy to confuse because both are small integers, but they answer different questions:
Registration is not the end of the story. Every unit a container tracks — static registrations and runtime `provide`s alike — lives in a small state machine owned by the scope's cascade engine (`src/_base/di/cascadeEngine.ts`, one per scope container, orchestrating tree-wide). Vocabulary you will meet in errors, tests, and the debug surface:
- `LifecycleScope` (App=0 / Session=1 / Agent=2) — **lifetime & visibility** (this stage).
- Dependency layer `Ln` (L0L7) — **who may import whom**: a domain at layer `L` may import only domains at layer `<= L`. Enforced by `lint:domain` from the authoritative `DOMAIN_LAYER` map in `scripts/check-domain-layers.mjs`.
- **Unit states**`Pending → Activating → Active`, plus `Unloading` during teardown and a sticky `Failed`. A construction failure parks the unit in `Failed` with no auto-retry: resolving it rethrows its error; an explicit `update()` reloads it.
- **Waiting area** — a unit whose declared dependencies are missing sits `Pending` and auto-activates when they arrive, including cross-scope wake-up when an ancestor gains the token. An `ondemand` unit counts as available: consumers pull it transitively at materialization.
- **Cascade transaction** — every `provide` / `unprovide` / `update` runs as one tree-wide transaction: contagion set from the persistent dependency graph (instance edges, child→parent across scopes) → abort hook → global reverse-topo teardown → apply the change → waiting-area recheck fixpoint → history ring. Static bootstrap shares this path: scope creation submits the kind's whole registration batch as one `provideAll`, so registration order never matters.
So a Session-scoped service is not "L1" — e.g. `session` is Session-scoped but lives at **L6**. When you write the header, read the number from the layer map, not from the scope.
## Import boundaries
| Layer | Role | Representative domains |
|---|---|---|
| L0 | base infrastructure | `_base`, `errors`, `llmProtocol` |
| L1 | bridges & low-level capabilities | `log`, `telemetry`, `event`, `environment`, `bootstrap`, `storage` |
| L2 | data & cross-cutting capabilities | `records`, `wireRecord`, `config`, `provider`, `auth`, `workspace` |
| L3 | registries & capabilities | `tool`, `toolRegistry`, `permission*`, `flag`, `skill`, `plugin` |
| L4 | agent behaviour | `turn`, `loop`, `prompt`, `profile`, `contextMemory`, `goal`, `plan`, `swarm` |
| L5 | async lifecycle | `background`, `mcp`, `cron`, `agentTool` |
| L6 | coordination | `session`, `agentLifecycle`, `sessionMetadata`, `interaction`, `terminal`, `undo` |
| L7 | boundary / edge | `gateway`, `rpc`, `approval`, `question`, `*Legacy` |
There is no domain-layer numbering — a domain may import any other domain, guided by the dependency-direction judgment in design.md. The only mechanically enforced import boundaries are (`lint:imports`, `scripts/check-import-boundaries.mjs`):
- v2 never imports v1 (`@moonshot-ai/agent-core` or any subpath).
- The kosong subtree (`src/kosong/{contract,protocol,provider,model}`) keeps its strict internal order (`contract ← protocol ← provider/model`), purity bans (no SDKs in `contract`/`protocol`), and the `provider/bases` registration boundary.
## File-header comment convention
`packages/agent-core-v2/AGENTS.md` mandates a header-only comment style:
- **Header only.** Comments live solely in the top-of-file `/** */` block — never beside functions, methods, or statements. The code is the source of truth for *how*; the header states *what the module exposes and the responsibility it owns*.
- **Identity line first.** Start with `` `<domain>` domain (Ln) — <one-line role>. `` Keep an existing `(cross-cutting)` label as-is. Write the role as a responsibility ("drives the turn lifecycle"), not a symbol list.
- **Scope is in the filename.** `session*.ts` = Session, `agent*.ts` = Agent, no prefix = App (see service-authoring.md). State the same scope in the header so the two never drift.
- **Identity line first.** Start with `` `<domain>` domain — <one-line role>. `` Keep an existing `(cross-cutting)` label as-is. Write the role as a responsibility ("drives the turn lifecycle"), not a symbol list.
- **Scope is in the filename.** `workspace*.ts` = Workspace, `session*.ts` = Session, `agent*.ts` = Agent, no prefix = App (see service-authoring.md). State the same scope in the header so the two never drift.
- **Interface files** (`<name>.ts`) state the public contract + scope: which `IXxx` they define and what it is for.
- **Impl files** (`<name>Service.ts`) add collaborators + scope: list every imported cross-domain collaborator as a role ("persists records through `records`"); read scope from `registerScopedService(LifecycleScope.X, …)`.
- **Contribution files** (`<targetDomain>.ts` / `<what>.contrib.ts`) state what they register into the target domain (e.g. "registers the `log` config section into `config`").
@ -83,7 +82,7 @@ Impl file example (`sessionMetadataService.ts`):
```ts
/**
* `sessionMetadata` domain (L6)`ISessionMetadata` implementation.
* `sessionMetadata` domain — `ISessionMetadata` implementation.
*
* Persists the session metadata document (`state.json`) through the `storage`
* access-pattern store (`IAtomicDocumentStore`), rooted at the `metaScope`

View file

@ -190,7 +190,7 @@ export interface IAppendLogStore {
## Platform primitives are deployment-coupled, not core abstractions
`hostFs` (local filesystem) is a **platform primitive** used only by local backends (`FileStorageService`, `LocalFileSystemBackend`, `LocalSkillCatalog`, `HostFolderBrowser`). It is **not** a core abstraction and must not appear in L2/L3 dependency graphs. A server deployment swaps those backends for DB / S3 implementations and never registers `hostFs`.
`hostFs` (local filesystem) is a **platform primitive** used only by local backends (`FileStorageService`, `LocalFileSystemBackend`, `LocalSkillCatalog`, `HostFolderBrowser`). It is **not** a core abstraction and must not appear in business-domain dependency graphs. A server deployment swaps those backends for DB / S3 implementations and never registers `hostFs`.
## Red lines (this topic)
@ -199,6 +199,6 @@ export interface IAppendLogStore {
- Name generic Stores by access pattern (`IAppendLogStore` / `IAtomicDocumentStore` / `IBlobStore`), never by business concept (`IRecordStore` / `IConfigStore`).
- Business-specific Stores (unique query semantics) are named after the domain (`ISessionIndex`).
- `IFileSystemStorageService` is the filesystem byte-layer interface; non-filesystem backends implement the **Store** interfaces directly. Route backends by binding a different Store implementation at the composition root, not by overloading `scope`.
- `hostFs` is a local-only platform primitive; L2/L3 domains must not import `node:fs` or `hostFs` directly.
- `hostFs` is a local-only platform primitive; business domains must not import `node:fs` or `hostFs` directly.
- Only the file-backed bootstrap (`FileBootstrapService`) and file backends import `pathe`; business domains do not.
- Do not create a pass-through `Store` that only forwards `read/write` — a Store must hide a real access-pattern concern, or it is noise; use `IFileSystemStorageService` directly instead.

View file

@ -69,7 +69,7 @@ Resolve the v2 Service that will back the route. Two cases:
**Case A — the v2 native Service already matches the v1 contract.** Use it directly. Most data/command Services (`IConfigService`, `IWorkspaceService`, `IApprovalService`, `IQuestionService`, `IFileStore`, …) land here: the route is a thin adapter that resolves the scope, calls the method, and wraps the result. Examples: `routes/config.ts`, `routes/messages.ts`, `routes/questions.ts`, `routes/files.ts`.
**Case B — the v1 contract needs behavior that would distort the v2 domain.** Introduce a **`*LegacyService`** — an L7 edge adapter that implements the v1 contract **on top of** the v2 native Service, leaving the native Service untouched. The v2 native Service keeps serving `/api/v2`; the LegacyService serves `/api/v1`.
**Case B — the v1 contract needs behavior that would distort the v2 domain.** Introduce a **`*LegacyService`** — an edge adapter that implements the v1 contract **on top of** the v2 native Service, leaving the native Service untouched. The v2 native Service keeps serving `/api/v2`; the LegacyService serves `/api/v1`.
Reach for a LegacyService when **any** hold:
@ -109,7 +109,8 @@ export const IAgentPromptService: ServiceIdentifier<IAgentPromptService> =
```ts
// promptService.ts — impl delegates to the native v2 Service
import { LifecycleScope, ScopeActivation, registerScopedService } from '#/_base/di/scope';
import { LifecycleScope } from '#/app/scopes';
import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
constructor(@IAgentPromptService private readonly prompt: IAgentPromptService /*, ... */) {}
// submit() builds v2-native input, calls the native Service, projects the result
@ -127,7 +128,7 @@ registerScopedService(
Conventions:
- **Name** the domain `<domain>Legacy` and the interface with the scope prefix, `I<Scope><Domain>LegacyService` (e.g. `prompt` / `IAgentPromptService`), per service-authoring.md.
- **Header comment** must say it is an `L7 edge adapter` and name both the v1 contract it implements and the native v2 Service it leaves untouched (see `prompt.ts`).
- **Header comment** must say it is an `edge adapter` and name both the v1 contract it implements and the native v2 Service it leaves untouched (see `prompt.ts`).
- **Scope** = the lifetime of the *legacy* state it holds (the `prompt` queue is per-agent → `LifecycleScope.Agent`). Apply [orient.md](orient.md) / [design.md](design.md) normally — a LegacyService is not exempt from scope rules.
- **Delegate, do not duplicate** business logic. The LegacyService translates the v1 contract into native-Service calls and translates results back; the real work stays in the native Service.
- **Contract types come from the v1 wire schema homes** (the owning v2 domain contract or `kap-server/src/protocol`), so the interface cannot drift from the wire shape.
@ -206,7 +207,7 @@ Where the route mirrors v1, the test is the regression guard for the schema-fide
- `pnpm -C packages/kap-server test` — server routes green.
- `pnpm -C packages/kap-server test` — server routes green (incl. any wire-schema guards).
- `pnpm -C packages/agent-core-v2 test` — native + Legacy Service tests green.
- `pnpm -C packages/agent-core-v2 run lint:domain` — a LegacyService is still inside the domain layers (edge adapter, L7); it must not pull business code into the edge or invert scope direction.
- `pnpm -C packages/agent-core-v2 run lint:imports` — the import boundaries (v1 ban, kosong subtree) still hold for a LegacyService.
- `pnpm -C packages/klient test` (optionally with `KIMI_SERVER_URL` for the live legacy suites) when a v1 parity scenario exists.
## Worked example — porting v1 `/sessions/:sid/prompts`
@ -235,11 +236,11 @@ Before submitting a server-align change:
- [ ] Request and response schemas come from their owning home (the `agent-core-v2` domain contract or `packages/kap-server/src/protocol`); no inline re-declaration in server-v2.
- [ ] Existing schema fields are unchanged in name, type, and semantics; only optional fields added (if any).
- [ ] Native v2 Service left clean; v1-only behavior isolated in a `<domain>Legacy` / `I<Domain>LegacyService` edge adapter when the semantics diverge.
- [ ] LegacyService registered with the correct `LifecycleScope` and a header comment naming it an L7 edge adapter + the native Service it preserves.
- [ ] LegacyService registered with the correct `LifecycleScope` and a header comment naming it an edge adapter + the native Service it preserves.
- [ ] Domain error codes registered in `agent-core-v2`; wire codes registered in `packages/kap-server/src/protocol`; route maps them in `sendMappedError`, matching v1's status codes and idempotent envelopes.
- [ ] Route resolves the scope from the URL by `accessor.get(IX)`; no cached scope; finishes before disposal.
- [ ] Tests assert the wire envelope + protocol shape; wire-shape guards added/updated where the route mirrors v1.
- [ ] `lint:domain` passes; the LegacyService did not invert scope or domain direction.
- [ ] `lint:imports` passes; the LegacyService did not invert scope direction.
## Red lines (this subskill)

View file

@ -17,7 +17,7 @@ One folder per domain, **camelCase**: `session/`, `sessionActivity/`, `contextMe
```
- **Strictly one service per file.** An interface file holds exactly one injectable interface and exactly one `createDecorator(...)`; an impl file holds exactly one service implementation class and exactly one `registerScopedService(...)`. No exceptions for "tightly-coupled" groups: even same-scope collaborators each get their own `<name>.ts` + `<name>Service.ts` pair.
- **Scope is in the filename.** `session*.ts` = Session, `agent*.ts` = Agent, no scope prefix = App (see [Naming](#naming)). The header comment restates the same scope.
- **Scope is in the filename.** `workspace*.ts` = Workspace, `session*.ts` = Session, `agent*.ts` = Agent, no scope prefix = App (see [Naming](#naming)). The header comment restates the same scope.
- A domain therefore has as many impl files as it has services (e.g. `logService.ts` for the App `ILogService`, `sessionLogService.ts` for the Session `ISessionLogService`). See [Multi-Service domains](#multi-service-domains).
The package entry `src/index.ts` imports and `export *`s every domain's leaf files precisely (one line per leaf), so importing the package still runs every `registerScopedService(...)` side effect — exactly as the old per-domain barrels did.
@ -28,12 +28,12 @@ The package entry `src/index.ts` imports and `export *`s every domain's leaf fil
| Artifact | Rule | Example |
|---|---|---|
| Interface | `I` + scope prefix + PascalCase domain + role suffix. Scope prefix: `Session` / `Agent` / none (= App). Role suffix is usually `Service`. | `ISessionLogService`, `IAgentLoopService`, `ILogService` (App) |
| Interface | `I` + scope prefix + PascalCase domain + role suffix. Scope prefix: `Workspace` / `Session` / `Agent` / none (= App). Role suffix is usually `Service`. | `IWorkspaceDirs`, `ISessionLogService`, `IAgentLoopService`, `ILogService` (App) |
| Class | the interface name minus the leading `I`, plus `Service` if it does not already end in `Service`; `implements` the interface | `SessionLogService implements ISessionLogService`, `AppendLogStoreService implements IAppendLogStore` |
| Decorator string | lowerCamelCase of the interface name minus the leading `I`; **globally unique and stable** (it surfaces in `CyclicDependencyError.path` and "no service registered" errors) | `createDecorator<ISessionLogService>('sessionLogService')` |
| Model / non-service types | PascalCase, no `I` prefix | `SessionMeta`, `LogEntry`, `ConfigSection` |
The scope prefix makes a service's lifetime readable from its name. App services carry **no** prefix (App is the default, longest-lived tier); Session and Agent services always carry `Session` / `Agent`. The prefix applies to the interface, the class, and therefore the file names.
The scope prefix makes a service's lifetime readable from its name. App services carry **no** prefix (App is the default, longest-lived tier); Workspace, Session and Agent services always carry `Workspace` / `Session` / `Agent`. The prefix applies to the interface, the class, and therefore the file names.
> Do **not** use the scope prefix to re-merge domains by lifetime. `IAgentEntityService`, `IAgentDataService`, and `ISessionEntityService` are still banned — the prefix marks lifetime, the rest of the name must still be the real owning domain (`IBackgroundTaskEntityService`, `ISessionMetadata`, `IPermissionRulesService`). See [domain-boundaries.md](domain-boundaries.md).
@ -137,7 +137,8 @@ Holds the concrete class(es) and the top-level registration. A typical impl:
* … collaborators as roles ("logs through `log`") … Bound at App scope.
*/
import { LifecycleScope, ScopeActivation, registerScopedService } from '#/_base/di/scope';
import { LifecycleScope } from '#/app/scopes';
import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
import { ILogService } from '#/log';
import { type Greeting, IGreeter } from './greet';
@ -163,6 +164,8 @@ What belongs here:
- **Helper classes / functions** used only by this impl (e.g. a built-in writer, an `extractError` helper) — co-located in the same file.
- **Top-level `registerScopedService(...)`** — one per Service the file owns; importing the impl file runs the registration.
Base class: extend `Service` (from `#/_base/di/service`) when the unit needs capability calls on `this``provide` / `effect` / `on` / `get` / `ref` (e.g. contributing a record to a `collection` token). `Service` extends `Disposable`, so `_register` keeps working; constructor-time `provide` / `on` / `effect` calls are buffered and flushed by the kernel after construction, while `get` / `ref` throw inside the constructor (dependencies stay constructor parameters). Otherwise extend `Disposable` — both are full DI units; a service whose own members collide with the `Service` vocabulary (`name` / `state` / `config` / `get`) must stay on `Disposable` (leave a NOTE comment saying so).
## Constructor conventions
- Declare every dependency with `@IX` on a constructor parameter.
@ -204,7 +207,7 @@ A scoped Service may expose a factory method that returns a **new** instance of
### Runtime state goes into the per-scope state container
Session/Agent-scope Services register their runtime state into the scope's state container (`ISessionStateService` / `IAgentStateService`, both over `_base`'s `StateRegistry`) instead of holding it in bare instance fields, so per-scope state lives in one observable place (`snapshot()` / `onDidChange`) and dies with the scope. Reference: `session/interaction/interactionService.ts`.
Workspace/Session/Agent-scope Services register their runtime state into the scope's state container (`IWorkspaceStateService` / `ISessionStateService` / `IAgentStateService`, all over `_base`'s `StateRegistry`) instead of holding it in bare instance fields, so per-scope state lives in one observable place (`snapshot()` / `onDidChange`) and dies with the scope. Reference: `session/interaction/interactionService.ts`.
- Declare keys in the domain file and export them: `export const interactionPendingKey = defineState<Map<string, Pending>>('interaction.pending', () => new Map())``<domain>.<field>` naming, factory initializers.
- Inject `@ISessionStateService private readonly states` (or the Agent token) and `this.states.register(key)` per key at the top of the constructor.
@ -317,7 +320,8 @@ export const IGreeter: ServiceIdentifier<IGreeter> = createDecorator<IGreeter>('
```ts
// greet/greetService.ts
import { LifecycleScope, ScopeActivation, registerScopedService } from '#/_base/di/scope';
import { LifecycleScope } from '#/app/scopes';
import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
import { type Greeting, IGreeter } from './greet';
export class Greeter implements IGreeter {

View file

@ -79,8 +79,8 @@ Reach for this only when *which layer a service lives in* is itself the thing be
```ts
import { beforeEach, describe, expect, it } from 'vitest';
import { LifecycleScope } from '#/app/scopes';
import {
LifecycleScope,
ScopeActivation,
_clearScopedRegistryForTests,
registerScopedService,
@ -224,6 +224,14 @@ Do **not** add the system-under-test itself to the store. `TestInstantiationServ
Scope-host tests call `host.dispose()` in `afterEach` (or at the end of the `it`). Route teardown through the store so ordering is deterministic and nothing leaks when a test fails mid-way.
## Cascade: asserting unit state
The cascade engine's test vocabulary lives in two files: `test/_base/di/cascade.test.ts` (the mechanism matrix, including cross-scope orchestration) and `test/_base/di/provide.test.ts` (provide/unprovide semantics).
- **Assert unit states, not internals.** Every container exposes its engine as `container.cascade`: `stateOf(IX)``'Pending' | 'Activating' | 'Active' | 'Unloading' | 'Failed'`; `failureOf(IX)` → the sticky error of a `Failed` unit; `pendingSnapshot()` → the waiting-area contents.
- **The waiting area parks units with unregistered dependencies** — a unit whose declared deps are missing stays `Pending` (no throw), so a test must seed the full dependency chain. Example: a root→agent chain with no session container must seed the session-scope dependency explicitly — `ix.set(ISessionStateService, new SessionStateService())` in `test/session/agentLifecycle/agentLifecycle.test.ts` — or the dependent unit never activates.
- **Eager activation failure is sticky `Failed`, not a scope-creation throw.** Assert state + rethrow: `expect(ix.cascade.stateOf(IX)).toBe('Failed')`, then `expect(() => ix.invokeFunction((a) => a.get(IX))).toThrow(…)`. Do not expect scope/host creation itself to throw for a failing eager constructor.
## Assertions and naming
- One behavior per `it`; describe observable behavior (`child shadows parent registration`), not implementation (`calls _getOrCreateServiceInstance`).

View file

@ -6,7 +6,7 @@ Run the guards and re-scan the red lines before submitting.
Run from the package (or with `--filter @moonshot-ai/agent-core-v2`):
- `pnpm --filter @moonshot-ai/agent-core-v2 lint:domain` — domain-layer / dependency-direction guard (`scripts/check-domain-layers.mjs`). Catches a domain importing a layer it must not.
- `pnpm --filter @moonshot-ai/agent-core-v2 lint:imports` — import-boundary guard (`scripts/check-import-boundaries.mjs`). Catches v1 imports (`@moonshot-ai/agent-core`) and kosong subtree violations.
- `pnpm --filter @moonshot-ai/agent-core-v2 typecheck``tsc -p tsconfig.json --noEmit`.
- `pnpm --filter @moonshot-ai/agent-core-v2 test``vitest run`.
@ -27,6 +27,6 @@ Then re-read the [global red lines](SKILL.md#global-red-lines) once — they cat
## Red lines (this stage)
- Do not skip `lint:domain` — it is the only automated check for the dependency-direction rules.
- Do not skip `lint:imports` — it is the only automated check for the v1-import ban and the kosong subtree rules.
- Do not list internal packages in a changeset when the change enters the CLI bundle — list `@moonshot-ai/kimi-code` and describe the real change.
- Never write a `major` changeset without explicit user confirmation.

View file

@ -19,11 +19,10 @@ All other `@moonshot-ai/*` packages are treated as internal packages, including
2. **List packages that changesets can release.** If a changed package is ignored in `.changeset/config.json`, do not put that ignored package in frontmatter together with a non-ignored package; changesets rejects mixed ignored/non-ignored frontmatter.
3. **Map ignored internal changes to the affected released package.** If an ignored internal package changes CLI output or behavior, list `@moonshot-ai/kimi-code` and describe the actual user-visible or release-artifact change in the changelog text.
4. **Internal package source changes that enter the CLI bundle must manually list the CLI — when they get a changeset at all.** `@moonshot-ai/kimi-code` inline-bundles `@moonshot-ai/*` source, but those internal packages are devDependencies from the CLI's perspective, so changesets will not automatically propagate bumps. If a change enters the CLI output and is user-perceivable, list `@moonshot-ai/kimi-code`. See rule 6 for when to skip the changeset entirely.
- **Web app (`@moonshot-ai/kimi-web`) changes always enter the CLI bundle.** `@moonshot-ai/kimi-web` is ignored by changesets (see `.changeset/config.json`) and cannot be mixed with `@moonshot-ai/kimi-code` in one changeset frontmatter. Describe the web change in the changelog text, but list `@moonshot-ai/kimi-code` so the CLI release carries the bundled `dist-web` output.
5. **Docs-only and tests-only changes usually do not need a changeset.** README, internal docs, and `test/` changes that do not enter package output do not trigger a CLI bump.
6. **Skip changes users cannot perceive — write no changeset at all.** The CLI changelog is user-facing; a changeset is a changelog entry, not a shipping gate. Internal changes merged to `main` still ship in the next release triggered by any user-facing changeset, so skipping the changeset loses nothing. Do not write changesets for:
- `agent-core-v2` internal architecture: new services, refactors, config-persistence or journal/wire mechanisms.
- `kap-server` WebSocket / REST protocol changes consumed only by the bundled web UI, kimi-inspect, or other dev tooling (new endpoints, subscribe protocols, stream baselines). A web-facing feature they back gets its own `web:` entry instead.
- `kap-server` WebSocket / REST protocol changes consumed only by the bundled web UI, kimi-inspect, or other dev tooling (new endpoints, subscribe protocols, stream baselines).
- Behavior that only takes effect on the experimental engine (e.g. experimental `kimi -p`), unless it exposes documented user configuration such as a `config.toml` section or env vars that also work on a shipped surface (TUI or `kimi web`).
- When unsure whether users can perceive a change, ask before writing.
7. `@moonshot-ai/vis` / `vis-server` / `vis-web` are ignored by changesets and should not be handled. `@moonshot-ai/kimi-inspect` (a private dev app that never ships) is likewise ignored and must never appear in a changeset frontmatter.
@ -153,36 +152,6 @@ Only SDK source changed, and the CLI does not use it:
Clarify session status typing for internal SDK callers.
```
## Web app changes
`@moonshot-ai/kimi-web` is ignored by changesets and must **never** appear in a changeset frontmatter. Because the web app is bundled into the CLI release artifact, any web change that ships must list `@moonshot-ai/kimi-code` instead and describe the actual web-facing change in the text.
- Prefix the changelog entry text with `web: ` (for example `web: Fix the chat not scrolling to the bottom after sending a message.`) so the synced docs changelog can mark web UI entries. Apply this whenever the change is to the web project (`@moonshot-ai/kimi-web`).
- If a PR ships a web UI feature backed by server API changes that exist solely to power that feature, prefer a single `web:` entry describing what the web user gets. Do not add a separate server-API changeset unless the API has independent user value (a public endpoint that SDK or server consumers call directly). The docs changelog sync also deduplicates this pattern, but catching it here avoids duplicate changesets.
- Do not enumerate every micro-tweak; keep it to one sentence that captures what the web user gets.
Web-only fix:
```markdown
---
"@moonshot-ai/kimi-code": patch
---
web: Fix the chat not scrolling to the bottom after sending a message.
```
Web UI plus backing server APIs in the same PR (prefer a single `web:` entry; the API is plumbing):
```markdown
---
"@moonshot-ai/kimi-code": minor
---
web: Add the server-hosted web UI, including chat layout and session list behaviors.
```
Split into two changesets only when the API has independent user value on its own (for example, a public endpoint SDK consumers call directly). In that case add the web entry above plus a separate one such as `Add a public REST API to list archived sessions for SDK consumers.`
## `@moonshot-ai/pi-tui` changes
`@moonshot-ai/pi-tui` is a vendored fork that lives in `packages/pi-tui`. It is `private: true` and is never published, but it is **not** ignored by changesets: changesets versions it and writes `packages/pi-tui/CHANGELOG.md` so the fork keeps its own history. Because it is bundled into the CLI like other internal packages, it is an exception to Core Rule 4 — do **not** list `@moonshot-ai/kimi-code` for a change that only touches pi-tui.
@ -233,5 +202,3 @@ Fix the transcript jumping to the top when scrolling up through history during s
- The CLI wording mentions internal package names, class names, or PR numbers.
- The entry includes real internal identifiers instead of neutral placeholders.
- A change that only touches `@moonshot-ai/pi-tui` lists `@moonshot-ai/kimi-code` instead of `@moonshot-ai/pi-tui`, or mixes both packages in one frontmatter.
- A web app change entry is missing the `web: ` prefix.
- A server/API changeset exists only to back a web feature that a `web:` changeset already describes (use one `web:` entry instead, unless the API has independent user value).

View file

@ -37,7 +37,7 @@ If the CLI changelog is not in the diff (for example an SDK-only release), stop
Process the version block exactly as `sync-changelog` does for the docs site, but only in memory:
- **Strip** (`sync-changelog` step 3): drop the H1, the `### Patch Changes` / `### Minor Changes` / `### Major Changes` subheadings, PR links, and commit-hash links; keep only each entry's body text. The `Thanks [@user](...)!` credit (including the multi-author form) must be removed every time. Within each entry, drop SDK-only and provider-internal sentences (SDK capability mapping / API exposure, provider wire-format mechanics, internal XML markers) and keep only the user-facing effect and required constraints.
- **Strip** (`sync-changelog` step 3): drop the H1, the `### Patch Changes` / `### Minor Changes` / `### Major Changes` subheadings, PR links, and commit-hash links; keep only each entry's body text. The `Thanks [@user](...)!` credit (including the multi-author form) must be removed every time. Within each entry, drop SDK-only and provider-internal sentences (SDK capability mapping / API exposure, provider wire-format mechanics, internal XML markers, hook/event payload mechanics such as what an event reports or carries) and keep only the user-facing effect and required constraints.
- **Merge and deduplicate** (`sync-changelog` step 4): merge micro-tweaks to the same surface into one higher-level entry; when three or more fixes target the same UI area or the same class of problem, merge them into one higher-level fix entry (do not merge broad or genuinely distinct fixes); and drop a server/API entry that only backs a web feature already listed.
- **Classify** (`sync-changelog` step 4): bucket into Features / Bug Fixes / Polish / Refactors / Other; order within each section by reader value (in Polish, user-visible improvements before protocol/internal adjustments).
- **Translate** (`sync-changelog` step 6): translate entry bodies to Chinese; keep one sentence per entry with a parallel rhythm within a section; section headings become 新功能 / 修复 / 优化 / 重构 / 其他.
@ -48,6 +48,8 @@ If an upstream entry is not in English, flag it and stop (changeset entries must
Print the preview directly. Use `<version>(预览)` as the heading because the version is not released yet. Write `无` for empty sections. Do not write any file.
The preview is pasted into chat tools (for example Lark), where relative docs links do not resolve. Rewrite every docs link to its absolute published URL: map `../<path>.md[#anchor]` to `https://moonshotai.github.io/kimi-code/zh/<path>.html[#anchor]` — for example `../configuration/config-files.md#loop-control``https://moonshotai.github.io/kimi-code/zh/configuration/config-files.html#loop-control`. Never emit raw relative paths, and never wrap a link in backticks; code-style the link text inside the brackets instead ([`loop_control`](...)).
```
发版 PR: <url>

View file

@ -115,18 +115,11 @@ Drop SDK-only and provider-internal detail. This changelog serves `@moonshot-ai/
- Drop sentences about how the SDK maps a capability, builds model aliases, or exposes a flag through an API such as `getExperimentalFeatures()` — that belongs in the SDK changelog, not here.
- Drop provider / wire-format implementation mechanics (XML markers like `<tools_added>`, protocol field explanations, "the wire protocol is unchanged", cache-hit mechanics) unless they are the behavior a user perceives.
- Drop hook/event payload mechanics — clauses about what extra fields an event payload carries or what an event reports in a specific case (for example "enrich hook payloads with the session title and client type", "`SessionEnd` reports `archive` when a session is archived"). Keep the new events or capability itself and how to configure it.
- Keep the user-facing effect and any constraints users must follow (for example "question texts must be unique").
Do not change facts or drop a real user-facing behavior — only trim the internal-only scaffolding. For over-long, internal-heavy entries, this trim applies on the English page too, not only in translation.
Web UI prefix: if the entry is a web UI change, prefix the body text with `web: ` so readers can tell it affects the web UI:
```markdown
- web: <body text>
```
An entry counts as a web UI change when its upstream commit touches `apps/kimi-web/`. Check with `git show --name-only <hash>` (the commit hash is the one stripped above). `gen-changesets` writes this prefix for web changes, so it is usually already present in upstream — preserve it when it is there, and add it when a web entry lacks it. When a commit touches both web and non-web code, use `web:` only if the user-facing change described by the entry is in the web UI. Keep the `web:` prefix on the Chinese page too — it is a scope marker, not translated text.
Upstream language rule: `gen-changesets` requires changelog entries to be English. If the upstream CLI changelog contains a non-English entry, stop and report it to the user. Do not silently rewrite it while syncing docs.
Public-text rule: do not copy real internal endpoints, key names, account names, or service names into docs changelogs. Replace examples with neutral placeholders such as `example.com`, `example.test`, or `YOUR_API_KEY` while preserving the user-visible meaning.
@ -135,13 +128,13 @@ Public-text rule: do not copy real internal endpoints, key names, account names,
Before classifying, merge related entries and drop redundant ones from the user-facing changelog:
- **Merge micro-tweaks to the same surface.** Collapse several small tweaks to the same UI area or feature into one concise entry at the higher level. For example, "change the composer's default height" and "change the composer's default font" merge into "Polish the composer's default styling." Use the most specific common ancestor (composer, settings page, tool card, and so on). Classify the merged entry by its combined effect, and keep the `web:` prefix if the combined change is still web-facing.
- **Merge micro-tweaks to the same surface.** Collapse several small tweaks to the same UI area or feature into one concise entry at the higher level. For example, "change the composer's default height" and "change the composer's default font" merge into "Polish the composer's default styling." Use the most specific common ancestor (composer, settings page, tool card, and so on). Classify the merged entry by its combined effect
- **Merge same-surface or same-kind fixes when you have three or more.** The `Bug Fixes` section tends to accumulate many narrow UI/polish fixes that read as noise when listed one by one. When three or more fixes target the same area (for example several tool cards in the TUI, or the web session/conversation surface) or the same class of problem (for example several "jumping/flickering/collapsing during streaming" fixes), merge them into one higher-level entry. Examples:
- "Fix the Bash tool card collapsing...", "Fix the Edit tool card jumping in height...", "Fix the Edit tool card flickering while its result streams in" → "Fix several TUI tool cards jumping, flickering, or collapsing in height when results stream in or end with short output."
- "Fix the collapsed sidebar not hiding...", "Stop the chat history from replaying its entrance animation...", "Fix tool components jumping the conversation when expanded/collapsed" → "web: Fix several layout and display glitches when switching sessions, including the collapsed sidebar not hiding, the chat history replaying its entrance animation, and tool components jumping the conversation."
- Keep `web:` if the merged fixes are all web-facing. Classify as `Bug Fixes`.
- "Fix the collapsed sidebar not hiding...", "Stop the chat history from replaying its entrance animation...", "Fix tool components jumping the conversation when expanded/collapsed" → "Fix several layout and display glitches when switching sessions, including the collapsed sidebar not hiding, the chat history replaying its entrance animation, and tool components jumping the conversation."
- Classify the merged fixes as `Bug Fixes`.
- **Do not over-merge.** Leave a fix standalone when it is broad, high-value, or genuinely distinct (for example model/provider tool-calling bugs, session-list corruption, file-completion gaps). Merging is for low-reader-value, similar-shape fixes that read as a wall of similar bullets.
- **Drop server/API plumbing covered by a web entry.** If one entry adds a web UI feature (for example, an Archived sessions page) and another entry only adds the server or REST/WebSocket endpoints that exist solely to power that web feature, keep the `web:` entry and drop the API entry. CLI and web users perceive the web page; the backing API is implementation detail with no independent user value on this changelog. Keep the API entry only when it has independent user value — a new public endpoint that SDK or server consumers call directly, or a capability usable outside the web feature. When unsure, keep both and let the reviewer decide.
- **Drop server/API plumbing covered by a web entry.** If one entry adds a web UI feature (for example, an Archived sessions page) and another entry only adds the server or REST/WebSocket endpoints that exist solely to power that web feature, keep the web UI entry and drop the API entry. CLI and web users perceive the web page; the backing API is implementation detail with no independent user value on this changelog. Keep the API entry only when it has independent user value — a new public endpoint that SDK or server consumers call directly, or a capability usable outside the web feature. When unsure, keep both and let the reviewer decide.
The docs changelog uses five section types:
@ -229,6 +222,8 @@ Example:
- Update the native release workflow to use current GitHub artifact actions.
```
Doc links: an entry that changes a documented config surface may end with a pointer to the docs page — `see [X](...) for details` (Chinese: `详见 [X](...)。`). Keep it a real Markdown link into the docs tree with a relative path (for example `../configuration/config-files.md#loop-control`). When the link text is a config key or another identifier, code-style the text inside the brackets: [`loop_control`](../configuration/config-files.md#loop-control). Never wrap the whole link in backticks — `` `[loop_control](...)` `` renders as raw inline code that exposes the relative path instead of a clickable link.
### 6. Translate The Increment Into Chinese
After updating the English page, translate only the newly added English content into `docs/zh/release-notes/changelog.md`.
@ -281,7 +276,7 @@ Guidelines:
- **Keep usage hints to one short clause**.
- Bad: `传入 --allowed-host 以允许额外的 host。例如 ... (多句展开)`
- Better: `例如 kimi web --allowed-host example.com。`
- **Do not translate technical identifiers**: keep command names, flag names, file names, env vars, config keys, and the `web:` scope prefix as-is.
- **Do not translate technical identifiers**: keep command names, flag names, file names, env vars, config keys as-is.
- **Keep parallel rhythm within a section.** When several entries fix similar web surfaces (layout, animation, sizing), phrase them with a consistent structure (for example 修复 <问题>,现 <行为>) so the section reads as a tidy list rather than a mix of shapes.
Example — translating a feature entry:
@ -322,6 +317,7 @@ Check:
- PR links and commit hashes were stripped.
- No `Thanks ...!` credit remains (remove it every time).
- Real internal identifiers were replaced with neutral placeholders.
- Doc links are real Markdown links (code-styled text inside the brackets when needed), never wrapped in backticks.
- There are no empty sections.
- Markdown indentation and blank lines are intact.
@ -426,7 +422,6 @@ Return the PR URL to the user when done.
- The English docs changelog is the source of truth.
- Never edit upstream `apps/kimi-code/CHANGELOG.md`.
- Do not backfill unreleased `.changeset/*.md` drafts into the docs site.
- Prefix web UI entries with `web: ` (when the upstream commit touches `apps/kimi-web/`), and keep the prefix on both the English and Chinese pages.
- If upstream wording is wrong, leave upstream alone and fix it in a future changeset.
- Always sync on a `docs/changelog-sync-*` branch and open a PR; never push changelog docs sync directly to `main`.
- Wait for the human review checkpoint before committing, pushing, or opening a PR.
@ -440,7 +435,7 @@ Return the PR URL to the user when done.
| Leaving the `Thanks ...!` credit in docs | Remove it every time, including the multi-author form |
| Leaving near-duplicate micro-tweaks as separate bullets | Merge small tweaks to the same surface into one higher-level entry (e.g. composer height + font → composer's default styling) |
| Listing many narrow fixes to the same surface as separate bullets | When three or more fixes target the same UI area or the same class of problem, merge them into one higher-level fix entry; keep genuinely distinct or high-value fixes standalone |
| Listing a server/API entry that only backs a web feature already listed | Drop the API entry and keep the `web:` entry, unless the API has independent user value |
| Listing a server/API entry that only backs a web feature already listed | Drop the API entry and keep the web UI entry, unless the API has independent user value |
| Rewording upstream English entries | Upstream is frozen; copy the body text unless the user explicitly asks otherwise |
| Leaving English text untranslated in the Chinese page | The Chinese page must be fully Chinese except preserved technical terms |
| Editing upstream changelog text | Do not edit upstream |
@ -454,12 +449,13 @@ Return the PR URL to the user when done.
| Leaving empty sections | Delete sections with no entries |
| Putting everything under Other for convenience | Classify what can be classified first |
| Translating tool names, command names, or config keys | Keep them as written |
| Wrapping a whole doc link in backticks | Code-style the link text inside the brackets instead, so the link stays clickable: [`loop_control`](...) |
| Keeping hook/event payload-mechanics clauses | Drop what an event reports or carries; keep the new capability and how to configure it |
| Creating a changeset for docs sync | Do not create one |
| Committing or pushing directly on `main` | Create `docs/changelog-sync-<version>`, commit there, then open a PR |
| Committing or opening a PR before the user skips review or confirms review is done | Wait at the human review checkpoint |
| Using curly quotes or half-width Chinese punctuation | Follow `docs/AGENTS.md` |
| Omitting the release date from a version heading, or guessing it | Add ` (YYYY-MM-DD)` (full-width `` in Chinese) taken from the published tag |
| Forgetting or translating the `web:` prefix on web UI entries | Prefix web UI entries (commit touches `apps/kimi-web/`) with `web: ` on both pages; keep the prefix as-is when translating |
## Stop Signals

View file

@ -20,7 +20,6 @@ All other workspace packages are private internal packages, are not published to
- `@moonshot-ai/kaos`
- `@moonshot-ai/kimi-code-oauth`
- `@moonshot-ai/kimi-telemetry`
- `@moonshot-ai/kimi-web`
- `@moonshot-ai/kosong`
- `@moonshot-ai/migration-legacy`
- `@moonshot-ai/protocol`

View file

@ -86,11 +86,9 @@ jobs:
echo "KIMI_CODE_BUILT_IN_CATALOG_FILE=$CATALOG_FILE" >> "$GITHUB_ENV"
- name: Build Kimi web assets
# The SEA blob step embeds apps/kimi-code/dist-web; build the web app
# and stage its assets before producing the native executable.
run: |
pnpm --filter @moonshot-ai/kimi-web run build
node apps/kimi-code/scripts/copy-web-assets.mjs
# The SEA blob step embeds apps/kimi-code/dist-web. The bundle is
# committed (synced from the code-app repo) — just verify it is in place.
run: node apps/kimi-code/scripts/check-web-assets.mjs
- name: Build native executable (release profile, macOS signed)
if: runner.os == 'macOS' && inputs.sign-macos

View file

@ -127,8 +127,6 @@ jobs:
done
- name: Typecheck VS Code extension
run: pnpm --filter kimi-code run typecheck
- name: Typecheck kimi-web (vue-tsc)
run: pnpm --filter @moonshot-ai/kimi-web run typecheck
- name: Typecheck vis-server
run: pnpm --filter @moonshot-ai/vis-server run typecheck
- name: Typecheck vis-web

View file

@ -36,9 +36,6 @@ jobs:
- name: Build package dependencies
run: pnpm run build:packages
- name: Build Kimi web assets
run: pnpm --filter @moonshot-ai/kimi-web run build
- name: Generate Kimi Code built-in catalog
shell: bash
run: |

1
.gitignore vendored
View file

@ -1,6 +1,5 @@
node_modules/
dist/
dist-web/
dist-single/
dist-native/
.tmp-api-extractor/

View file

@ -90,6 +90,26 @@
"eslint/no-console": "off"
}
},
{
// The stage-6 worker closure: these modules (and everything
// packages/minidb/src/worker/ pulls in) are loaded by a bare
// node:worker_threads Worker under Node's native type stripping with
// `execArgv: ['--experimental-transform-types']`, which requires
// explicit `.ts` import specifiers (the strip loader does not remap
// `.js` -> `.ts`). Keep the exception scoped to exactly that closure.
"files": [
"packages/minidb/src/worker/**/*.ts",
"packages/minidb/src/codec.ts",
"packages/minidb/src/crc32.ts",
"packages/minidb/src/trigram.ts",
"packages/minidb/src/text-postings.ts",
"packages/minidb/src/text-index/tokenize.ts",
"packages/minidb/src/gen-codec.ts"
],
"rules": {
"import/extensions": "off"
}
},
{
"files": ["packages/kosong/src/providers/**/*.ts"],
"rules": {
@ -146,6 +166,7 @@
],
"ignorePatterns": [
"dist/",
"dist-web/",
"coverage/",
"node_modules/",
"apps/*/scripts/",

File diff suppressed because one or more lines are too long

View file

@ -8,4 +8,4 @@ agents/
src/generated/vis-web-asset.ts
# Copied from packages/pi-tui/native at build time by scripts/copy-native-assets.mjs
native/
/native/

View file

@ -1,5 +1,187 @@
# @moonshot-ai/kimi-code
## 0.34.0
### Minor Changes
- [#2646](https://github.com/MoonshotAI/kimi-code/pull/2646) [`3c75a27`](https://github.com/MoonshotAI/kimi-code/commit/3c75a27da66e522ae670ec8ce9093ea71d091d27) Thanks [@liruifengv](https://github.com/liruifengv)! - Show a cache-expiry reminder when resuming a long-idle session or submitting after a long idle stretch.
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: When a model request fails and interrupts a conversation, a persistent failure card now stays in the session with one-click resume.
- [#2652](https://github.com/MoonshotAI/kimi-code/pull/2652) [`68ba740`](https://github.com/MoonshotAI/kimi-code/commit/68ba740ebfb3e32ad9abdb8607f48d4387cf6f69) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Add Windows support for the built-in Kimi Computer Use capability and show the underlying error when capability setup fails. Install it from `/plugins` on Windows x64.
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Add a flat view to the sidebar session list.
### Patch Changes
- [#2648](https://github.com/MoonshotAI/kimi-code/pull/2648) [`d1ded01`](https://github.com/MoonshotAI/kimi-code/commit/d1ded01b7c50c9847440f4645fe13f588becdc66) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Restore how the last turn ended (completed, cancelled, or failed) when a session is resumed after a server restart, so clients can still surface a previously failed turn instead of the session looking silently stopped.
- [#2666](https://github.com/MoonshotAI/kimi-code/pull/2666) [`335588e`](https://github.com/MoonshotAI/kimi-code/commit/335588e2594a61a767ce258b34b4049a32b18fe5) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Session listings keep how the last turn ended (completed, cancelled, or failed) across server restarts, so clients can mark previously failed sessions before they are opened.
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix the model picker overflowing the screen when many models are available, leaving the bottom options unreachable.
- [#2639](https://github.com/MoonshotAI/kimi-code/pull/2639) [`8588121`](https://github.com/MoonshotAI/kimi-code/commit/858812193a267fcb9382e351137f892646ef79aa) Thanks [@7Sageer](https://github.com/7Sageer)! - /feedback now works for any signed-in user regardless of the active model; signed-out users are shown the sign-up page and GitHub Issues links instead.
- [#2675](https://github.com/MoonshotAI/kimi-code/pull/2675) [`34c4181`](https://github.com/MoonshotAI/kimi-code/commit/34c418143759a9e80cdda97e95e609c5a993916d) Thanks [@sailist](https://github.com/sailist)! - Fix kimi -p exiting right after the main turn instead of waiting for background tasks and subagents to finish.
- [#2694](https://github.com/MoonshotAI/kimi-code/pull/2694) [`02c026d`](https://github.com/MoonshotAI/kimi-code/commit/02c026d4871a14cd5e7b4b0e0ec71ba815f643df) Thanks [@sailist](https://github.com/sailist)! - Keep live sessions stable when an MCP server is removed from the workspace config or uninstalled with its plugin: its tools stay registered in open sessions but calls fail with a removal notice, and the MCP panel shows the removed status. Servers added mid-session — by a plugin install or a config edit — are not registered in open sessions; they take effect in new sessions or after `/new` or `/reload`.
- [#2647](https://github.com/MoonshotAI/kimi-code/pull/2647) [`7bd3fd9`](https://github.com/MoonshotAI/kimi-code/commit/7bd3fd9f6e6c10f88d33b85760631ad6212b5f58) Thanks [@sailist](https://github.com/sailist)! - Read UTF-16 LE/BE text files (with or without a BOM) by transcoding them to UTF-8 instead of refusing them as binary; the web UI file viewer displays them as text as well.
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: The sidebar error marker now only appears when the last turn failed; manually cancelled sessions are no longer flagged.
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix attachments being silently dropped when sent together with a skill command.
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix a manually chosen thinking level being reset to the model default when the first message of a new session is a skill command.
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix session renaming during IME composition — Enter no longer submits mid-composition and Esc no longer exits the editor while composing.
- [#2686](https://github.com/MoonshotAI/kimi-code/pull/2686) [`ef61084`](https://github.com/MoonshotAI/kimi-code/commit/ef610840098a57819d62d407f33256e14b512c77) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Use a compatible PowerShell for Windows Kimi Computer Use installation, provide actionable recovery for locked plugin files, and keep its marketplace name consistent after installation.
- [#2679](https://github.com/MoonshotAI/kimi-code/pull/2679) [`7b2784b`](https://github.com/MoonshotAI/kimi-code/commit/7b2784b9b7bf4749058da48923ecbbc8019eb7af) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Subagent tasks now show the model and thinking level they use.
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix text selection while renaming a session or workspace — dragging no longer moves the whole list item.
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix the chevron direction on the "show less" button of the changed-files summary card.
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: During automatic retries after a failed model request, the working status now shows retry progress (attempt N of M) instead of looking unresponsive.
- [#2677](https://github.com/MoonshotAI/kimi-code/pull/2677) [`713bf1a`](https://github.com/MoonshotAI/kimi-code/commit/713bf1a5a2b388e4c5f9d3f471a728b8edbf5811) Thanks [@liruifengv](https://github.com/liruifengv)! - Fix resumed sessions rendering background task completion notifications as raw protocol text instead of a task status card.
- [#2692](https://github.com/MoonshotAI/kimi-code/pull/2692) [`03aa66c`](https://github.com/MoonshotAI/kimi-code/commit/03aa66ca0cca5880dc3a4a89e4f46d09acbe47ae) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Show browser extension links and activation steps after installing Kimi WebBridge.
- [#2645](https://github.com/MoonshotAI/kimi-code/pull/2645) [`2b89373`](https://github.com/MoonshotAI/kimi-code/commit/2b893733f9853dc0aaeb775d9670d277db8e0381) Thanks [@sailist](https://github.com/sailist)! - Fix the web UI opening the Documents folder instead of the requested file on Windows when the file path contains spaces.
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix the background-tasks and todos pills being pushed to the top of the window when the plan approval dialog expands.
## 0.33.0
### Minor Changes
- [#2407](https://github.com/MoonshotAI/kimi-code/pull/2407) [`0abcd00`](https://github.com/MoonshotAI/kimi-code/commit/0abcd00f7fd3e3cbf087509ffef1c54a6f8d396d) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Add Kimi Computer Use and Kimi WebBridge as built-in official marketplace entries in the v2 CLI. Installing from `/plugins` sets up the latest managed runtime and plugin together, reports incomplete manual steps, and supports retrying interrupted setup.
- [#2627](https://github.com/MoonshotAI/kimi-code/pull/2627) [`f881cdd`](https://github.com/MoonshotAI/kimi-code/commit/f881cdd97073475c43272ec5734bbc39290dd399) Thanks [@sailist](https://github.com/sailist)! - Run the CLI surfaces (interactive TUI, `kimi -p`, `kimi acp`, `kimi export`, `kimi provider`) on the agent-core-v2 engine by default. Set `KIMI_CODE_LEGACY_FLAG=1` to fall back to the legacy engine.
- [#2565](https://github.com/MoonshotAI/kimi-code/pull/2565) [`54c04bf`](https://github.com/MoonshotAI/kimi-code/commit/54c04bf03ddbeb46d02b2edb460ea091ae194509) Thanks [@7Sageer](https://github.com/7Sageer)! - `/fork` no longer switches to the forked session: the current session stays active and its background tasks keep running. Find the fork in `/sessions`.
- [#2630](https://github.com/MoonshotAI/kimi-code/pull/2630) [`3bd098b`](https://github.com/MoonshotAI/kimi-code/commit/3bd098b80643c99eabdc602b767dbc53fc47cedd) Thanks [@liruifengv](https://github.com/liruifengv)! - Ask whether to trust the current folder on startup.
- [#2630](https://github.com/MoonshotAI/kimi-code/pull/2630) [`3bd098b`](https://github.com/MoonshotAI/kimi-code/commit/3bd098b80643c99eabdc602b767dbc53fc47cedd) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Add and manage custom providers in settings.
- [#2599](https://github.com/MoonshotAI/kimi-code/pull/2599) [`541ddd2`](https://github.com/MoonshotAI/kimi-code/commit/541ddd2d898c4880a312874b1c539f85888bf0c1) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Overhaul the UI/UX and fix known issues.
### Patch Changes
- [#2601](https://github.com/MoonshotAI/kimi-code/pull/2601) [`75fe068`](https://github.com/MoonshotAI/kimi-code/commit/75fe068a01261ff6b34f176530b338ec6a24918e) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Fix built-in capability availability and installed status in `/plugins`, preserve legacy WebBridge skills as backups during updates, and prevent Computer Use updates from duplicating or disconnecting MCP servers.
- [#2635](https://github.com/MoonshotAI/kimi-code/pull/2635) [`2b3e9a9`](https://github.com/MoonshotAI/kimi-code/commit/2b3e9a9f7910b0bb8050380068fa122c2c2cee91) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Rename the partner plugin marketplace tab to Curated and clarify that it contains third-party plugins from Kimi partners.
- [#2614](https://github.com/MoonshotAI/kimi-code/pull/2614) [`8db7d42`](https://github.com/MoonshotAI/kimi-code/commit/8db7d42f23472a692eb389a0e0e5a3e18aa1b94d) Thanks [@RealKai42](https://github.com/RealKai42)! - Add /bug as an alias for the /feedback slash command. Type /bug to submit feedback.
- [#2586](https://github.com/MoonshotAI/kimi-code/pull/2586) [`278b6af`](https://github.com/MoonshotAI/kimi-code/commit/278b6af19d8708ec0f3eb2696d62a8d8209d497d) Thanks [@7Sageer](https://github.com/7Sageer)! - Ensure the first request waits for MCP startup to finish while the interface still opens immediately.
- [#2620](https://github.com/MoonshotAI/kimi-code/pull/2620) [`2ee6e43`](https://github.com/MoonshotAI/kimi-code/commit/2ee6e431240a4a31034e0a403011dd6b2bfef9df) Thanks [@xpzouying](https://github.com/xpzouying)! - Fixed MCP OAuth re-authorization always failing with "Invalid redirect URI": the OAuth callback listener binds a random port per flow, but the dynamic client registration recorded the first flow's port, so every later interactive authorization was rejected at the authorization endpoint. A stale registration is now dropped automatically and the flow re-registers with the current callback URI.
- [#2596](https://github.com/MoonshotAI/kimi-code/pull/2596) [`c32e661`](https://github.com/MoonshotAI/kimi-code/commit/c32e661faa931df9fdc72e63230f3ebebc00dce5) Thanks [@xpzouying](https://github.com/xpzouying)! - MCP tool results now surface the spec-defined `structuredContent` field and `_meta` server metadata to the model as a serialized `<mcp-structured-result>` block, instead of silently dropping them. Servers that return their machine-readable contract in these fields work the same as on other MCP hosts.
- [#2612](https://github.com/MoonshotAI/kimi-code/pull/2612) [`e357028`](https://github.com/MoonshotAI/kimi-code/commit/e3570280bde775a153ee04388393506da7ac4cc1) Thanks [@7Sageer](https://github.com/7Sageer)! - Fix all tool calls failing with spawn EBADF on macOS when a skill folder contains a very large file tree.
- [#2630](https://github.com/MoonshotAI/kimi-code/pull/2630) [`3bd098b`](https://github.com/MoonshotAI/kimi-code/commit/3bd098b80643c99eabdc602b767dbc53fc47cedd) Thanks [@liruifengv](https://github.com/liruifengv)! - Start the interactive TUI without creating a session.
- [#2630](https://github.com/MoonshotAI/kimi-code/pull/2630) [`3bd098b`](https://github.com/MoonshotAI/kimi-code/commit/3bd098b80643c99eabdc602b767dbc53fc47cedd) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Show the signed-in account and plan usage.
- [#2630](https://github.com/MoonshotAI/kimi-code/pull/2630) [`3bd098b`](https://github.com/MoonshotAI/kimi-code/commit/3bd098b80643c99eabdc602b767dbc53fc47cedd) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Set an emoji for the session title.
- [#2630](https://github.com/MoonshotAI/kimi-code/pull/2630) [`3bd098b`](https://github.com/MoonshotAI/kimi-code/commit/3bd098b80643c99eabdc602b767dbc53fc47cedd) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Pin sessions to the top of the sidebar.
## 0.32.0
### Minor Changes
- [#2558](https://github.com/MoonshotAI/kimi-code/pull/2558) [`75395f6`](https://github.com/MoonshotAI/kimi-code/commit/75395f6abb17f83f30d16b51f4e060a639f43622) Thanks [@sailist](https://github.com/sailist)! - Add the TurnStarted, UserPromptQueued, TaskStarted, and SessionHeartbeat hook events, enrich hook payloads with the session title and client type, include the model and profile in SessionStart, and report SessionEnd as archive when a session is archived instead of exited. Configure the new events under [[hooks]] in config.toml.
### Patch Changes
- [#2416](https://github.com/MoonshotAI/kimi-code/pull/2416) [`eaab2b6`](https://github.com/MoonshotAI/kimi-code/commit/eaab2b6f28c0b958edf8ab5ae5e78a4c0426af26) Thanks [@mangeshraut712](https://github.com/mangeshraut712)! - Fall back to the built-in models.dev catalog snapshot when the public catalog is unreachable, so Known third-party provider import still works offline or in blocked networks.
- [#2083](https://github.com/MoonshotAI/kimi-code/pull/2083) [`bfa0080`](https://github.com/MoonshotAI/kimi-code/commit/bfa00807c975fdc5b84dda32d47b16b09e8d42c1) Thanks [@StaR4y](https://github.com/StaR4y)! - web: Fix dark-mode monochrome controls and align the chat composer corner radius with the design system.
- [#2559](https://github.com/MoonshotAI/kimi-code/pull/2559) [`dfc55a5`](https://github.com/MoonshotAI/kimi-code/commit/dfc55a5c977dbff657e1da74ff5c2b9d488807be) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Render the "/login" already-logged-in confirmation in the success color instead of dim text, so the "Already logged in. Model configuration refreshed." message is clearly visible.
- [#2572](https://github.com/MoonshotAI/kimi-code/pull/2572) [`6ba75a1`](https://github.com/MoonshotAI/kimi-code/commit/6ba75a173b595904bc70d0d7161de2f9b964c961) Thanks [@sailist](https://github.com/sailist)! - Rename the `[loop_control] max_retries_per_step` config key to `max_attempts_per_step` and `max_steps_per_run` to `max_steps_per_turn`: on the v2 engine the old keys no longer take effect and a startup warning prompts the rename in `config.toml`. The `KIMI_LOOP_MAX_RETRIES_PER_STEP` env var is likewise deprecated in favor of `KIMI_LOOP_MAX_ATTEMPTS_PER_STEP` but keeps working with a warning.
- [#2585](https://github.com/MoonshotAI/kimi-code/pull/2585) [`c396873`](https://github.com/MoonshotAI/kimi-code/commit/c39687318c64bf8a305a10bf9ca86ef6ef2c6656) Thanks [@sailist](https://github.com/sailist)! - Fix submitting answers to interactive question prompts being rejected when the model provider returns tool call IDs containing colons (some OpenAI-compatible gateways).
- [#2562](https://github.com/MoonshotAI/kimi-code/pull/2562) [`071b6a5`](https://github.com/MoonshotAI/kimi-code/commit/071b6a50d9c2ce9c4b45dc4d58dac1101b8c4f52) Thanks [@sailist](https://github.com/sailist)! - Serve v1 message history from the server layer and drop the engine-side legacy message adapter; the /api/v1 message contract is unchanged.
- [#2562](https://github.com/MoonshotAI/kimi-code/pull/2562) [`071b6a5`](https://github.com/MoonshotAI/kimi-code/commit/071b6a50d9c2ce9c4b45dc4d58dac1101b8c4f52) Thanks [@sailist](https://github.com/sailist)! - Assemble the session snapshot endpoint from the engine's services for both cold and live sessions, and remove the KIMI_SNAPSHOT_READER, KIMI_SNAPSHOT_TIMEOUT_MS, and KIMI_SNAPSHOT_CACHE_LIMIT environment knobs.
- [#2563](https://github.com/MoonshotAI/kimi-code/pull/2563) [`2118544`](https://github.com/MoonshotAI/kimi-code/commit/21185447fe0f04dbe342bebb6c6d0b364fd43daa) Thanks [@sailist](https://github.com/sailist)! - Fix the context window limit showing as 0 in session status updates when no model is bound yet or the configured model no longer resolves; the limit now falls back to the default model or is omitted when unknown.
- [#2563](https://github.com/MoonshotAI/kimi-code/pull/2563) [`2118544`](https://github.com/MoonshotAI/kimi-code/commit/21185447fe0f04dbe342bebb6c6d0b364fd43daa) Thanks [@sailist](https://github.com/sailist)! - The `[token_counting]` strategy now only selects the reported context size: `estimated` keeps provider-reported usage out of the context-size display, and `measured` no longer gets stuck retrying an oversized compaction request until it fails.
- [#2563](https://github.com/MoonshotAI/kimi-code/pull/2563) [`2118544`](https://github.com/MoonshotAI/kimi-code/commit/21185447fe0f04dbe342bebb6c6d0b364fd43daa) Thanks [@sailist](https://github.com/sailist)! - Add a `[token_counting]` config section to choose how context token counts are derived: `measured+estimated` (default), `measured` (provider usage only), or `estimated` (heuristic only, for providers without usage reporting). Set `strategy` under `[token_counting]` in config.toml (or `KIMI_TOKEN_COUNTING_STRATEGY`) to switch.
## 0.31.1
### Patch Changes
- [#2410](https://github.com/MoonshotAI/kimi-code/pull/2410) [`f1a3475`](https://github.com/MoonshotAI/kimi-code/commit/f1a3475ad5d6540447496701aa75fd4b035ecb28) Thanks [@sailist](https://github.com/sailist)! - Fix sporadic "model is not configured" errors when starting kimi web, caused by the background provider-model refresh transiently clearing the model catalog while the first session was being created.
- [#2400](https://github.com/MoonshotAI/kimi-code/pull/2400) [`1f3f5da`](https://github.com/MoonshotAI/kimi-code/commit/1f3f5dadaaa4a1d705cc98aee1dbbef13680502c) Thanks [@7Sageer](https://github.com/7Sageer)! - Preserve the assistant's partial output when a turn is interrupted with Esc, and remind the model that the previous turn was deliberately interrupted.
- [#2415](https://github.com/MoonshotAI/kimi-code/pull/2415) [`5c0ec29`](https://github.com/MoonshotAI/kimi-code/commit/5c0ec2938ac3a01624b6503e5e5df80c9b08f46a) Thanks [@wbxl2000](https://github.com/wbxl2000)! - web: Enable Monaco-based highlighting for code blocks, and fix line numbers overlapping or drifting out of alignment in fallback-rendered code blocks.
- [#2442](https://github.com/MoonshotAI/kimi-code/pull/2442) [`bb2919e`](https://github.com/MoonshotAI/kimi-code/commit/bb2919eb818a6cb51c71cbabf1bac9020131bce7) Thanks [@liruifengv](https://github.com/liruifengv)! - Reduce frequent full-screen redraws in the TUI.
- [#2125](https://github.com/MoonshotAI/kimi-code/pull/2125) [`e111c87`](https://github.com/MoonshotAI/kimi-code/commit/e111c878fd5cd07994e125b9e4e07e4069f01be1) Thanks [@bowenliang123](https://github.com/bowenliang123)! - web: Order permission modes from safest to most permissive across settings surfaces, and fix the swapped yolo/auto risk colors in the status panel and mobile settings.
- [#2459](https://github.com/MoonshotAI/kimi-code/pull/2459) [`326e1fb`](https://github.com/MoonshotAI/kimi-code/commit/326e1fb6ce59fbf2d6c7646e6d587759565814fd) Thanks [@wbxl2000](https://github.com/wbxl2000)! - web: Fix chat code blocks rendering in the proportional UI font at the wrong size after the markdown renderer upgrade, and align the loading fallback with the highlighted block so the upgrade no longer shifts layout.
- [#2437](https://github.com/MoonshotAI/kimi-code/pull/2437) [`ed7a4cc`](https://github.com/MoonshotAI/kimi-code/commit/ed7a4cc095e1619e4dbb6c2c77c89a52e312b085) Thanks [@sailist](https://github.com/sailist)! - web: Make the @ file mention work in a new-session draft, before the first prompt creates the session.
- [#2437](https://github.com/MoonshotAI/kimi-code/pull/2437) [`ed7a4cc`](https://github.com/MoonshotAI/kimi-code/commit/ed7a4cc095e1619e4dbb6c2c77c89a52e312b085) Thanks [@sailist](https://github.com/sailist)! - web: Fix new sessions showing the thinking level (e.g. Max) while the first message actually ran with thinking off.
## 0.31.0
### Minor Changes
- [#2365](https://github.com/MoonshotAI/kimi-code/pull/2365) [`fa2c5ce`](https://github.com/MoonshotAI/kimi-code/commit/fa2c5ce18b70577fa3ada4eb8bdd4993891994ce) Thanks [@7Sageer](https://github.com/7Sageer)! - Add support for plugin-contributed custom agents, discovered automatically and available for sub-agent delegation. Ship an `agents/` directory in the plugin (or declare `agents` paths in the plugin manifest) to provide them.
- [#2314](https://github.com/MoonshotAI/kimi-code/pull/2314) [`02d77b2`](https://github.com/MoonshotAI/kimi-code/commit/02d77b20d941873563f14890e049ffe40cec76e4) Thanks [@7Sageer](https://github.com/7Sageer)! - Allow enabled plugins to contribute agent system-prompt instructions through `systemPrompt` or `systemPromptPath` in `kimi.plugin.json`, effective on both agent engines (the TUI, `kimi -p`, and `kimi web`).
- [#2232](https://github.com/MoonshotAI/kimi-code/pull/2232) [`efac96c`](https://github.com/MoonshotAI/kimi-code/commit/efac96c8a95a3c3ca4e1ae9bce38082498a02b2e) Thanks [@7Sageer](https://github.com/7Sageer)! - Support Markdown-defined custom agents on agent-core.
- [#2232](https://github.com/MoonshotAI/kimi-code/pull/2232) [`efac96c`](https://github.com/MoonshotAI/kimi-code/commit/efac96c8a95a3c3ca4e1ae9bce38082498a02b2e) Thanks [@7Sageer](https://github.com/7Sageer)! - Add the /secondary_model slash command to configure the secondary model used by subagents.
### Patch Changes
- [#2382](https://github.com/MoonshotAI/kimi-code/pull/2382) [`40172c7`](https://github.com/MoonshotAI/kimi-code/commit/40172c7ca96ca981b043b793588dd32e898979fa) Thanks [@liruifengv](https://github.com/liruifengv)! - Fix request headers not being passed correctly on some requests.
- [#2379](https://github.com/MoonshotAI/kimi-code/pull/2379) [`691ec46`](https://github.com/MoonshotAI/kimi-code/commit/691ec4679ea19d6be8ac18f359088384ed3e446d) Thanks [@RealKai42](https://github.com/RealKai42)! - Remove the blocking `block`/`timeout` wait from the TaskOutput tool so checking a background task can no longer stall the conversation; it now always returns an immediate snapshot, and completion still arrives via automatic notification.
- [#2395](https://github.com/MoonshotAI/kimi-code/pull/2395) [`d10b1c1`](https://github.com/MoonshotAI/kimi-code/commit/d10b1c130813dbd6ee8c8599a6a98feb36aea67f) Thanks [@sailist](https://github.com/sailist)! - Fix sessions missing from the session picker when their cached metadata predates the archived flag.
## 0.30.0
### Minor Changes
- [#2255](https://github.com/MoonshotAI/kimi-code/pull/2255) [`67dd031`](https://github.com/MoonshotAI/kimi-code/commit/67dd03149f36be91a0c081e70d8a2d721b0f1c64) Thanks [@he-yufeng](https://github.com/he-yufeng)! - Add a customizable footer status line, configured via `[status_line]` in `tui.toml`.
### Patch Changes
- [#2313](https://github.com/MoonshotAI/kimi-code/pull/2313) [`de0ba9d`](https://github.com/MoonshotAI/kimi-code/commit/de0ba9d0654273ff6b028a7a561983ebee4e723e) Thanks [@starquakee](https://github.com/starquakee)! - Stop the turn after repeated invalid tool calls instead of retrying indefinitely.
- [#2147](https://github.com/MoonshotAI/kimi-code/pull/2147) [`29783e4`](https://github.com/MoonshotAI/kimi-code/commit/29783e471afcf7975852e496907646458264d2e6) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Show a quota note after installing official plugins that bill against plan quota (such as Kimi Datasource).
- [#2147](https://github.com/MoonshotAI/kimi-code/pull/2147) [`29783e4`](https://github.com/MoonshotAI/kimi-code/commit/29783e471afcf7975852e496907646458264d2e6) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Show a notice when an official plugin used in the session has an update available. Run /plugins to install it.
- [#1857](https://github.com/MoonshotAI/kimi-code/pull/1857) [`cdbd33c`](https://github.com/MoonshotAI/kimi-code/commit/cdbd33c13c7f5cd4c49ec112ee4313b3938a7752) Thanks [@vinlee19](https://github.com/vinlee19)! - Fail fast when account quota or balance is exhausted instead of silently retrying for ~3 minutes.
- [#2294](https://github.com/MoonshotAI/kimi-code/pull/2294) [`425cfdf`](https://github.com/MoonshotAI/kimi-code/commit/425cfdf53f0fd3b01527f5fba87acff68f49f368) Thanks [@wbxl2000](https://github.com/wbxl2000)! - web: Fix garbled line numbers in code blocks.
- [#2312](https://github.com/MoonshotAI/kimi-code/pull/2312) [`d03a488`](https://github.com/MoonshotAI/kimi-code/commit/d03a4886fdf7c35014c10079a3d417aeb0447d9a) Thanks [@sailist](https://github.com/sailist)! - Remove the 50 MB size limit on file uploads to the built-in server.
## 0.29.2
### Patch Changes

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import{bQ as A,M as H,aU as b,bE as V,az as J,aL as O,s as Q,v as R,I as F,bJ as G,bL as K,aw as L,H as W,bb as Z,bB as ee,g as te,au as le,T as ae,as as B,bR as ne}from"./index-HRJ6xRtC.js";var k=(h,E,e)=>new Promise((o,p)=>{var i=a=>{try{d(e.next(a))}catch(c){p(c)}},y=a=>{try{d(e.throw(a))}catch(c){p(c)}},d=a=>a.done?o(a.value):Promise.resolve(a.value).then(i,y);d((e=e.apply(h,E)).next())});const oe=["id"],ie=["data-placement"],ue=A(H({__name:"Tooltip",props:{visible:{type:Boolean},anchorEl:{},content:{},placement:{},offset:{},originX:{},originY:{},id:{},isDark:{type:[Boolean,null]}},setup(h){var E;const e=h,o=b(null),p=b(null),i=b({transform:"translate3d(0px, 0px, 0px)",left:"0px",top:"0px"}),y=b({}),d=b((E=e.placement)!=null?E:"top"),a=b(!1);let c=null,$=null,T=null,C=null,w=null,s=0;function X(){return C?Promise.resolve(C):(w||(w=ne(()=>import("./floating-ui.dom-xGUaHE3m.js"),[]).then(l=>(C=l,l)).catch(l=>{throw w=null,l})),w)}function D(){c&&(c(),c=null),$=null,T=null}function P(l){return k(this,null,function*(){const t=e.anchorEl,n=o.value;if(!e.visible||!t||!n||$===t&&T===n)return;const{autoUpdate:r}=yield X();l()&&e.visible&&e.anchorEl===t&&o.value===n&&(D(),$=t,T=n,c=r(t,n,()=>{N().catch(()=>{_()})}))})}function N(){return k(this,null,function*(){var l,t;const n=e.anchorEl,r=o.value;if(!e.visible||!n||!r)return!1;const{arrow:u,computePosition:m,flip:v,offset:f,shift:x}=yield X();if(!e.visible||e.anchorEl!==n||o.value!==r)return!1;const g=[f((l=e.offset)!=null?l:6),v(),x({padding:6}),...p.value?[u({element:p.value,padding:4})]:[]],{x:S,y:j,placement:Y,middlewareData:z}=yield m(n,r,{placement:(t=e.placement)!=null?t:"top",middleware:g,strategy:"fixed"});if(!e.visible||e.anchorEl!==n||o.value!==r)return!1;if(i.value.transform=`translate3d(${Math.round(S)}px, ${Math.round(j)}px, 0)`,i.value.left="0px",i.value.top="0px",d.value=Y,z.arrow&&p.value){const{x:I,y:U}=z.arrow,q={top:"bottom",bottom:"top",left:"right",right:"left"}[Y.split("-")[0]];y.value={left:I!=null?`${I}px`:"",top:U!=null?`${U}px`:"",[q]:"-3px"}}return!0})}function _(){var l,t;const n=e.anchorEl,r=o.value;if(!n||!r)return!1;const u=n.getBoundingClientRect(),m=r.getBoundingClientRect(),v=(l=e.offset)!=null?l:6,f=(t=e.placement)!=null?t:"top";let x=u.left,g=u.top;return f==="bottom"?g=u.bottom+v:f==="left"?x=u.left-m.width-v:f==="right"?x=u.right+v:g=u.top-m.height-v,i.value.transform=`translate3d(${Math.round(Math.max(0,x))}px, ${Math.round(Math.max(0,g))}px, 0)`,i.value.left="0px",i.value.top="0px",d.value=f,y.value={},!0}V(()=>e.visible,l=>k(null,null,function*(){const t=++s;if(l){if(a.value=!1,yield B(),t!==s||!e.visible)return;if(e.anchorEl&&o.value)try{const n=e.anchorEl,r=o.value,u=n.getBoundingClientRect();if(!(yield N())||t!==s||!e.visible||e.anchorEl!==n||o.value!==r)return;const m=i.value.transform;if(e.originX!=null&&e.originY!=null){const v=Math.abs(Number(e.originX)-u.left),f=Math.abs(Number(e.originY)-u.top);if(Math.hypot(v,f)>120){if(i.value.transform=`translate3d(${Math.round(e.originX)}px, ${Math.round(e.originY)}px, 0)`,yield B(),t!==s||!e.visible||(a.value=!0,yield B(),t!==s||!e.visible))return;i.value.transform=m}else a.value=!0}else a.value=!0;yield P(()=>t===s)}catch{if(t!==s||!e.visible)return;if(a.value=_(),e.anchorEl&&o.value)try{yield P(()=>t===s)}catch{}}else a.value=!0}else a.value=!1,D()}));let M=0;return V([()=>e.anchorEl,()=>e.placement,()=>e.content],()=>k(null,null,function*(){const l=++M;if(e.visible&&e.anchorEl&&o.value){if(yield B(),l!==M||!e.visible||!e.anchorEl||!o.value)return;try{const t=yield N();if(l!==M||!e.visible||!e.anchorEl||!o.value)return;t||_()}catch{_()}yield P(()=>l===M)}})),J(()=>{s+=1,D()}),(l,t)=>(O(),Q(ae,{to:"body"},[R("div",{class:le(["markstream-vue",{dark:h.isDark}])},[F(te,{name:"tooltip",appear:""},{default:G(()=>[K(R("div",{id:e.id,ref_key:"tooltip",ref:o,style:L({position:"fixed",left:i.value.left,top:i.value.top,transform:i.value.transform,visibility:a.value?"visible":"hidden",pointerEvents:a.value?void 0:"none"}),class:"tooltip-element",role:"tooltip"},[W(Z(h.content)+" ",1),R("div",{ref_key:"arrowEl",ref:p,class:"tooltip-arrow","data-placement":d.value,style:L(y.value)},null,12,ie)],12,oe),[[ee,h.visible]])]),_:1})],2)]))}}),[["__scopeId","data-v-c606ee4c"]]);export{ue as default};

View file

@ -0,0 +1 @@
function e(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}export{e as g};

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-Dk056XM2.js";import{p as f}from"./chunk-JWPE2WC7-K16PVern.js";import{_ as n,l as o}from"./mermaidParser.worker-BFSlSHEW.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-C-EfYT07.js";var v=d().RailroadAbnf.parser.LangiumParser,l=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),i=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:i,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return l(e.element);case"AbnfOptionalGroup":return{type:"optional",element:l(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:l(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},w={parser:R,db:a,renderer:u,styles:p};export{w as diagram};

View file

@ -0,0 +1 @@
import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-hIDvr-8C.js";import{p as f}from"./chunk-JWPE2WC7-DTx-f56M.js";import{_ as n,l as o}from"./mermaid.core-Cahi9cr1.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-C5gNr-Q4.js";import"./index-HRJ6xRtC.js";import"./_commonjsHelpers-CqkleIqs.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},F={parser:R,db:a,renderer:u,styles:p};export{F as diagram};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show more