Commit graph

362 commits

Author SHA1 Message Date
github-actions[bot]
8bf5bacba9
ci: release packages (#1989)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-22 17:24:39 +08:00
Kai
4c763f6763
feat: send prompt-attached videos directly with the prompt (#1999)
Some checks are pending
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 / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Release / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
CI / test-windows (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat: send prompt-attached videos directly with the prompt

Videos attached to a prompt (pasted in the TUI, uploaded in the web UI)
previously reached the model only after it opened the file with
ReadMediaFile — an extra tool round trip that could leave the video
unseen when the model never made that call. They are now uploaded
through the model provider's file channel and embedded directly in the
user message as the provider-issued reference; ReadMediaFile stays as
the fallback and as the model's own way to open video files.

- agent-core: new uploadVideo agent RPC and Session.uploadVideo in the
  SDK; the TUI uploads pasted videos at submit time, falling back to
  the file-tag form on failure
- kap-server: inline file-source video prompt parts at the REST edge
  and map provider file ids back to local uploads behind
  GET /files/llm/{llm_id}
- kimi-web: play provider-referenced prompt videos after reload/resume

* fix: fall back to inline video when the upload channel fails

ReadMediaFile only used the provider's video upload channel when one
was bound, and surfaced a hard tool error when the upload itself
failed — on providers without a files endpoint (or a transient upload
failure) the model was told the video could not be read at all. Now a
missing or failing upload falls back to delivering the video inline
(base64), the same shape providers without an upload channel already
get. Both engines (agent-core and agent-core-v2) are fixed.

* test: cover prompt video edge cases and failure paths

Stress coverage for the prompt video pipeline:

- agent-core uploadVideo RPC: extension/magic classification
  (.txt with video bytes accepted, extension trusted when magic is
  absent), exact 100MB boundary, directory and nonexistent paths
- TUI: mixed per-video outcomes (one inlined, one tag fallback),
  submission order behind an in-flight upload, queued video messages
  carrying final uploaded parts
- kap-server: per-video provider-id mappings, Range requests through
  the llm redirect, mapping persistence across a server restart

* fix: surface auth rejections from the video upload channel

The base64 fallback for a failing video upload must not mask auth
rejections: a 401/403 (surfaced as provider.auth_error) drives the
credential force-refresh and a clear auth error, while an inline
payload would just be rejected again by the next request. Only a
missing or broken upload channel (no files endpoint, network/server
errors) falls back to inline delivery.

* fix: keep the by-design no-hook video error from degrading to inline

Main's contract for a provider with no video upload hook is an honest
"does not support video upload" tool error — an inline payload would
be dropped on that protocol's wire anyway. The base64 fallback now only
applies to an upload channel that exists but failed at runtime. The
no-hook throw gets a stable type (VideoUploadUnsupportedError) so the
two cases are told apart without matching message text.

* fix: constrain the llm video route param to a safe alphabet

The provider file id is used as a blob-store key, and the node-fs
backend joins scope and key into a storage path. An id containing an
encoded path separator (%2F) could address a different storage path
than the intended llm-video mapping; the route now only accepts the
provider-id alphabet.

* style(agent-core-v2): move added explanations into top-of-file comment blocks

The package's comment convention keeps comments solely in the top-of-file
block; relocate the new notes (url-source id pairing, video delivery
fallback, VideoUploadUnsupportedError) from functions and schema fields
into their module headers.

* fix: reject prompt video uploads when the model lacks video input

The uploadVideo RPC only checked the provider's upload channel, so an
SDK caller on a text-only or unknown-capability model could obtain a
valid-looking video_url part the current model is not supposed to
accept. The TUI capability guard does not cover this public path, so
the agent now gates on video_in itself.

* fix: sniff uploaded video bytes before inlining them

The inline branch trusted the upload-time content type; now the bytes
are sniffed first, and anything magic-confirmed as a non-video kind
(e.g. an image mislabeled as video/mp4) falls back to the file-tag
form instead of being uploaded. Like the image gate, bytes are
authoritative where the container format allows it — an MPEG-PS
lookalike still rides the extension, matching ReadMediaFile.

* test: keep the generation stub pending until the abort lands

An immediately-answered 404 ends the turn (non-retryable) before the
test's abort call, racing the cleanup into a 409; the stub now hangs
generation until the abort cancels it.

* fix: validate prompt file references before mutating session controls

A stale file_id failed inside media resolution — after the model,
thinking and permission overrides had already been applied — so a
rejected prompt still changed the session's controls. File references
are now checked up front, keeping failed submits side-effect free.

* fix: serialize prompt submissions per session

A slow provider video upload let a later text-only request reach the
queue ahead of an earlier video one, silently reordering the
conversation for REST clients and multiple tabs. Submissions to the
same session now chain, matching the ordering the TUI already
guarantees locally.

* fix: play reloaded provider videos through the authenticated fetch path

A video recovered from an ms:// reference carried only the bare redirect
URL, which 401s under daemon auth when loaded natively. The attachment
now keeps the provider file id (llmFileId) end to end, and AuthMedia
fetches the bytes with the Bearer credential through the daemon's llm
redirect — the same blob-URL path uploaded files already use.

* fix: fence pending video submits against session and model switches

A slow paste-upload left the TUI idle, so /new, the session picker or
/model could fire mid-upload; the continuation then dispatched the old
session's provider reference into the newly selected session or a
model that cannot resolve it. The dispatch now re-checks that the
session and model are unchanged and asks for a resend instead.

* fix: preserve the caller's video path verbatim in uploadVideo

Trimming the path changed the filesystem target before validation and
upload, so a name that legitimately starts or ends with whitespace
resolved to the wrong file; the trim is now only the emptiness check.

* fix: forward provider-issued ids on image URL prompt parts too

The shared url-source schema accepts id for image and video parts, but
only the video path forwarded it, dropping provider-keyed image ids
between prompt acceptance and the model request.

* fix(web): reconcile inlined video echoes into the optimistic user message

The loose user-message matcher counted media parts and <video path>
tags but not the [video:ms://…] text shape, so a racing server echo of
an inlined upload slipped through as a duplicate user bubble.

* fix: queue bash submits behind a pending video upload

A bash-mode submit could start while a pasted video was still
uploading, recording shell context before the earlier prompt was
dispatched and reordering user actions; it now chains behind the
upload like normal submits.

* test: cast the driver through unknown for the session-switch fence test

* fix: validate prompt file references before resolving the prompt agent

A stale file_id posted to a fresh or cold session materialized the main
agent (registering it in session metadata and igniting agent-scoped
services) before the request was rejected. The file check now runs
first, so failed submits create nothing and mutate nothing.

* fix: key the playback mapping by the id embedded in the reference URL

Projections and clients read the provider id from the ms:// URL, not
the id field, so an upload that returns an id-less or mismatched
reference would inline fine but 404 on playback. The mapping now
derives its key from the URL, falling back to the explicit id.

* fix: sanitize provider video ids on the write side of the playback map

The read route got the safe-alphabet guard, but recordLlmVideoRef still
used the provider-returned id verbatim as a blob-store key; a crafted
provider response could write the mapping outside the llm-video
namespace. Out-of-alphabet ids are now dropped on both put and get.

* fix(web): keep recovered provider videos resendable through the edit path

The composer reload path only honored fileId and fell back to a
bare fetch(url) without the Bearer token, so editing a reloaded
provider-video turn dropped the chip after a 401. llmFileId is now
threaded through and the bytes are re-uploaded via the authenticated
llm redirect.

* fix: fall back to inline video for no-hook providers whose wire carries it

The by-design no-hook error is only the honest answer when the wire
would drop an inline payload anyway (the OpenAI family). Protocols
that convert video_url (kimi, anthropic, google-genai, vertex) now
take the base64 fallback instead of failing every video read; the
registrar computes the flag from the model's protocol.

* test: satisfy the full IBlobStore shape in the playback-map stub

The tsgo typecheck job rejects a structural stub missing _serviceBrand
and list even though plain tsc accepted it.

* fix: queue prompt-producing slash commands behind pending uploads

Skill activations and plugin commands started their turn immediately
while a pasted video was still uploading, so the earlier video prompt
queued behind them and user actions ran out of order. Both paths now
chain behind inputSubmitChain (and re-check session/model at dispatch)
like normal and bash submits.

* fix: validate media kinds in the prompt file-reference preflight

The preflight only proved a referenced file exists; a real upload used
with the wrong kind (e.g. a PDF submitted as video) still passed it and
mutated session controls before assertMediaFile rejected the request.
The kind assertion now runs up front with the existence check.

* fix(web): play sent and recovered videos in the file preview

The media preview returned early for every kind except image, so a
user-turn video chip's play action was a no-op even with llmFileId
threaded through. The preview now handles video: bytes come from the
authenticated file/llm fetch into a blob URL and render in a native
player.

* fix(web): preview recovered videos with the authenticated blob URL

The llm re-upload branch fetched bytes with auth but kept the protected
redirect URL as the chip preview, which 401s as a native video src; the
fetched blob now becomes the preview URL, mirroring the fileId branch.

* style(agent-core-v2): move the inline-fallback note into the module header

Same package comment convention as before: rationale lives in the
top-of-file block, not beside the registration call.

* fix: fence delayed bash submits to the originating session

The chained bash callback ran runShellCommandFromInput against whatever
session was active at dispatch time, so a command submitted in session
A could execute in session B's workspace and be recorded there after a
mid-upload switch. The originating session is now captured at submit
and re-verified at dispatch, like the prompt and skill paths.

* fix: emit prompt video telemetry from the agent scope

video_upload is an agent-level event requiring ambient agent identity,
but the uploader was built with the Core-scoped session view, leaving
prompt-upload events unattributable. The route now resolves telemetry
from the target agent for the uploader while image compression keeps
the session-scoped view.

* fix: preserve provider image ids through legacy projections and web mappers

The url-source id accepted by the prompt schema was dropped again by
the legacy message projection and the web wire mapper, so provider-
keyed image references lost their id across messages, snapshots, and
undo responses. It now flows through both directions.

* fix(web): revoke recovered video blob URLs before dropping attachments

A failed llm re-upload removed the attachment without revoking the
freshly created preview blob URL, pinning the whole video in the
browser blob store until page unload on every failed edit attempt.

* fix: serialize foreground slash commands behind pending uploads

/compact and /init started their turn while a pasted video was still
uploading, so the earlier message landed after them — and compaction
summarized the context without it. Prompt-producing builtin commands
now share the same queueBehindPendingUploads chain (with the
session/model dispatch fence) as skill, plugin, and bash submits.

* fix: defer session controls until media preparation succeeds

Media resolution now runs before any profile/model/thinking/permission/
denylist mutation, with the uploader resolved transiently from the
requested (or currently bound) model — a failed submission leaves the
session's controls untouched. A concurrent model switch during
preparation is rejected with session.busy instead of enqueueing a
reference uploaded for the previous model.

* chore: bump the new SDK video upload API as a minor release

Session.uploadVideo is new public API surface, not a patch-level tweak.

* fix: re-check busy state before draining upload-queued commands

A slash command deferred behind a video upload ran the moment the video
prompt dispatched, landing on an already-running turn: beginSessionRequest
wiped the active turn's live pane, and /init or /compact started on top
of it. The deferred callbacks for skills, plugin commands, /compact and
/init now re-run the resolver's busy check at dispatch and show the same
blocked message the user would get when typing while streaming.

* fix: resolve profile-bound models before choosing the uploader

A first prompt carrying "profile" without "model" resolved the upload
model from the still-unbound alias, so no uploader was installed and
every attached video fell back to a tool-read tag even when the
configured default model supports provider upload. The transient
resolution now mirrors AgentProfileService.bind: an explicit body model
wins, a profile bind falls back to the configured default model, and
only otherwise does the currently bound alias apply.

* refactor: resolve prompt videos at request time inside the engine

Move prompt-video delivery out of the submission edge: the TUI submits
synchronously with a local file:// part the v1 turn resolves before the
message enters history, and kap-server carries an internal kimi-file://
reference the v2 requester resolves against the effective model with an
app-scoped upload cache. History keeps the durable local file id, the
/messages projection emits structured video parts, and the web plays
videos back through the authenticated /files channel - deleting the
submit fences, per-session serialization, provider-id reverse mapping,
redirect endpoint, and the unreleased SDK upload API.

* fix: propagate abort through video upload delivery instead of degrading

A turn cancelled mid-upload used to be treated as an ordinary upload
failure: v1 fell back to an inline base64 part and appended the degraded
message to history, and the v2 resolver memoized the tag fallback for the
rest of the agent's lifetime. Both catch sites now check the delivery
signal itself - abort rejections vary in shape by provider - and re-throw
so cancellation ends the turn (v1, classified as cancelled via the abort
reason) or the request (v2, not memoized, so the next turn uploads).

* fix: keep the tag form for no-upload providers whose wire drops inline video

An OpenAI-family model configured with video_in but no provider upload
channel used to receive prompt videos as an inline base64 part - which
chat completions rejects and the Responses adapter degrades to an
omitted-video placeholder, persisting ~4/3x the file size in history for
bytes the model never sees. The prompt path now mirrors the v2 resolver's
protocol gate and degrades to the <video path> tag instead; ReadMediaFile's
own delivery is unchanged. Also merges the prompt-video changesets into a
single user-facing entry.

* fix: escape the NUL separator in the video upload cache key

The cache-key template literal contained a literal NUL byte instead of
the \0 escape, which made Git classify the whole source file as binary
- no inline diffs, unreliable text tooling. The escape produces the
byte-identical runtime string, so hashed cache keys are unchanged.

* fix: check the abort signal before the inline video fallback

The no-uploader inline path (and the post-upload-failure fall-through)
never consulted the delivery signal, so cancelling a turn while the video
bytes were being read still base64-encoded the file and appended the
degraded message to history. The inline branch now re-throws the abort
reason first, matching the upload catch.

* fix: retry transient prompt video upload failures on later steps

A generic upload failure used to memoize its tag fallback for the rest of
the agent's lifetime, freezing a transient files-endpoint error into a
permanently degraded video. The resolver now marks failure-born fallbacks
as non-memoizable: the current request keeps the lightweight tag form and
the next step retries the upload. Structural outcomes (successful uploads,
capability and sniff fallbacks, no-hook inline) stay memoized for
step-retry stability.
2026-07-22 13:46:00 +08:00
Kai
b5efba7abc
fix: consume the model metadata declared by the models.dev catalog (#2015)
* fix: stop advertising Claude thinking efforts for non-Claude models

Models served over the Anthropic protocol whose names carry no Claude
marker (e.g. a catalog-imported Kimi K3) no longer inherit the latest
Opus effort list, so the model selector stops offering levels the model
does not accept. The models.dev catalog import now also parses
reasoning_options and records the declared effort levels on the model
alias, so K3 offers its real levels (low / high / max).

* fix: consume deprecated, override, and input-limit metadata from the models.dev catalog

- Models declared status=deprecated in the catalog are no longer
  offered for import.
- Per-model provider overrides on gateway providers (an npm package
  targeting an Anthropic SDK plus a usable endpoint) now land as alias
  protocol and base_url, so those models are served over the right
  protocol and endpoint; overrides without a usable URL are skipped.
- A declared limit.input now sizes the context budget instead of the
  larger total context window (e.g. gpt-5: 272k instead of 400k).

The model alias schema gains an optional base_url field (not accepted
in overrides) that Anthropic wire resolution prefers over the
provider-level base URL.

* fix: honor thinking-disable semantics and the OpenAI-compatible fallback in catalog imports

- reasoning_options 'none' is the model's off encoding: off_effort flows
  from the catalog through the model alias to the OpenAI wire providers,
  so turning thinking off sends 'none' instead of omitting the effort
  field; models with effort levels but no way to disable thinking are
  imported as always_thinking and no longer offer an Off option.
- Bare Claude family aliases (e.g. sonnet-latest) recover the inferred
  Anthropic effort profile; v2 comment conventions restored.
- Providers whose SDK the catalog does not type now fall back to the
  OpenAI-compatible wire (with a visible "guessed" note) instead of
  being refused; imports lacking a usable endpoint ask for one
  (--base-url on the CLI, a prompt in the TUI). Proprietary SDKs
  (Amazon Bedrock), unrecognized explicit types, and env-placeholder
  URLs are refused with a clear reason.

* fix: align catalog imports with the reference models.dev consumer

- A JSON null tier in declared effort values is now read as the 'none'
  off-encoding (previously such models were wrongly imported as
  always-thinking with no way to turn reasoning off).
- Alpha-status models are filtered out alongside deprecated ones.
- Models whose per-model provider override targets a wire that cannot be
  expressed per-model (e.g. Claude on google-vertex, whose wire here is
  Gemini-mode Vertex, or gpt entries on an Anthropic provider) are
  skipped instead of being imported under the silently wrong protocol.
- interleaved: true no longer pins reasoning_content: the provider's
  default three-field scan is wider and the pinned key only narrowed
  reasoning parsing for gateways answering with another field name.

* fix: require endpoints for Anthropic-compatible catalog imports and honor --base-url

- catalogProviderNeedsBaseUrl now covers the Anthropic wire: a
  non-official Anthropic-compatible vendor without a concrete catalog
  endpoint (e.g. google-vertex-anthropic) must supply --base-url / the
  TUI prompt instead of silently falling back to the default Anthropic
  endpoint.
- --base-url now takes precedence over the catalog-declared endpoint,
  and an empty --base-url is rejected instead of persisting a blank
  endpoint.

* fix: enforce always-on thinking on every wire and refuse Cohere at import

A model that declares always_thinking (e.g. a catalog-imported gpt-5)
no longer resolves to a dishonest off state via thinking.enabled=false
or an SDK/ACP off request: resolution clamps to the model's default
effort on every wire instead of letting upstream keep reasoning while
the UI reports Off. The Anthropic warn-and-send path for unlisted
effort levels is unchanged. Cohere's proprietary SDK joins Amazon
Bedrock on the import-refusal list instead of being guessed as
OpenAI-compatible.

* fix: harden catalog import edge cases

- An explicit but unrecognized catalog type is now refused before
  npm/id inference, so a future catalog protocol is never silently
  miswired through the OpenAI fallback.
- User-supplied --base-url values for Anthropic-wire providers get the
  same trailing-/v1 normalization as catalog endpoints, avoiding
  /v1/v1/messages requests.
- The TUI import prompt rejects env-placeholder base URLs like the CLI
  does.

* fix: await the floating assertion promise in the catalog add CLI test

* refactor: unify catalog import resolution into a single decision function

Wire-type inference, the OpenAI-compatible fallback, proprietary-SDK
refusal, endpoint adaptation, and the base-URL requirement are now
produced together by resolveCatalogImport, one pure resolver consumed
by both the CLI and the TUI — replacing the cooperating predicates
(inferWireType, isGuessedWireType, catalogProviderNeedsBaseUrl) whose
permutations kept producing edge cases. No behavior change.

* fix: close configured-off clamp hole, keep inferWireType compat, carry same-wire override endpoints

- A configured thinking.effort = "off" no longer bypasses the
  always-on clamp: it is treated as absent and the model default
  applies, mirrored on both engines.
- The previously public inferWireType stays as a deprecated
  compatibility wrapper over resolveCatalogImport so existing SDK
  consumers do not break on a patch release.
- Catalog model overrides that stay on the provider's wire but declare
  their own endpoint now persist it on the alias (and the v1 OpenAI
  wire branches honor alias-level base URLs like the Anthropic branch).

* fix: split total window from input cap and close override/endpoint gaps

- max_context_tokens once again means the total context window (used by
  completion budgeting); a model's declared input limit is tracked as
  max_input_tokens, which compaction, context-splice and usage-ratio
  checks prefer — fixing the over-clamping introduced when the input
  cap was stored as the context budget.
- A catalog endpoint declared only as an env placeholder now always
  produces needs-base-url (official SDK included), so credentials are
  never sent to the public vendor host by default.
- api-only per-model overrides are honored as same-wire endpoint
  changes; overrides targeting another known but inexpressible wire
  (e.g. google-genai on an OpenAI gateway) are skipped; same-wire
  models whose declared endpoint is an unusable placeholder are
  skipped instead of silently rerouted.

* style: drop a function-level comment from the v2 thinking resolver

* chore: consolidate the PR's changesets into two user-facing entries
2026-07-22 01:23:31 +08:00
liruifengv
576d650380
feat(cli): add third-party source note to update prompt (#2014)
* feat(cli): add third-party source note to update prompt

* fix(cli): adjust third-party source note wording

* refactor(cli): move official install URL into app constants

* chore: simplify changeset wording

* chore: adjust changeset wording
2026-07-21 20:42:23 +08:00
liruifengv
e45832398d
fix(tui): keep long sessions responsive and bound resumed history (#1976)
* fix(pi-tui): reuse processed lines across frames in the renderer

Each frame previously re-truncated, re-normalized, and re-compared every
transcript line, so steady-state frames (spinner ticks, streaming flushes)
cost O(total lines x chars) and pegged one core in long sessions. Keep the
previous frame's raw lines, processed output, and per-line kitty image ids;
a line whose raw string reference is unchanged reuses its processed output
verbatim, and image-id consumers read the cache instead of re-scanning text.

* fix(tui): stop tree-wide transcript invalidation on structural updates

Grouping a second Read/Agent call, removing swarm progress, finalizing an
MCP status row, replay tool-call removal, and /undo each invalidated the
entire transcript tree, forcing every mounted message to re-render (markdown
lexing + code highlighting) on the next frame. The container's render cache
already validates per child by reference, so structural child-list changes
are picked up without a tree-wide invalidate; reserve it for global style
changes such as theme switches.

* fix(tui): fold older assistant messages into the turn step summary

Step merging only collapsed thinking/tool steps, so assistant text blocks
accumulated without bound inside a turn (hundreds of markdown components in
a single long turn, all re-processed every frame). A running turn now keeps
its last 20 assistant messages (KIMI_CODE_TUI_KEEP_RECENT_ASSISTANT), and a
finished turn folds down to its conclusion tail of 2
(KIMI_CODE_TUI_KEEP_RECENT_ASSISTANT_COMPLETED); older ones collapse into
the step summary line with a message count. Entries are kept, so expand
behavior is unchanged.

* fix(session): bound resumed history to the most recent user turns

Resume used to return every agent's full replay over the RPC boundary (a
long session can reach ~100MB, serialized and parsed several times on an
in-process call), while the TUI only renders the last 10 user turns. The
resume payload now accepts an optional replayTurnLimit, the turn-boundary
predicate moves into agent-core as the single source of truth
(limitAgentReplayByTurns, re-exported through the SDK), and the CLI passes
its existing 10-turn limit so resume transfers just the tail.

* fix(session): count goal continuation rounds as replay turns

Replay turn boundaries only matched real user input, so a 100-round goal
(a handful of user prompts plus 100 system-trigger continuations) fell
entirely inside the 10-turn replay window and resumed by rendering the whole
run from the start. The goal driver already fires one synthetic prompt per
goal turn and counts those as turns itself, so replay trimming now treats
goal_continuation prompts as turn boundaries and keeps the most recent 10
rounds. The continuation prompt is model-facing and hidden live; replay no
longer renders it as a user bubble either, while still advancing the replay
turn so each round groups separately.

* fix(tui): keep replay turn folding when goal continuations are hidden

Suppressing goal continuation bubbles removed every turn-boundary
component from goal-session replays, so mergeAllTurnSteps found no turn
edges and skipped step/assistant folding entirely — a single oversized
goal round still mounted all of its tool cards and assistant messages.
Mount an invisible ReplayTurnBoundaryComponent for each hidden
continuation: it renders zero lines but keeps the turn edges discoverable
to folding and window trimming, matching replay trimming's per-round turn
semantics.

* chore: consolidate and simplify changesets
2026-07-21 15:20:12 +08:00
7Sageer
ce0e3ceb04
feat: support custom agent files (#1735)
Some checks are pending
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 (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
* feat: support custom agent files

Discover Markdown+frontmatter agent definitions from user, project, and configured directories; merge them into a session-level profile catalog (priority: builtin < user < extra < project < explicit). Custom agents work as subagents via the Agent tool and as the main agent.

- agent-core-v2: new agentFileCatalog domain (discovery/parsing/profile factory, mirroring skillRoots/parseFrontmatter) and sessionAgentProfileCatalog merged view; AgentProfile.tools becomes optional (undefined = all tools) and gains disallowedTools deny list, evaluated in profileService.isToolActive and persisted in session wire records so resume keeps the gate even if the file is gone
- CLI: restore --agent/--agent-file for the v2 print runner (KIMI_CODE_EXPERIMENTAL_FLAG); the v1 TUI rejects them with a clear v2-only error
- kap-server/protocol: optional profile field on prompt submission with first-bind semantics (same-name no-op, different name rejected)
- docs: custom agents section (en/zh) + config/CLI references + changeset

* chore: shorten custom agents changeset

* fix(agent-core-v2): stringify caught errors in agent catalog log calls

* docs: drop v2 engine notes from custom agents docs

* fix: align custom agent binding semantics across engine and edges

- agent-core-v2: bind now owns the first-bind invariant — switching
  profiles after bind throws profile.already_bound (checked again in the
  synchronous segment before the first wire dispatch, so concurrent binds
  cannot both pass); unknown names throw profile.unknown; same-name
  rebinds keep the persisted thinking effort.
- kap-server / CLI: edges degrade to error mapping / same-name no-op
  instead of their own divergent guards.
- agent files: reject non-string mode values, honor disallowedTools in
  the append-mode Skill probe, pass --agent-file through unresolved so
  the engine can expand ~, reject empty --agent-file values.
- session catalog: ready is recoverable via reload() after a fatal
  source failure, and agent-file discovery is kicked at session
  materialize so resumed sessions see file agents from the first turn.
- docs: first-bind semantics, name: agent override, tools: [] meaning,
  --agent-file last-wins.

* fix: tighten custom agent behavior

* fix: address custom agent review findings

* fix(agent-core,agent-core-v2): keep active-tool wire records replayable by v1

Binding a profile without a tool allowlist (the default for file-defined
custom agents) persisted a tools.set_active_tools record with no `names`
key. v1 clients discover v2 sessions through the shared session index and
replay newer wire versions without migration, so the record crashed v1
resume with a TypeError and wedged the session permanently.

The v2 engine no longer writes the record when the base set is already
"every tool active" (its absence encodes the same state), and v1 replay
now skips records that lack `names` as defense in depth for wires already
written by preview builds. A same-name rebind that resets an allowlist to
"all tools" has no v1-safe encoding and is left as a documented gap (no
production caller today); a future tools.reset_active_tools Op is safe
because v1 silently no-ops unknown record types.

* fix(agent-core-v2): tolerate unreadable directories in agent-file discovery

A single unreadable subdirectory (EACCES anywhere in the scanned tree)
previously aborted the whole discovery pass, zeroing every agent of that
source on every session start with one path-less warning. The walker now
skips-and-warns per directory below the root (mirroring the skill
discovery it parallels), root-level failures are isolated per root so one
bad root no longer takes its siblings down, and only a genuinely transient
whole-fs outage (os.fs.unavailable) still propagates so the session
catalog keeps its previous contribution. Source-level warnings now name
the offending path, and repeated skip warnings are capped with a summary
that samples the suppressed paths.

Also consolidates the path primitives (~ expansion, base-relative
resolution, realpath type probes) shared by the root resolvers, the
walker, and the explicit-file source into agentFileCatalog/paths.ts, and
tightens parser diagnostics: frontmatter null is treated as absent, and a
present-but-wrong-typed name/description reports a type error instead of
"missing".

* fix(agent-core-v2): warn when a same-name builtin suppresses a file profile

A directory-discovered agent file colliding with a builtin profile
without override: true was silently dropped at merge time. The suppression
now logs a warning naming the profile and the opt-in.

* refactor(agent-core-v2): pass skillActive explicitly to renderSystemPrompt

The third parameter was a full tool list used only for includes('Skill'),
which forced the agent-file profile factory to answer a boolean question
with sentinel lists. The template now takes an explicit skillActive flag;
a skillActiveFor helper keeps builtin call sites derived from their tool
arrays.

* refactor(agent-core-v2): fall back to the configured default model in bind

BindAgentInput.model is now optional: the engine resolves a missing model
against the configured defaultModel and throws model.not_configured when
neither is set, so edges no longer each re-implement the fallback.

* fix(agent-core-v2,kap-server): reject unsupported thinking atomically at first bind

A REST prompt carrying profile + an unsupported thinking effort bound the
session first and failed setThinking after, wedging the session on an
identity the user never successfully used. The effort is now validated up
front when the caller marks it as an explicit request (strictThinking):
the bind rejects before any await or state mutation, and the requested
effort rides along in the bind instead of a separate setThinking. Internal
spawn/fork paths pass inherited thinking without the flag and keep the
previous clamp behavior — a persisted effort that drifted out of the
model's support list must not break subagent spawning. The route's
now-redundant model fallback is dropped in favor of the engine-side
default.

* fix(agent-core-v2): await the agent profile catalog at session materialize

The catalog's ready promise was only kicked, so a resumed session's first
turn could render the Agent tool description without the file-defined
agent types. Discovery is local-fs and cheap, so materialize now awaits
it; ready only rejects for a fatal explicit-source error, which is exactly
the case that should fail fast. A failure there now also removes and
disposes the half-materialized handle instead of leaving it registered in
the session cache.

* test: cover the --agent-file fatal path and tidy profile registration hygiene

The v2 print CLI now has a test asserting an invalid --agent-file fails
before any turn. The denylist profiles in binding.test.ts register in a
beforeAll (idempotent, scoped to the describe's run window) instead of at
module scope during collection.

* docs: align custom agent docs with v2-engine gating

--agent/--agent-file are rejected without the v2 engine, so restore the
requirement in the Agents and Command Reference pages (and use
KIMI_CODE_EXPERIMENTAL_FLAG=1 in the examples), note that tool lists only
shape model-visible disclosure (permission rules are the enforcement
layer), and remind authors of delegation-bound agents to state the
handoff contract in the prompt body.

* test(agent-core-v2): revert unrelated style churn in fs/workspace tests

Keep these files' diff limited to the realpath fakes the feature needs;
the lint-preference rewrites belong to a separate cleanup.

* feat(agent-core-v2): add permanent system prompt override via SYSTEM.md

Read $KIMI_CODE_HOME/SYSTEM.md on every startup and inject it as the default main-agent profile (name "agent", override: true), replacing the builtin default system prompt while inheriting builtin tools and description. Missing or empty files are ignored; unreadable files warn and fall back to the builtin profile.

The body supports variable substitution (${skills}, ${agents_md}, ${cwd}, ${cwd_listing}, ${os}, ${shell}, ${now}); unknown variables pass through verbatim.

Priority: --agent-file / --agent / project override > SYSTEM.md > same-name user-scope scan files.

* feat(agent-core-v2): gate tools globally and accept session disabledTools

Add a [tools] config section: "enabled" acts as a global allowlist (empty = unconstrained), "disabled" as a denylist applied on top, both intersected with the active profile's policy in isToolActive (mcp glob supported).

Plumb a session-persistent disabledTools parameter through the stack: v2 RPC PromptPayload, REST "disabled_tools" (protocol and kap-server parallel schemas), klient contract/facade, and node-sdk. The server applies it via profileService.setSessionDisabledTools, which replaces the client-owned denylist, keeps the profile's own deny, persists across resume, and rejects calls before a profile is bound with profile.not_bound (mapped to 40001). v1 core-api gains a type-only field and ignores it.

* fix: enforce session tool policy across agents

* fix(agent-core-v2): enforce tool policy at execution

* fix(agent-core-v2): align subagent tool descriptions with policy

* fix(agent-core-v2): harden custom agent policy state

* fix(agent-core-v2): harden custom agent lifecycle

* refactor(agent-core-v2): persist profile binding in a single profile.bind record

* fix(agent-core-v2): skip unreadable paths during agent file discovery

* fix(agent-core-v2): exempt select_tools from the executor policy guard

- share one composed profile/global/session tool-policy evaluation between
  the executor gate and prompt rendering instead of two verbatim copies
- tolerate context-build failure in system prompt refresh instead of
  rejecting callers (config watcher void-fire, session policy fan-out)

* test(agent-core-v2): resolve profile and tool-policy SUTs by interface

- drop the Object.assign patching of tool-policy methods onto the shared
  profile service; rename describes so the SUT ownership is accurate
- classify profile.bind as v2-only with the accepted v1-replay tradeoff
  documented, un-red the wire vocabulary guard test
- cover the select_tools guard exemption with an executor-level test

* fix(agent-core-v2): enforce explicit select_tools policy

* feat(agent-core-v2): accept Claude-style tool lists and rename agent-file mode to promptMode

* feat(agent-core-v2): unify prompt templating on ${var}

- Replace the nunjucks renderer with a single ${var} regex renderer
  (unknown placeholders pass through verbatim) and drop nunjucks from
  agent-core-v2.
- Merge the variable tables into one catalog shared by the builtin
  system.md, SYSTEM.md, and agent file bodies; adds additional_dirs_info
  plus code-composed blocks (windows_notes, additional_dirs_section,
  skills_section).
- Replace the agent-file promptMode field with ${base_prompt}: bodies
  are always rendered as templates, and ${base_prompt} expands to the
  effective default profile prompt (honoring the SYSTEM.md override).
- Migrate the builtin system.md, goal reminders, compaction instruction,
  and tool description templates to the same syntax.

* docs: complete agent priority chain and link SYSTEM.md precedence

* test: fix invalid custom agent fixture

* fix(cli): reject multiple agent selectors

* feat(agent-core-v2): add subagents allowlist to agent files

* fix(agent-core-v2): persist the subagent allowlist in the profile binding

The delegation allowlist now rides the profile.bind record like the tool
denylist, so a resumed session keeps enforcing it even when the source
agent file was deleted or changed. Agent/AgentSwarm resolve the caller's
allowlist from the persisted binding data instead of looking the profile
up in the live catalog.

* feat(agent-core-v2): warn on tool patterns that never match

Profile bind/apply and [tools] config changes now statically flag
entries that can never activate anything — wildcards without the mcp__
prefix (a bare * in an allowlist disables everything, in a denylist
nothing), incomplete mcp__ literals, and names no registered or
builtin-profile tool has — via a tool-pattern-no-match warning event,
once per pattern, instead of letting the tool set silently shrink. The
known-name vocabulary is the live registry plus literal names from the
builtin profiles, so flag-gated tools stay known and a typo in one agent
file cannot legitimize the same typo in another.

* docs: align --agent-file docs with the single-selector CLI

The flag accepts exactly one file and conflicts with --agent, but the
docs still described the earlier repeatable, composable design.

* docs: note the agent-file trust model and never-matching tool patterns

Spell out that project-scoped agent files can replace the default main
agent's whole system prompt (unlike AGENTS.md reference injection), and
list the three tool-pattern shapes that never match and now raise a
warning.

* chore: slim changeset wording to user-facing language

Drop wire record names, enforcement mechanics, and template syntax from
the entries; split the v1 resume fix into its own patch changeset; add a
patch entry for the tool-pattern warnings.

* chore: shorten changeset entries to one-line summaries

* Delete .changeset/v1-resume-v2-sessions.md

Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>

* test: drop class-instance spread in sessionLifecycle test stub

* chore: clear the comments

---------

Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
2026-07-21 15:01:43 +08:00
liruifengv
73eb5f89e0
fix(tui): remove red coloring from code syntax highlighting (#1995) 2026-07-21 12:54:47 +08:00
liruifengv
115b0968ce
fix(tui): hide system-trigger messages in resume replay (#1990)
Goal mode stores its per-turn continuation prompt as a user message with
a system_trigger origin. Live rendering never shows these model-facing
messages, but resume replay only filtered three specific system_trigger
names, so the continuation prompt leaked into the transcript as a user
message. Skip all system_trigger user messages during replay, matching
the live path and the markdown exporter, and drop the two goal-specific
filters this makes redundant.
2026-07-21 12:16:46 +08:00
github-actions[bot]
efacf0452d
ci: release packages (#1946)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-20 22:57:20 +08:00
Haozhe
ad8cc85251
feat(cli): foreground-only web servers, deprecated kimi server kill (#1967)
- the /web slash command now always starts a new server instead of offering to reuse a running one
- remove the kimi web kill and kimi web ps subcommands; foreground servers stop with Ctrl+C
- keep kimi server kill as a deprecated fallback that only stops servers started by a version before 0.28.0 (recorded in the legacy server lock)
2026-07-20 21:50:09 +08:00
liruifengv
dde92fe5ab
feat: support max and exact version targeting for tips banner (#1960)
* feat: support max and exact version targeting for tips banner

Add optional banner_max_version (exclusive upper bound) and banner_version (exact match) fields to the remote tips banner schema. They combine with banner_min_version (all must pass) and apply to both the active banner and fallback list entries. Invalid semver constraints fail closed and hide the banner.

* chore: drop changeset for banner version targeting
2026-07-20 20:08:52 +08:00
github-actions[bot]
a05228c671
ci: release packages (#1868)
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
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-20 13:55:49 +08:00
liruifengv
11c1683a1c
feat: scope thinking effort to the current session (#1933)
* feat: scope thinking effort to the current session

Selecting a model or thinking mode in the TUI or web UI no longer
persists the concrete effort to the config; only the boolean thinking
toggle is saved. The web UI now restores and submits each session's own
daemon-reported level instead of a browser-wide per-model pick, whose
localStorage store is removed.

* feat: persist bounded thinking efforts and migrate persisted max once

Only low/medium/high/xhigh are written to the config on a pick; max and
unrecognized levels stay session-only with just the boolean toggle saved.
A one-shot migration rewrites a previously persisted thinking.effort max
to high (recorded in migrations.json, never re-run). The web UI brings
back the per-model localStorage pick as the seed for new sessions, while
a session's own daemon-reported level keeps winning for existing ones.

* feat: gate effort persistence on the model top declared level

The session-only tier is now the last entry of the model's own
support_efforts (ordered by strength) instead of a fixed name list, so
custom provider-declared levels get the same treatment; anything below
the top persists as before, and unknown model metadata keeps the
concrete effort.

* docs: align changeset wording with the top-tier persistence rule

* refactor: rename the config migration marker file to migrations-effort.json

* feat: drop the legacy global thinking pick fallback in the web UI

A raw single-level string left in localStorage by older versions was
migrated into a '*' fallback entry applied to every model; stale values
(typically max) could silently steer any session. Non-map content is now
discarded instead of inherited.

* feat: drop the web per-model thinking pick store entirely

Thinking in the web UI is now sourced only from the daemon: a session's
own reported level wins when the model declares it, and everything else
falls back to the model's catalog default. The stale localStorage key is
cleaned up on startup.

* feat: skip the effort write when the picker confirms its initial value

Re-confirming the effort shown when the model/effort picker opened is not
an explicit choice: the TUI persists only the model (no effort key, or no
write at all when nothing changed), and the web skips the global config
write the same way it already did for model switches.

* revert: keep the web global thinking write on re-confirm for now

Only the TUI skips the effort write when the picker confirms its initial
value; the web behavior stays unchanged until the interaction is
reconsidered.

* fix: carry the draft thinking pick into a newly created session

A level picked on the empty composer lived only in rawState.thinking, so
selectSession's watcher overwrote it with the catalog default and the
first prompt/skill submitted that instead of the pick. createDraftSession
now captures the draft level and seeds the new session's own entry.

* docs: simplify the changeset wording

* fix: close the remaining top-tier persistence and draft-capture gaps

The provider-add default-model flow now resolves support_efforts through
effectiveModelForHost (overrides + protocol-profile inference), so a
top-tier pick on catalog models without declared efforts no longer
persists. The draft thinking level is captured before the session
creation awaits, so a concurrent session switch can no longer seed the
new session with another session's effort.

* fix: wait for the session status fold before resolving prompt thinking

In the cold window right after a reload or session switch, the session's
own level has not landed from /status yet; resolving straight to the
catalog default would carry the wrong level to the daemon, which writes
it into the session profile. Send, steer, side-chat and skill-activation
paths now await the fold when the session entry is missing.
2026-07-20 12:57:22 +08:00
Haozhe
a41a09c33c
feat(cli): replace the kimi server command tree with kimi web and share one home across servers (#1826)
Some checks are pending
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (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-pi-tui (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): enable multi-server shared home by default

- always register kap-server instances under server/instances and drop the
  legacy single-instance lock (acquireLock/getLiveLock/ServerLockedError)
- remove the multi_server experimental flag and its
  KIMI_CODE_EXPERIMENTAL_MULTI_SERVER env var from agent-core-v2
- discover running servers via the instance registry in server
  ps/kill/rotate-token, kimi web daemon reuse, and the desktop app
- remove the pending minidb changesets

* feat(cli): add per-instance targeting to server kill and ps

- `kimi server kill [serverId]` stops only the matching instance; without
  an id it still stops the longest-running one, and an unknown id errors
  with the live server ids listed
- `kimi server ps` lists connections grouped per server id (`--json`
  nests them under a per-server object); an unreachable instance degrades
  to a per-server note instead of failing the whole listing
- update the zh/en command reference and the multi-server changeset

* feat(cli): replace kimi server with the kimi web command tree

- `kimi web` now runs the local server in the foreground and opens the
  browser; the background daemon (ensureDaemon / spawn / idle-exit) is
  removed, so repeated runs simply start another instance on the next
  free port
- drop the OS-service lifecycle (install/uninstall/start/stop/restart/
  status) together with kap-server's svc layer
- `kimi web kill [serverId]`, `kimi web ps`, and `kimi web rotate-token`
  manage instances from the registry
- the TUI /web command now connects to an already-running instance
  instead of spawning a background daemon
- update the zh/en command reference, dev scripts, and tests

* feat(cli): route kimi server invocations to a deprecation notice

Any `kimi server …` call — bare or with any legacy subcommand/flags —
now prints a deprecation notice pointing at `kimi web` and exits 1,
instead of failing with an opaque "unknown command". The shim is
scheduled for removal in the next major version.

* feat(cli): add the `all` keyword to kimi web kill

`kimi web kill all` stops every live instance in the registry (ULIDs
can never collide with the keyword). Each instance still gets the API
shutdown + SIGTERM/SIGKILL treatment; a failure on one instance does
not stop the sweep and is reported at the end.

* docs(changeset): drop the web-foreground-default changeset

The kimi web command tree replaces the foreground-default behavior this
entry describes: --background, daemon reuse, and the version-mismatch hint
no longer exist, so the pending entry would contradict the actual release
notes.

* docs(changeset): tighten the multi-server entry wording

* feat(cli): let /web pick a running server or start a new one

The /web picker now lists the live instances from the registry with
their versions (flagging a CLI mismatch) instead of only connecting to
the longest-running one, and offers starting a new server: that one
runs in the foreground attached to the terminal after the TUI exits,
via the restored exit-takeover wiring. formatReadyBanner is exported
and adapts its Stop hint to Ctrl+C for the attached case.

* feat(cli): skip the /web picker and start a new server when none is running
2026-07-19 11:43:46 +08:00
qer
4f3c7240c4
feat(cli): run kimi web and the TUI /web command in the foreground by default (#1853)
Some checks are pending
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
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(cli): run kimi web and the TUI /web command in the foreground by default

kimi web and /web now start the server attached to the terminal (Ctrl+C
stops it) instead of backgrounding a daemon; --background opts back into
the daemon behavior. kimi server run is unchanged. A foreground kimi web
reuses an already-running server instead of failing to bind, the /web
handoff prints the same ready banner as kimi web (plus the session deep
link), and the banner Stop hint adapts to the hosting mode.

* fix(cli): resolve the server token after the server is up in /web

The token lookup had moved ahead of ensureDaemon()/startServerForeground()
during the foreground-default refactor, so a first-time start (no
server.token on disk yet) opened the browser without the #token= fragment
and left the user at the auth gate. Resolve the token after the daemon is
healthy, and inside the foreground ready hook.

* docs(changeset): flag the web foreground default as breaking in the changelog

The bump stays minor, but the entry now calls out the behavior break and
the --background mitigation for scripts that expect kimi web to return
immediately.

* feat(cli): flag a version mismatch when reusing a server from an older CLI

After an upgrade, an older still-running server is reused as-is. The
lock's host_version is now surfaced: kimi web / server run print a
version-mismatch line next to the reuse notice, /web shows the same as a
status warning, and the docs plus changeset tell users a single
kimi server kill switches them onto the new version.
2026-07-18 23:32:41 +08:00
Kai
3086e47039
fix: unify YOLO and Auto permission mode descriptions across surfaces (#1867)
Some checks are pending
CI / test-windows (push) Waiting to run
CI / lint (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 / 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 / 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(tui): correct YOLO and Auto permission mode descriptions

* fix: unify YOLO and Auto permission mode descriptions across CLI, ACP, web, and docs

* docs: correct YOLO and Auto mode descriptions in the interaction guide

* fix: correct YOLO mode notices in session replay and vscode extension

* feat(vscode): rename /afk command to /auto, keeping afk as hidden alias

Also correct the stale 'afk' mode reference in the built-in MCP config
skill guidance of both agent engines.

* fix(vscode): forward engine approval requests instead of blanket-approving them

The extension-level approval handler auto-approved every request when
legacy yolo/afk was on, silently swallowing the sensitive-file,
plan-review, and ask-rule prompts the engine yolo mode still sends.
Forward every request to the user and let the engine permission mode do
the auto-approving, matching TUI and web behavior.
2026-07-18 02:09:31 +08:00
github-actions[bot]
5cc194956f
ci: release packages (#1785)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-17 20:45:40 +08:00
Kai
1b907b07cd
fix(tui): hide whitespace-only thinking from the transcript (#1829)
Models occasionally stream whitespace-only thinking (e.g. a single
space). It starts a thinking draft that renders as a bare bullet line,
both while streaming and when replaying session history. Skip
whitespace-only thinking deltas before they create a draft, and skip
whitespace-only think text at the component funnel so stored
whitespace think parts never render on replay. Stored thinking is
still replayed verbatim to the model.
2026-07-17 17:19:22 +08:00
liruifengv
bfecd0128f
feat: refresh model list for API-key providers at the managed endpoint (#1824)
* feat: refresh model list for API-key providers at the managed endpoint

* docs: simplify changeset wording

* docs: add kimi-code/k3 to the default config example

* fix: clear stale default model when a refresh drops its alias

* fix: clear refresh defaults via replace; set(undefined) cannot delete

* docs: drop the auto model refresh paragraph from providers page

* docs: simplify changeset wording
2026-07-17 17:17:58 +08:00
liruifengv
a5c568dc7a
feat(tui): add /copy slash command to copy the last assistant message (#1822)
* feat(tui): add /copy slash command to copy the last assistant message

The command copies the latest assistant reply (text parts only, skipping thinking, tool-call-only turns, error and internal messages) to the clipboard. Clipboard writes now also emit an OSC 52 sequence (tmux-aware) so copying keeps working over SSH and in containers where no native clipboard tool exists.

* fix(tui): gate /copy to idle and report OSC 52-only copies as unverified

During streaming the in-flight assistant text is not in getContext() history yet, so /copy would silently copy an older message; make the command idle-only like /export-md. copyTextToClipboard now returns how the text was delivered so /copy can say when only an unverified OSC 52 escape carried it.

* fix(tui): source /copy text from the visible transcript

After /compact the model context keeps only user messages plus a user-role summary, so scanning it found no assistant message even though the last reply is still on screen. Read the last assistant transcript entry instead — it matches what the user sees and survives compaction and resume.

* fix(tui): mark real model text in the transcript so /copy skips synthetic cards

Hook-result and goal-completion cards are also 'assistant' transcript entries appended after the real reply, so /copy could copy a hook card instead of the answer. Tag the single entry-creation site for genuine model text (both live and replay flow through it) and have /copy only accept tagged entries.
2026-07-17 16:39:19 +08:00
Haozhe
9b496946dc
feat(kimi-inspect): add kap-server web inspector with dev-only /api/v1/debug RPC surface (#1806)
* feat(kimi-inspect): add web inspector for kap-server /api/v2 surface

- new apps/kimi-inspect app: connect screen (server URL + optional bearer
  token, persisted in localStorage, deep-linkable via ?url=/?token=),
  workspace/session browser sidebar, per-session chat view, and live
  Service panels with data and trigger buttons for Session/Agent scopes
- built on @moonshot-ai/klient (HTTP for calls, /api/v2/ws for events);
  Vite dev server proxies /api to a running kap-server
- register the workspace in AGENTS.md project map and flake.nix
  workspacePaths/workspaceNames

* fix(kimi-inspect): align dependency versions with the workspace (sherif)

* feat: dev /api/v1/debug RPC surface and kimi-inspect channel rework

- kimi-inspect: replace @moonshot-ai/klient with an in-app old-klient-style
  channel layer (service-bound IChannel, HTTP ProxyChannel, shared /api/v2/ws
  socket with ref-counted event listens), typed by agent-core-v2 interfaces;
  /channels descriptors + serviceByName keep every wire protocol loaded 1:1
- kimi-inspect: local server auto-discovery (Vite middleware over the
  kap-server instance registry + home token), zero-config startup connect,
  and a header switcher for runtime server switching
- kap-server: wire the dormant --debug-endpoints flag to a new
  whitelist-free /api/v1/debug dispatcher (every scoped service callable),
  gated to loopback binds; repo dev scripts pass the flag
- kimi-inspect: probe the debug surface at connect, falling back to /api/v2
  on servers without it
- tests: channel + discovery unit tests in kimi-inspect; debug RPC and
  loopback-gating coverage in the kap-server rpc/debugNonloopback suites

* chore(changesets): ignore @moonshot-ai/kimi-inspect

The private dev app never ships, so it should never appear in a changeset.
Add it to the changeset config ignore list (next to vis*) and note the rule
in the gen-changesets skill.
2026-07-17 15:56:53 +08:00
liruifengv
cec15e2188
fix(tui): dismiss /btw panel before cancelling compaction on Esc and Ctrl+C (#1811) 2026-07-17 13:29:12 +08:00
github-actions[bot]
36b05820cb
ci: release packages (#1767)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-16 18:54:29 +08:00
Kai
d531398d01
fix: scope Anthropic effort fallback profile to non-Kimi providers (#1765)
* fix: scope Anthropic effort fallback profile to non-Kimi providers

Managed Kimi models routed through the Anthropic protocol (protocol =
"anthropic", no catalog-declared think_efforts) inherited the inferred
latest-Opus effort profile, so the UI showed reasoning effort choices the
server never declared. The fallback now applies only when the provider type
is known, non-kimi, and the effective wire protocol is Anthropic; Kimi
providers keep catalog-declared efforts only, and callers without provider
context fall back to name matching.

* fix: align v2 catalog with resolver for providerless Anthropic models

Flat models (inline base_url, no named provider) and providers without a
declared type now fall back to the model's own protocol when deciding the
Anthropic fallback effort profile, so the model catalog stays consistent
with runtime resolution.

* fix: keep flat Anthropic model effort metadata in TUI and ACP catalogs

Flat models without a named provider (inline base_url, protocol:
"anthropic") have no provider entry to look up; fall back to the
model's own protocol as the provider identity so the effort picker and
ACP catalog stay consistent with runtime resolution.

* style: move v2 anthropic fallback rationale into the modelAuth header

The v2 comment convention keeps comments in the top-of-file block only;
drop the inline explanations added beside functions and statements.
2026-07-16 17:30:20 +08:00
qer
d1ca65e1de
feat(vscode): migrate extension to Node SDK (#1769)
* feat(vscode): migrate extension to Node SDK

* fix(vscode): address CI failures

* fix(vis): handle token count records

* fix(vscode): keep chat toolbar and header readable at narrow widths

* fix(vscode): map yolo to core yolo permission and honor the global yolo setting

* docs(vscode): record Node SDK migration design

* docs(vscode): split breaking changes out of the 0.6.0 changelog

* fix(vscode): keep a resumed session's thinking effort instead of reapplying the default

* fix(vscode): announce session status when a view attaches so the display matches it

* fix(vscode): align webview thinking effort handling with the TUI
2026-07-16 17:27:21 +08:00
liruifengv
b5139757e2
fix: align context usage display with 1024-based units and ceiled percents (#1771)
* fix(web): align context usage display with 1024-based units and ring-only meter

- simplify the composer context meter to the ring only; the full
  used/max/pct numbers live in the tooltip
- format token counts with 1024-based k/M units via a shared formatTokens
  helper (256k context reads "256k", not "262k"), applied to the composer
  tooltip, status panel, mobile settings sheet, model picker, goal strip,
  and turn rendering
- ceil the usage percent so sub-0.5% usage still shows a sliver instead
  of an empty meter

* fix(tui): render context usage with 1024-based units and ceil percent

- formatTokenCount is now 1024-based ("256k", not "262.1k"); the footer,
  /status and /usage panels, subagent cards, and goal stats all share it,
  replacing five local 1000-based copies
- the footer and panel percents use an integer ceil (new usagePercent
  helper) so any non-zero usage shows at least 1% instead of "0.0%"

* fix(web): clamp the status panel context percent to [0,100]

ctxUsed can momentarily exceed ctxMax (estimates), which could flash a
"101%" readout — the composer and mobile sheet already clamp the same
ConversationStatus data, so apply the same clamp around the ceiled
percentage here.

* chore: merge the context usage changesets into one

* chore: reword the context usage changeset in English
2026-07-16 16:41:40 +08:00
liruifengv
81414b6ad5
feat: warn about context-cache loss when switching model or thinking effort (#1763)
* feat: warn about context-cache loss when switching model or thinking effort

* chore: polish the switch warning copy

* feat: wrap the switch warning instead of truncating it

* chore: bold /new in the switch warning

* Revert "chore: bold /new in the switch warning"

This reverts commit a438e87cda543202a6d4726c96cdabd73b8dcf6f.

* chore: align the changeset with the warning copy
2026-07-16 15:43:23 +08:00
github-actions[bot]
50b919b67e
ci: release packages (#1752)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-16 10:17:32 +08:00
Kai
1d7c205e83
fix: close two silent-exit vectors around unhandled rejections (#1758)
Some checks are pending
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 / test (4) (push) Waiting to run
CI / test (5) (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
CI / typecheck (push) Waiting to run
Release / Native release artifact (push) Blocked by required conditions
Release / Desktop release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix: record crash telemetry for unhandled promise rejections

The crash handler only listened on uncaughtExceptionMonitor, which never
fires for a rejection that has a listener — and the TUI always registers
one, converting rejections into a silent exit(1) with no telemetry at
all. Observe unhandledRejection directly so those crashes still leave a
trace. Since registering a real listener suppresses Node's default
crash-on-rejection, rethrow when we are the only listener (print /
server modes), deduped so the monitor does not double-report.

* fix: fall back to text paste when the clipboard image handler rejects

The Ctrl+V image-paste dispatch chained off the async handler without a
rejection branch, so any failure inside it became an unhandled
rejection — which the CLI's crash path turns into a silent exit(1).
Treat a rejection like "no image available" and paste as text.

* fix: dedupe all rethrown rejection reasons at the crash monitor

Non-Error rejection reasons (plain objects, strings, null) were recorded
by the rejection handler but not added to the dedupe set, so the
uncaughtExceptionMonitor pass after the rethrow reported a second crash
for the same failure; null/undefined reasons also crashed the monitor's
own error-type extraction. Use a Set so primitives dedupe by value, add
every rethrown reason, and classify monitor crashes null-safely.
2026-07-16 02:14:14 +08:00
Kai
918c1354d9
fix: align Anthropic-compatible model capabilities (#1746)
* fix: align Anthropic-compatible model capabilities

* fix: warn on Anthropic effort mismatches

* fix: harden Anthropic model resolution

* fix: align Anthropic replay and ACP thinking state

* fix: normalize Anthropic thinking stream payloads

* fix: backfill non-empty preserved thinking

* fix: resolve session thinking effort with provider context

* fix: honor adaptive thinking opt-out in effort resolution
2026-07-16 01:31:38 +08:00
Kai
f0c8a103c6
fix: preserve the crash error in diagnostic logs on unexpected exit (#1757)
The TUI crash path (uncaughtException / unhandledRejection) logged the
error into an asynchronously-drained sink and then called process.exit()
on the same tick, so the one line explaining the crash was never written
to disk. Flush the diagnostic logs synchronously before exiting.
2026-07-16 01:26:27 +08:00
github-actions[bot]
6a8e610ca3
ci: release packages (#1702)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-15 18:00:18 +08:00
7Sageer
c291ee2ddb
fix(telemetry): deliver session_started in v2 headless mode (#1687)
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 / typecheck (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (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 / Desktop release artifact (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
* fix(telemetry): deliver session_started in v2 headless mode

session_started/session_load_failed fire inside create()/resume(), but the CloudAppender was installed only after resolveNativeSession() returned, so they were dropped to the null appender.

- run-v2-print: install the appender before resolving the session; reconcile a resumed session's real model via setContext afterwards.
- sessionLifecycle: bind the session id in announceCreated before emitting so session_started carries session_id.
- CloudAppender.setContext: allow updating the model context.

* fix(agent-core-v2): bind session id on session_load_failed

The resume failure path emitted session_load_failed without binding the
session id first, so the event went out with session_id null even though
the service knows which session failed to load. Bind it via setContext
before track2, mirroring the announceCreated path for session_started,
and update the two tests that locked the empty context in as expected.
2026-07-15 11:52:01 +08:00
Haozhe
78ca44b4d1
feat(kimi-code): keep print-mode goal runs alive until the goal settles (#1712)
* feat(kimi-code): keep print-mode goal runs alive until the goal settles

- applyPrintBackgroundPolicy waits for goal continuation turns via a goalActive hook before applying the exit/drain/steer mode, bounded by the wait ceiling
- createPrintTurnEndings skips the timeout when the remaining budget is not finite
- update the changeset to cover the goal-run lifecycle

* fix(kimi-code): wake the print goal wait periodically so settled goals exit promptly
2026-07-14 21:09:09 +08:00
Haozhe
38a2363a00
fix: cross-version session compatibility and real message times in snapshot history (#1704)
* fix(server): report CLI version as server_version

- kap-server: accept opts.version in startServer, reported as
  server_version (/meta, OpenAPI, session exports, lock registry,
  default User-Agent); defaults to its own package version
- kimi-code: pass the CLI product version when starting the server
- add boot test covering /api/v1/meta, lock file, and User-Agent

* fix(agent-core-v2): make new sessions resumable by older v1 builds

- add wireRecord.seal() to write the metadata envelope at agent creation,
  so fresh logs satisfy v1 replay's first-record-must-be-metadata invariant
- seal the wire log before any op dispatch in AgentLifecycleService.create;
  no-op when the log already has records (resume / forked copies)
- seed empty agents/custom maps in session metadata so v1 Session.resume()
  can index agents['main'] on a v2-created state.json
- add seal unit tests and lifecycle sealing tests

* fix(kap-server): show real message send times in snapshot history

- read per-record times stamped on wire.jsonl via reduceContextTranscript
  and cache them alongside the transcript messages
- prefer each record's real dispatch time for created_at; records without a
  stamp fall back to session.createdAt + index, clamped so the page stays
  strictly increasing (mirrors MessageLegacyService.list)
- add tests for fallback, clamping, and the page-offset index mapping

* fix(agent-core-v2): backfill missing agents/custom maps in existing session metadata

- load() now heals pre-fix v2 state.json documents that never gained the
  agents/custom maps, persisting the backfill so one open on a new build
  leaves the session resumable by released v1 builds (Session.resume()
  indexes agents['main'] unconditionally)
- updatedAt is deliberately untouched so the format heal does not reorder
  session listings

* feat(agent-core-v2): make subagent timeout configurable, default 2h

- add [subagent] config section with timeout_ms and KIMI_SUBAGENT_TIMEOUT_MS env override
- resolve Agent and AgentSwarm per-run timeouts through resolveSubagentTimeoutMs
- update tool descriptions and tests to reflect the 2-hour default

* feat(agent-core-v2): align print-mode background policy with v1

- add printBackgroundMode/printMaxTurns to the task config section with resolvePrintBackgroundMode (keepAliveOnExit fallback)
- apply exit/drain/steer policy to kimi -p on the experimental engine, buffering turn.ended events and failing the run when a steered turn fails
- register the subagent config section from the package entry

* fix(agent-core-v2): strict equality in metadata heal, comments in file headers only

- replace == null with === undefined in the session-metadata heal to
  satisfy eqeqeq (oxlint --type-aware in CI)
- move the seal() rationale into the wireRecord contract header and the
  agents/custom invariant into the sessionMetadata header per the
  tree's header-only comment convention
2026-07-14 18:35:02 +08:00
github-actions[bot]
1d2249bfa4
ci: release packages (#1682)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-14 17:03:41 +08:00
Kai
d158e0a7ac
fix: resolve and synchronize thinking effort (#1625)
* fix: preserve provider thinking effort values

* fix: resolve Kimi thinking effort fallbacks

* fix: use resolved Kimi effort in v2 requests

* fix: align Kimi effort resolution paths

* fix: synchronize forced Kimi effort state

* fix: synchronize forced Kimi effort in v2 state

* fix: tolerate unresolved models in v2 status
2026-07-14 16:37:03 +08:00
liruifengv
268fd41734
docs(changelog): sync 0.24.0 from apps/kimi-code/CHANGELOG.md (#1683)
* docs(changelog): sync 0.24.0 from apps/kimi-code/CHANGELOG.md

* chore: update changelog

---------

Co-authored-by: qer <wbxl2000@outlook.com>
2026-07-14 16:23:07 +08:00
github-actions[bot]
2383137f3e
ci: release packages (#1583)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-14 15:01:30 +08:00
Kai
7c889f3a96
fix(agent-core): mark auto-approved plan exits as not user-reviewed (#1638)
* fix(agent-core): mark auto-approved plan exits as not user-reviewed

In auto permission mode ExitPlanMode is approved without user
involvement, but its result read "## Approved Plan:" exactly like a
genuine user approval and the transcript showed a green "Approved"
chip. The model treated that as a signal to start executing, even
when the user had asked it to stop after planning.

- Emit an "auto-approved, not user-reviewed" result with a note that
  execution follows the user's original instructions (v1 + v2).
- Extend the auto-mode reminder: plan approvals are automatic and are
  not a user signal to proceed (v1 + v2).
- Render an "Auto-approved" warning-toned chip in the TUI, keeping
  backward compatibility with the old result marker.
- Document the Auto mode plan-exit behavior in interaction guides.

* fix(agent-core): only mark plan exits as auto-approved in auto permission mode

Address review feedback on the auto-approved plan exit change:

- Branch the direct-execution output on the permission mode in both
  engines: only auto mode yields the "auto-approved, not
  user-reviewed" result and telemetry outcome. In manual / yolo modes
  the direct path means a configured or session allow/ask rule let the
  call through — an explicit user decision that keeps the user-approved
  output, the Approved transcript chip, and the approved outcome.
- Move the v2 rationale out of the method body into the file header,
  per the agent-core-v2 comment convention.
- Drop the absolute "only Auto mode" wording from the interaction
  guides (en/zh).
2026-07-14 11:30:47 +08:00
Haozhe
8027fe291b
fix(agent-core-v2): caller MCP servers and skill dirs parity (#1636)
* feat(agent-core-v2): support caller-supplied MCP servers on session create

- add mcpServers to CreateSessionOptions, forwarded to the session's first
  ensureMcpReady call so caller-supplied servers ride on the initial load
- ensureMcpReady accepts callerServers, honored only by the call that starts
  the initial load; later calls just await the in-flight single-flight load
- merge precedence mirrors v1 (rpc/core-impl.ts): file config <
  caller-supplied < plugin servers

* feat(agent-core-v2): file tools reach skill roots; add skillDirs seed

- add extendWorkspaceWithSkillRoots to path-access; Read/Write/Edit/Grep/
  Glob and media tools merge skill-catalog roots into the workspace per
  call (v1 merged once at construction), so skill dirs outside the cwd
  stay reachable and late-loading roots are picked up
- add skillCatalogRuntimeOptionsSeed; wire --skillsDir into the v2 print
  CLI and skillDirs into kap-server startServer (v1 SDK skillDirs parity)

* chore: add changesets for v2 caller MCP servers and skill dirs fixes
2026-07-14 10:02:15 +08:00
Haozhe
4ec2e7fab1
feat(server): default to kap-server and remove the v1 server package (#1617)
* feat(server): default to kap-server and remove the v1 server package

- kimi server run / kimi web now boot kap-server (agent-core-v2 engine)
  unconditionally; the KIMI_CODE_EXPERIMENTAL_FLAG gate on the server
  path is gone (the kimi -p print-mode gate stays)
- move the OS service manager (svc: launchd/systemd/schtasks) from
  packages/server into packages/kap-server and export it there
- repoint the CLI server subcommands, tests, and dev scripts at
  kap-server; relabel the web dev backend presets default/multi
- delete packages/server and update workspace bookkeeping (flake.nix,
  pnpm-lock.yaml, changeset ignore docs, AGENTS.md, agent-core-dev skill)

* test(server-e2e): remove scenarios that depend on v1 debug endpoints

Scenarios 04-stateless-controls, 10-prompt-queue-steer and
12-send-and-cancel assert through the /api/v1/debug/prompts/*
introspection routes, which only the deleted v1 server mounted —
kap-server's --debug-endpoints is a documented no-op, so these
scenarios can only 404 now. The vitest e2e files using the same
surface already skip when it is absent.
2026-07-13 21:43:45 +08:00
7Sageer
d601847f22
fix: send the product User-Agent on provider registry and catalog fetches (#1597)
* fix: send the product User-Agent on provider registry and catalog fetches

Registry (api.json) and models.dev catalog fetches only carried the
runtime default User-Agent while every other outbound request sends
kimi-code-cli/<version>. Thread an optional userAgent through
fetchCustomRegistry / fetchCatalog and the shared refresh host, pass
the product UA from the CLI, TUI, and both daemons, and seed a default
product UA in kap-server that hosts can override via opts.seeds.

* refactor: use options for registry fetches
2026-07-13 17:45:34 +08:00
_Kerman
5502b90b42
test: cut slow suite runtimes and isolate experimental flag from host env (#1595) 2026-07-13 17:45:22 +08:00
_Kerman
bc8eb1e417
chore: drop #/ import array fallbacks and custom resolution plugins (#1594) 2026-07-13 16:37:35 +08:00
liruifengv
20615902c2
fix(tui): deliver pasted media in /skill args and Ctrl-S steer (#1588)
* fix(tui): deliver pasted media in /skill and plugin command args via file tags

Skill and plugin command args are a plain-text RPC channel, so pasted image/video placeholders never reached the model. Rewrite matched placeholders into <image|video path="..."> tags pointing at cache-dir copies (the same convention pasted videos already use), and gate them on model media capabilities like the normal prompt path.

* fix(tui): deliver pasted media when steering with Ctrl-S

Ctrl-S steer dropped media: queued messages were reduced to their text and the editor draft never went through placeholder extraction, so session.steer only ever received placeholder strings. Carry the queued items' extracted parts, extract the editor draft on the spot (with the same capability gate as a normal submit), and merge everything into a part list when any item has media.

* fix(tui): avoid empty text separator when steering consecutive media

The '\n\n' item separator could land as a standalone whitespace-only text part between two media parts, which normalizePromptInput rejects — failing the steer after the queue and editor draft were already cleared. Only append the separator onto a trailing text part so it always merges.

* fix(tui): keep blank-line separator between media and text steer items

The separator was only appended onto trailing text parts, so a media item followed by a plain-text steer item lost the historical '\n\n' separation. Always append it — it merges into the adjacent text part — except between two touching media parts, where a standalone whitespace-only text part would be rejected by normalizePromptInput.

* fix(tui): use escape-proof media references in /skill args

Skill args are XML-escaped on render (renderSkillAttributes + expandSkillParameters), which mangled the <image|video path> tags into escaped text. Add a 'plain' reference style (Attached image file: <path> (open it with ReadMediaFile)) with no tag/attribute boundary characters and use it for the skill channel; plugin command args expand verbatim and keep the tag form.

* fix(tui): strip XML boundary chars from plain-style video cache names

Video cache names carry the original label, which permits <>&"; in the plain (/skill) reference style those chars get XML-escaped in args and the reference no longer matches the file on disk. Strip them from the cache name in plain mode; the tag channel is unchanged (its attribute escaping already handles them).

* fix(tui): surface media materialization failures as TUI errors

The cache copy inside media placeholder rewriting can throw (unwritable cache dir, vanished video source) before any RPC .catch is installed, escaping the fire-and-forget slash-command dispatcher as an unhandled rejection. Catch it in sendSkillActivation, activatePluginCommand, and the Ctrl-S draft extraction, show an error, and leave queue/draft/session state untouched.
2026-07-13 16:16:44 +08:00
qer
924d5c9141
feat(web): dev backend switcher and engine badge for dual-engine debugging (#1592)
Add the plumbing to debug kimi-web against the v1 (server) and v2
(kap-server) engines side by side:

- root dev:v1 / dev:v2 scripts; v2 boots kap-server with the
  multi_server flag on a fixed port so both engines can run at once
- dev-proxy backend switcher: GET/POST /__kimi-dev/backend repoints
  the /api/v1 proxy at runtime (HTTP + WS) without a Vite restart
- dev-only sidebar backend pill reading /meta's backend field, with a
  one-click switcher menu; Settings shows the backend engine too
- re-fetch /meta on every WS (re)connect so the badge stays truthful
  across backend restarts and switches
- strip the browser Origin header on proxied requests: v1's WS upgrade
  path rejected the Vite-origin vs server-Host mismatch with 403
2026-07-13 15:22:02 +08:00
Haozhe
ceb158dc54
feat(v2): land agent-core-v2 engine and kap-server behind experimental flag (#1441)
Some checks are pending
CI / typecheck (push) Waiting to run
CI / test-pi-tui (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
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Release / Desktop release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix: adapt grep tool to agent-core-v2

* fix(agent-core-v2): enrich PATH from the user's login shell at startup

- port probeLoginShellPath/mergeLoginShellPath/applyLoginShellPath into
  _base/execEnv/loginShellPath.ts as a pure helper (no DI)
- export execFileText from environmentProbe for reuse by the probe
- run applyLoginShellPathFromNode concurrently with the host probe in
  HostEnvironmentService, mirroring kaos LocalKaos.create()

Aligns agent-core-v2 with kaos 021786f5 so the Bash tool finds
user-installed tools (e.g. Homebrew's gh) when kimi-code is launched
from a GUI or non-login shell.

* fix(agent-core-v2): prefer persisted cwd on resume

* feat(agent-core-v2): support structured response formats

* fix: restore v2 grep telemetry and tests

* fix: preserve v2 compaction boundary

* fix(agent-core-v2): align agent and swarm tool behavior

* feat(ws-v1): add per-agent event subscription filter

- protocol: add optional agent_filter to client_hello and subscribe
- kap-server: carry per-subscription agent allowlists through the broadcaster
  and connection, narrowing live fan-out and replay to selected agents while
  keeping a single global sequence and bypassing the filter for global events
- agent-core-v2: degrade MiniDbQueryStore to a no-op read model when the
  query-store lock is held by another process instead of crashing the host

* feat(web): prefix skill slash commands with skill: to distinguish them from built-in commands (#1492)

* ci: release packages (#1468)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* docs(changelog): sync 0.23.2 from apps/kimi-code/CHANGELOG.md (#1496)

* chore: add changeset for agent swarm parity

* fix: align v2 compaction prompt

* fix: recover v2 compaction from plain 413

* fix: report v2 compaction retry telemetry

* fix: align MCP discovery and output with v1

* fix: align v2 compaction auth guards

* fix: align v2 grep behavior with v1

* chore: remove agent swarm changeset

* fix(agent-core-v2): restore task resume parity

* feat(agent-core-v2): record llm request traces

* fix(agent-core-v2): align AskUserQuestion tool chain with v1

- translate wire ids back to question text / option labels when resolving
  a question over REST, joining multi-select labels with ', '
- enforce unique question texts / option labels and non-empty strings at
  both the schema and the execution path
- cancel pending questions on turn abort or background task stop by
  dismissing the parked entry (resolves null, v1 broker semantics)
- restore the unsupported-client fallback and dismissed-error handling
- pass empty header / option description through verbatim and align the
  model-facing tool description byte-for-byte with v1
- drop the synthetic expires_at field from the question wire shape

* fix(agent-core-v2): preserve compaction hook session

* test(agent-core-v2): cover concurrent agent background limit

* fix(agent-core): report EXIF-rotated image dimensions and raise edge cap to 3000px (#1460)

* fix(agent-core): report EXIF-rotated image dimensions and raise edge cap to 3000px

Image compression now reports original dimensions in the decoded
(EXIF-rotated) space, matching the coordinate system of the sent image
and of ReadMediaFile region readback; previously portrait JPEGs
(orientation 5-8) got swapped width/height in captions. The longest-edge
downscale cap rises from 2000px to 3000px, and the default jimp resize
path is documented as the anti-aliased area-average one so it is not
accidentally switched to a point-sampled interpolation mode.

* test: shrink oversized image fixtures to fit CI timeouts

The 3600x3600 fixtures introduced for the 3000px edge cap nearly doubled
the pixel area jimp has to decode and deflate, pushing the slowest
compression tests past the 5s vitest timeout on CI runners. 3600x1800
keeps every fixture over the cap while restoring roughly the workload of
the old 2600x2600 fixtures that CI handled comfortably.

* test: pin anti-aliased downscale quality with executable guards

A 1px checkerboard probe pins the compressor to full-coverage averaging
at integer and fractional ratios, with jimp's point-sampled BILINEAR
mode kept as the executable aliasing counter-example (it collapses the
50%-gray pattern to solid black at 4:1). Also guards the other classic
downscale bugs: transparent-pixel color bleed, mean-brightness drift,
iterative recompression degradation, and zero-size collapse on extreme
aspect ratios.

* fix(agent-core): report decoded EXIF-rotated dimensions in ReadMediaFile notes

The media note derived its original-dimensions line from the header
sniff, which reports pre-rotation values for EXIF orientation 5-8
JPEGs. The sent image and region readback both live in the decoded
(rotated) space, so portrait photos got axis-swapped coordinate
guidance. Once a decode has happened — compression or crop — its
dimensions now overwrite the sniffed ones.

* fix(agent-core): improve handling of EXIF orientation in image dimensions and metadata

* fix(agent-core): sniff EXIF orientation and step budget fallback through 2000px

Two follow-ups to the EXIF and 3000px-cap changes:

sniffImageDimensions now reads the JPEG EXIF Orientation tag (pure
header parse, both byte orders) and reports display-space dimensions
for orientations 5-8. Passthrough images — never decoded — previously
kept the pre-rotation header size in compression results and media
read notes, disagreeing with the decoded space that region readback
uses.

encodeWithinBudget steps the over-budget fallback through 2000px
before the 1000px last resort. Raising the cap to 3000px had left a
regression window: an image whose 2000px encode fits the byte budget
was sent at 1000px where the old 2000px cap used to send it at
2000px.

* fix(kimi-code): record pasted image dimensions in display space

The TUI paste path recorded attachment and original dimensions from its
raw header parser, which ignores EXIF orientation. For a portrait JPEG
the submit-time caption then contradicted the sent image's aspect and
region readback coordinates were axis-swapped. Dimensions now come from
the compression result, which reports display space on both the
compressed and passthrough paths; parseImageMeta remains only the
format/mime gate.

* feat(agent-core): add image compression and crop telemetry

Every image ingestion path now reports an image_compress event —
outcome (compressed / passthrough fast, guard, unsupported, unhelpful,
error), input/output formats, byte and pixel sizes, EXIF transposition,
and duration — and region readback reports an image_crop event with a
failure classification and the region's share of the original area.

Wiring is per call site via a new CompressImageOptions.telemetry
option, so the outcome split and timing are measured inside the
compressor while each caller only names its source: ReadMediaFile
(tool construction, like GrepTool), MCP tool results (McpOutputOptions),
server prompt ingestion (ICoreProcessService now exposes the host
telemetry client), ACP prompts (session track adapter), and TUI paste
(host.track adapter). Properties are numeric/enum only — never paths
or content — and a throwing client can never affect the compression
result.

* fix(agent-core): run the full JPEG quality ladder at fallback sizes

The fallback rescales encoded only at quality 20, so a JPEG whose
ladder failed at the fitted size collapsed straight to the lowest
quality even when the smaller size left budget headroom for a higher
rung (the realistic window is the 1000px step, where the 4x pixel
drop pays for q80/q60). Each fallback edge now walks the same
q80-to-q20 ladder as the fitted size.

* test: shrink heavy JPEG fixtures and add explicit timeouts

The fallback-ladder test runs ~11 pure-JS JPEG encodes and the EXIF
paste test decodes, rotates, and re-encodes a 6.5MP frame; both sat at
the edge of the 5s vitest timeout on CI runners. Narrower fixtures cut
the pixel area (the ladder test keeps its width above 2000px so the
full fallback chain still runs) and explicit 15s timeouts absorb runner
variance.

* fix(server): scope prompt image compression telemetry to the session

The prompt-ingestion image_compress events were emitted with the bare
host telemetry client, while every agent-side source inherits a
session-scoped client — so prompt_inline/prompt_file events could not
be correlated with their session. The route now wraps the client with
withTelemetryContext({ sessionId }) like rpc/core-impl does for
session telemetry.

* chore(changeset): consolidate image compression changesets

One entry covering the cap raise and the EXIF dimension fix, listed
for both the CLI and the SDK so the SDK changelog's compression
description (previously pinned at 2000px) stays accurate.

* fix: count goal creation turn (#1477)

* feat(kosong): support structured response formats (#1397)

* fix: clarify goal blocked audit guidance (#1481)

* feat(agent-core): discard loaded tool schemas on compaction (#1471)

Align progressive tool disclosure with the discard-on-compaction model:
compaction no longer rebuilds loaded dynamic tool schemas. The boundary
announcement re-lists every loadable name, the model re-selects what it
still needs, and a from-memory call to a no-longer-loaded tool is
rejected by preflight with select guidance.

This removes the keep-all rebuild and its half-trigger budget heuristics
entirely: the post-compaction floor is back to users + summary, which is
structurally outside the auto-compaction trigger band, and the guard
baseline degenerates to summary + reinjected reminders. Every downstream
mechanism already treated the empty loaded set as its consistent base
state (ledger scan, pending clear at the compaction boundary, deferred
extras, preflight wording), so this is a strict simplification.

Co-authored-by: fengchenchen <fengchenchen@moonshot.ai>

* fix(kimi-code): exit 1 when a headless (-p) turn fails (#1483)

Headless (`kimi -p`) failures could exit with code 0 when the event loop
drained during the shutdown cleanup (e.g. telemetry's unref'd retry backoff
when the network is blocked), because the rejection never reached the
process.exit(1) call. Set the failure exit code before any await in both
the run-prompt catch and the main catch, and keep the cleanup timeout ref'd
so the loop stays alive long enough for the rejection to propagate.

* feat(plugins): add Vercel plugin to marketplace (#1489)

* feat(web): support Enter key to confirm archive and other dialogs (#1490)

* feat(web): redesign cron reminder as a message bubble (#1480)

* feat(web): redesign cron reminder as a message bubble

Restyle the cron trigger notice as a right-aligned user-style message bubble that shows the scheduled prompt in full (wrapping across lines), with a small meta row beneath it for the schedule, status, job id and run time. Extract a shared MessageTime component used by both user messages and the cron reminder so the timestamp format and click-to-expand behavior stay consistent, and give the CronCreate/CronList/CronDelete tools distinct calendar icons.

* refactor(web): render cron reminders only as standalone turns

Remove the embedded cron block path from the web transcript projector so cron reminder fires always render through the standalone right-aligned bubble path.

* chore(web): simplify cron redesign changeset

* fix(web): composer model switch also updates global default model (#1491)

* fix(web): composer model switch also updates global default model

The composer model switcher still switches the active session's model via
POST /sessions/{id}/profile (awaited, so the model pill reflects the result),
and additionally fires POST /api/v1/config with { default_model } as a
fire-and-forget side effect so new sessions inherit the chosen default. The
config request is skipped when the model already matches the current default.

* fix(web): route ModelPicker overlay selection through the default-model update

The overlay opened from the composer's "More models" row (and /model) is a
continuation of the same switch flow, so its selection now also bumps the
global default model instead of only switching the active session.

* fix(web): only persist the default model after a confirmed session switch

setModel now returns whether the switch was accepted (true for the draft
path), so the composer flow no longer writes a stale or invalid model alias
into the global config when the session-level switch failed and rolled back.

* feat(web): prefix skill slash commands with skill: to distinguish them from built-in commands (#1492)

* ci: release packages (#1468)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(agent-core-v2): restore native append for Write append mode

- add IHostFileSystem.appendText backed by fs.appendFile (O_APPEND)
- route WriteTool append through it instead of read-then-rewrite, so
  existing content is never read, truncated, or clobbered by concurrent
  writers and a crash mid-append can only lose the new bytes
- update typed host-fs test fakes and WriteTool append assertions

* fix(agent-core-v2): align task tool prompts with v1

* fix(agent-core-v2): align compaction empty retry

* fix(agent-core-v2): restore web search source site and citation reminders

- surface source site: add WebSearchResult.siteName, map site_name in the
  Moonshot provider, and render the Site: line in tool output
- restore the per-search inline citation reminder alongside the results
- align web-search.md with v1: source-site/result-summary guidance and the
  static citation reminder

* fix(agent-core-v2): route task timeouts through SIGTERM grace + SIGKILL

- add terminateWithGrace shared by stop, timeoutMs, detachTimeoutMs, and
  track deadlines: cancel/SIGTERM -> 5s grace -> forceStop (SIGKILL)
- coerce a post-abort self-settled `killed` to `timed_out` so a deadline
  stays reported as timed_out, matching v1 settlementForOutcome
- add manager tests for SIGTERM-ignored escalation, graceful-exit within
  the grace window, and detachTimeout teardown

* fix(agent-core-v2): restore fs.grep streaming early-kill and symlink reporting

- stream `rg --json` in fs.grep and SIGKILL once max_total_matches/max_files is reached, restoring v1 early-stop instead of buffering the whole output
- report symlinks as kind 'symlink' in fs search/list/stat via lstat and never descend into symlinked directories
- remove the unused os grepSearch helper (dead, non-streaming, bypassed ISessionProcessRunner)

* test(agent-core-v2): align truncated compaction retry

* fix(agent-core-v2): align MCP tool results with v1

* fix(agent-core-v2): align blocked compaction failures

* fix(agent-core-v2): align manual compaction tool projection

* fix(agent-core-v2): preserve tail in windowed compaction

* fix(agent-core-v2): read todos from wire model, sanitize replay

- make SessionTodoService a stateless facade over the main agent's TodoModel:
  getTodos reads wire.getModel(TodoModel) live, setTodos only dispatches a
  todo.set op, and onDidChange is bridged from wire.subscribe(TodoModel); the
  in-memory list copy is gone so the live and post-replay views cannot drift
- sanitize todo.set payloads in apply via readTodoItems, so replayed or
  hand-written records cannot poison the model or downstream renders
- update todo tests to a sanitizing/notifying/replaying wire stub and cover
  malformed todo.set replay and main-absent reads
- record the main-agent-wire persistence debt for the ISessionWireService move

* fix(agent-core-v2): port v1 Bash tool output cap, saved-output reference, and background gating (#1503)

* fix: align v2 task observable behavior

* fix: v2 full compaction

* fix(agent-core-v2): align task wait timeout behavior

* chore: webSearch & FetchUrl Sync #1260

* fix: align background agent guidance

* fix(agent-core-v2): align full compaction with v1

* fix(agent-core-v2): align v1 wire records

* fix(agent-core-v2): remember observed compaction context window

* fix: align Agent / AgentSwarm

* fix(agent-core-v2): port v1 parity fixes for hooks, anthropic, thinking config and add-dir (#1504)

* fix(agent-core-v2): hide console window when running hooks on Windows

Port the v1 hooks runner fix: extract buildHookSpawnOptions and pass
windowsHide:true so hook child processes no longer flash a console
window on Windows, mirroring the node-local process host defaults.
Includes the same regression tests as v1.

* fix(agent-core-v2): port anthropic max_tokens ceiling and override fixes

Port two v1 kosong fixes to the v2 anthropic provider:

- Fall back to the nearest lower catalogued minor when resolving the
  Claude output ceiling, and catalogue Opus 4.8's documented 128k cap,
  so an uncatalogued minor no longer drops to the family baseline.
- Treat an explicit defaultMaxTokens as the final max_tokens value
  instead of clamping it to the built-in ceiling.

Mirrors the v1 regression tests in a new anthropic-max-tokens test file.

* refactor(agent-core-v2): converge thinking config to enabled/effort

Port the v1 thinking-config overhaul (#1132's config side) to v2:

- ThinkingConfigSchema becomes { enabled, effort, keep }; the mode enum,
  the separate defaultThinking section, and the KIMI_MODEL_THINKING_MODE /
  KIMI_MODEL_DEFAULT_THINKING env bindings are removed.
- The effort resolver drops the mode/defaultThinking branches and no
  longer normalizes a requested 'on' to a concrete effort in core; 'on'
  is taken verbatim and normalization stays at the UI boundary.
- OAuth login/refresh and catalog refresh now persist the thinking.enabled
  value computed by the shared oauth apply/restore logic instead of
  dropping it and writing the removed default_thinking key, so
  [thinking] enabled = false actually disables thinking and the login
  default survives on disk.

Mirrors the v1 resolver regression tests and adds a persistence
regression for the refresh path.

* docs(agent-core-v2): fix stale loop-event comments after wire parity

The v1.4 wire-parity alignment switched the v2 live loop to stream turns
as context.append_loop_event records, but three comments still described
the old world (restore-only Op, "v2 never emits loop events"). Update
them to match the actual write path: non-loop appends use append_message,
the loop persists loop events byte-compatible with v1, and the fold runs
both at live dispatch time and on replay.

* fix(agent-core-v2): load workspace additional dirs on session create and resume

The /add-dir command persisted remembered dirs to .kimi-code/local.toml,
but session materialization never read them back and offered no caller
additionalDirs entry point — a remembered dir silently stopped applying
to new, resumed, and forked sessions.

Mirror v1's createSession/resumeSession: merge the project-local
local.toml dirs with caller-supplied additionalDirs (relative paths
resolve against workDir) and seed the session workspace context in
materializeSession, so create/resume/fork all pick them up. A broken
local.toml fails the create loudly with CONFIG_INVALID, same as v1.
Tests mirror v1's runtime coverage for the load/merge/dedupe/resume/fork
scenarios.

* fix(kimi-code): forward create-session additional dirs from the v2 harness

The in-process v2 print-mode harness dropped the SDK CreateSessionOptions
additionalDirs when calling ISessionLifecycleService.create, so --add-dir
never reached the v2 resolver. Pass it through.

* fix(agent-core-v2): align full compaction observability

* fix(agent-core-v2): remove compact hook trigger state

* feat(agent-core-v2): enhance agent lifecycle with context size tracking and concurrency checks

* fix(agent-core-v2): align media reads with v1 note channel and EXIF handling (#1505)

* fix(agent-core-v2): align media reads with v1 note channel and EXIF handling

Port two agent-core changes into agent-core-v2:

- Move the ReadMediaFile media summary from an inline <system> text part
  onto the tool result's note side channel, so raw <system> markup never
  renders in UIs (matching the MCP output path).
- Report image dimensions in the decoded EXIF-rotated space: the header
  sniff now reads the JPEG Orientation tag, and once a decode happened
  (compression or crop) its dimensions overwrite the sniffed ones, so
  portrait photos no longer get axis-swapped coordinate guidance.
- Raise the longest-edge downscale cap from 2000px to 3000px, step the
  over-budget fallback through 2000px before the 1000px last resort, and
  run the full JPEG quality ladder at fallback sizes.
- Report image_compress / image_crop telemetry for media reads (source
  read_media), with EXIF transposition and crop failure classification.

The tool description also regains the downsampling recovery guidance
(region / full_resolution readback) that the v2 copy predated.

* fix(agent-core-v2): align v1 wire records

* fix(agent-core-v2): hide compression captions and register media tools in production

Port the remaining v1 media gaps into agent-core-v2:

- Reroute inline image-compression captions out of user messages: the
  prompt service splits them at the append chokepoint (prompt and steer
  flush) and delivers them through the built-in system-reminder
  injection (origin {kind: 'injection', variant: 'image_compression'}),
  which every UI hides. Session titles/lastPrompt strip the caption the
  same way. The model still receives the full note.
- Register ReadMediaFile in production: media tools cannot use the
  module-level contribution table (capabilities are unknown until a
  model binds), so a new Eager agent-scope registrar re-runs
  registerMediaTools on every agent.status.updated where the model
  alias or its media capabilities changed, rebinding the video uploader
  and dropping the tool when the model loses media input.

* fix(agent-core-v2): port v1 parity fixes for hooks, anthropic, thinking config and add-dir (#1504)

* fix(agent-core-v2): hide console window when running hooks on Windows

Port the v1 hooks runner fix: extract buildHookSpawnOptions and pass
windowsHide:true so hook child processes no longer flash a console
window on Windows, mirroring the node-local process host defaults.
Includes the same regression tests as v1.

* fix(agent-core-v2): port anthropic max_tokens ceiling and override fixes

Port two v1 kosong fixes to the v2 anthropic provider:

- Fall back to the nearest lower catalogued minor when resolving the
  Claude output ceiling, and catalogue Opus 4.8's documented 128k cap,
  so an uncatalogued minor no longer drops to the family baseline.
- Treat an explicit defaultMaxTokens as the final max_tokens value
  instead of clamping it to the built-in ceiling.

Mirrors the v1 regression tests in a new anthropic-max-tokens test file.

* refactor(agent-core-v2): converge thinking config to enabled/effort

Port the v1 thinking-config overhaul (#1132's config side) to v2:

- ThinkingConfigSchema becomes { enabled, effort, keep }; the mode enum,
  the separate defaultThinking section, and the KIMI_MODEL_THINKING_MODE /
  KIMI_MODEL_DEFAULT_THINKING env bindings are removed.
- The effort resolver drops the mode/defaultThinking branches and no
  longer normalizes a requested 'on' to a concrete effort in core; 'on'
  is taken verbatim and normalization stays at the UI boundary.
- OAuth login/refresh and catalog refresh now persist the thinking.enabled
  value computed by the shared oauth apply/restore logic instead of
  dropping it and writing the removed default_thinking key, so
  [thinking] enabled = false actually disables thinking and the login
  default survives on disk.

Mirrors the v1 resolver regression tests and adds a persistence
regression for the refresh path.

* docs(agent-core-v2): fix stale loop-event comments after wire parity

The v1.4 wire-parity alignment switched the v2 live loop to stream turns
as context.append_loop_event records, but three comments still described
the old world (restore-only Op, "v2 never emits loop events"). Update
them to match the actual write path: non-loop appends use append_message,
the loop persists loop events byte-compatible with v1, and the fold runs
both at live dispatch time and on replay.

* fix(agent-core-v2): load workspace additional dirs on session create and resume

The /add-dir command persisted remembered dirs to .kimi-code/local.toml,
but session materialization never read them back and offered no caller
additionalDirs entry point — a remembered dir silently stopped applying
to new, resumed, and forked sessions.

Mirror v1's createSession/resumeSession: merge the project-local
local.toml dirs with caller-supplied additionalDirs (relative paths
resolve against workDir) and seed the session workspace context in
materializeSession, so create/resume/fork all pick them up. A broken
local.toml fails the create loudly with CONFIG_INVALID, same as v1.
Tests mirror v1's runtime coverage for the load/merge/dedupe/resume/fork
scenarios.

* fix(kimi-code): forward create-session additional dirs from the v2 harness

The in-process v2 print-mode harness dropped the SDK CreateSessionOptions
additionalDirs when calling ISessionLifecycleService.create, so --add-dir
never reached the v2 resolver. Pass it through.

* fix(agent-core-v2): report video_upload telemetry for media reads

Port the v1 video-upload telemetry wrapper into createVideoUploader:
every upload emits a video_upload event with outcome (success/error),
byte size, mime type, duration, and the caller's static props (model
alias, protocol tags), and a throwing telemetry client never affects
the upload outcome. The media-tools registrar supplies the sink and
props from the bound model.

Also restores two v1 rationale comments in ReadMediaFile (original-size
reporting and the full_resolution hard refusal) that were dropped
during the earlier port.

---------

Co-authored-by: 7Sageer <7sageer@djwcb.cn>
Co-authored-by: liruifengv <liruifeng1024@gmail.com>

* refactor(agent-core-v2): remove the microCompaction domain

- delete the microCompaction domain (service, wire model/op, config section,
  experimental flag) and its dedicated tests
- stop truncating old tool results in the context projector and drop the
  projector's now-unused instantiation dependency
- remove the domain from the layer map, package exports, and the DI x Scope
  dependency diagram
- retarget the flag-registry test and skill examples at a neutral flag

* fix(contextProjector): surface projection repairs via log warning

- add ProjectionAnomaly + onAnomaly sink through the project / projectStrict
  passes (reorder, synthesize, orphan / duplicate drop, leading drop, merge,
  blank-text drop) so the pure projection reports every wire-repair it applies
- AgentContextProjectorService injects ILogService and emits a single
  signature-deduped 'repaired the request to keep it wire-valid' warning,
  excluding trailing-tail synthesis, matching agent-core parity
- cover the trace and its dedup in the projector tests

* fix(agent-core-v2): fix cron killswitch, lost deliveries, id clashes

- killswitch: read KIMI_DISABLE_CRON live by re-applying the ConfigService
  env overlay on every get(); CronCreate reads it via ISessionCronService
  instead of a value frozen at tool registration
- delivery: resolve fire delivery on promptService.steer().launched so a
  rejected launch retains one-shot tasks for retry instead of deleting
  them; tick() is now async and awaits delivery before advancing cursors
- ids: switch cron task ids to ULIDs (from 32-bit hex) so two sessions
  sharing a workspace cannot overwrite each other's persisted task;
  CronDelete and persistence accept both ULID and legacy 8-hex ids
- display: CronCreate reports nextFireAt through the service so it honors
  KIMI_CRON_NO_JITTER and matches the scheduler and CronList
- migration: adopt shape-valid tasks with no sessionId tag on
  loadFromStore and stamp the tag back to disk
- persistence: create cron directories 0700 and files 0600 via
  FileStorageService dirMode/fileMode

Gate SessionCronService startup on config.ready and resolve clocks after
ready so config is never read before it is loaded; start() is now async.

* feat(fs-watch): add workspace fs watch with v1-compatible WS delivery

- os layer: add IHostFsWatchService over chokidar (raw create/modify/delete, .git ignored)
- session layer: add ISessionFsWatchService, a workspace-confined, debounced, .gitignore-aware FsChangeEvent feed
- kap-server: add FsWatchBridge pushing event.fs.changed over /api/v1/ws (watch_fs_add/remove, volatile, per-connection filter), byte-compatible with v1
- tests: os/session unit tests and kap-server fs-watch e2e

* refactor(agent-core-v2): run external hooks through IHostProcessService

- inject IHostProcessService into ExternalHooksRunnerService and thread it
  through runMatchedHooks to runHook instead of spawning node:child_process
- route hook termination through the service's cross-platform process-tree kill
- settle on the exit code plus drained stdout/stderr so fast-exiting hooks
  keep their trailing output
- hide the child console window on Windows via the service default
- update externalHooks tests for the new dependency

* fix(agent-core-v2): strict-decode Edit reads, align with v1

- read the Edit target with errors:'strict' so a non-UTF-8 file fails the
  edit instead of being silently rewritten as U+FFFD (matches v1 kaos)
- declare readWriteFile access since Edit reads before it writes, matching v1
- render edit.md directly instead of through renderPrompt: it has no template
  vars, and raw avoids treating literal {{ }} as a template
- restore the replace_all usage example in edit.md (v1 #1102)
- add a regression test asserting a non-UTF-8 file fails the edit and keeps
  its bytes untouched

* fix(agent-core-v2): dedupe AgentMeta legacy field declarations

* refactor(agent-core-v2): persist wire records natively in the v1 vocabulary

Remove the persist-time v1 rewrite layer (serializeV1WireRecord): ops now
write v1-shaped records directly, live-only state is declared persist:false
on the op instead of being stripped at write time, and the swarm-exit
reminder pop replays from the swarm_mode.exit record via a cross-model
reducer. Fixes resumed sessions losing the todo list, drifting turn
counters after retries, and removed reminders reappearing on resume.

* refactor(agent-core-v2): move ReadTool status block to note side channel

- ReadTool.finishReadResult now returns rendered lines as `output` only and
  rides the `<system>` status block on the model-only `note` side channel
- drop the finishOutput helper that concatenated content and status
- update read.test.ts expectations to assert `note` separately from `output`

* refactor(agent-core-v2): split blob service helpers, rewrite tests

- extract rewriteMediaUrls and blobref parse/format helpers to dedupe URL rewriting
- move the byte-bounded LRU cache into a module-private ByteLruCache with focused unit tests, dropping the protected maxCacheSize test seam
- rewrite blob service tests against the contract on in-memory storage, removing cache-internals cases

* fix: make release-e2e scenarios pass under agent-core-v2

Three independent fixes for release-e2e failures that only appeared with the experimental v2 engine (KIMI_CODE_EXPERIMENTAL_FLAG):

- agent-core-v2: register the KIMI_MODEL env overlay statically so it takes effect even when ModelService is not instantiated (the DI layer does not auto-instantiate Eager services). Fixes wire-llm-request-trace.

- cli: omit the leading system.version meta line in stream-json prompt mode so the role sequence stays clean. Fixes stream-json-cron.

- agent-core-v2: honor --skills-dir via a new explicit skill source seeded from the host. Fixes interactive-skills-dir.

Cherry-picked from 2a7232737 (v2-migration), excluding the node-sdk V2Host change (not applicable on this branch).

* refactor(agent-core-v2): drop replay-only wire ops

Remove the three replay-only Ops that were kept for pre-alignment / 1.5 sessions, now that v2 persists natively in the v1 vocabulary:

- turn.launch (replaced by turn.prompt)

- todo.set (replaced by tools.update_store with key 'todo')

- context.splice (replaced by context.append_message / append_loop_event)

Also drop the dead code that handled them (transcript reducer, task-origin extraction, blob dehydration, harness helpers) and migrate the affected tests to the v1 record types. The live write path already emitted only v1 records, so wire.jsonl output is unchanged.

* Revert "fix: make release-e2e scenarios pass under agent-core-v2"

This reverts commit ec9dae72ab.

* fix(agent-core-v2): use a fresh TextDecoder per append-log read

The module-level TextDecoder is stateful in stream mode: it buffers a
trailing incomplete multi-byte sequence until the next decode. Sharing it
across reads let leftover state from an earlier read that returned early
(e.g. ensureWireMetadata bailing on the leading metadata record) leak into
the next read and prepend a U+FFFD to its first line, corrupting the
metadata envelope and breaking session fork with "corrupted line 1".

Give each read its own TextDecoder so decoder state never leaks between
reads.

* fix(agent-core-v2): register KIMI_MODEL env overlay statically

The KIMI_MODEL_* effective overlay was registered by ModelService on construction, but the DI layer does not auto-instantiate Eager services, so the overlay never took effect when nothing resolved IModelService. This broke the release-e2e wire-llm-request-trace scenario, where KIMI_MODEL_NAME must synthesize the env model and its thinking capability.

Move registration to module load via a new configOverlayContributions collector, drained by ConfigRegistry on construction — mirroring the existing configSectionContributions pattern. ModelService no longer depends on IConfigRegistry.

* docs(agent-core-v2): clarify live-only op semantics

* fix(agent-core-v2): preserve oversized tool results

* chore: remove full compaction complete data type

* fix(agent-core-v2): align foreground output cap

* fix(agent-core-v2): gate skill prompt injection

* chore(skills): bundle review and test lenses into kc-review

- add agent-core-review umbrella skill with slop and test sub-skills
- move write-tests rules into agent-core-review/test and drop the standalone skill

* feat(v2): auto-mint session ids and harden print-mode background drain

- make CreateSessionOptions.sessionId optional; SessionLifecycleService.create and fork now mint `session_<lowercase-uuid>` via a shared createSessionId helper, so edge layers stop minting their own ids (drop randomUUID in the v2 harness, ulid in kap-server)
- rework V2Session.waitForBackgroundTasksOnPrint to re-enumerate each round, suppress terminal notifications while waiting, and bound the drain by [task].print_wait_ceiling_s (default 1h) instead of a hardcoded 30s cap, so kimi -p can run long tasks to completion without being steered into a new turn
- add v2-session unit tests; seed session/agent/bootstrap context in the tool-dedupe harness for the real executor

* fix(agent-core-v2): refresh system prompt after compaction

* docs(agent-core-review): limit kc-review skill to agent-core-v2

Clarify that the kc-review lenses apply only to packages/agent-core-v2
(the DI x Scope engine), not to the legacy packages/agent-core or other
packages.

* fix(agent-core-v2): align model-facing prompts

* fix(agent-core): report EXIF-rotated image dimensions and raise edge cap to 3000px (#1460)

* fix(agent-core): report EXIF-rotated image dimensions and raise edge cap to 3000px

Image compression now reports original dimensions in the decoded
(EXIF-rotated) space, matching the coordinate system of the sent image
and of ReadMediaFile region readback; previously portrait JPEGs
(orientation 5-8) got swapped width/height in captions. The longest-edge
downscale cap rises from 2000px to 3000px, and the default jimp resize
path is documented as the anti-aliased area-average one so it is not
accidentally switched to a point-sampled interpolation mode.

* test: shrink oversized image fixtures to fit CI timeouts

The 3600x3600 fixtures introduced for the 3000px edge cap nearly doubled
the pixel area jimp has to decode and deflate, pushing the slowest
compression tests past the 5s vitest timeout on CI runners. 3600x1800
keeps every fixture over the cap while restoring roughly the workload of
the old 2600x2600 fixtures that CI handled comfortably.

* test: pin anti-aliased downscale quality with executable guards

A 1px checkerboard probe pins the compressor to full-coverage averaging
at integer and fractional ratios, with jimp's point-sampled BILINEAR
mode kept as the executable aliasing counter-example (it collapses the
50%-gray pattern to solid black at 4:1). Also guards the other classic
downscale bugs: transparent-pixel color bleed, mean-brightness drift,
iterative recompression degradation, and zero-size collapse on extreme
aspect ratios.

* fix(agent-core): report decoded EXIF-rotated dimensions in ReadMediaFile notes

The media note derived its original-dimensions line from the header
sniff, which reports pre-rotation values for EXIF orientation 5-8
JPEGs. The sent image and region readback both live in the decoded
(rotated) space, so portrait photos got axis-swapped coordinate
guidance. Once a decode has happened — compression or crop — its
dimensions now overwrite the sniffed ones.

* fix(agent-core): improve handling of EXIF orientation in image dimensions and metadata

* fix(agent-core): sniff EXIF orientation and step budget fallback through 2000px

Two follow-ups to the EXIF and 3000px-cap changes:

sniffImageDimensions now reads the JPEG EXIF Orientation tag (pure
header parse, both byte orders) and reports display-space dimensions
for orientations 5-8. Passthrough images — never decoded — previously
kept the pre-rotation header size in compression results and media
read notes, disagreeing with the decoded space that region readback
uses.

encodeWithinBudget steps the over-budget fallback through 2000px
before the 1000px last resort. Raising the cap to 3000px had left a
regression window: an image whose 2000px encode fits the byte budget
was sent at 1000px where the old 2000px cap used to send it at
2000px.

* fix(kimi-code): record pasted image dimensions in display space

The TUI paste path recorded attachment and original dimensions from its
raw header parser, which ignores EXIF orientation. For a portrait JPEG
the submit-time caption then contradicted the sent image's aspect and
region readback coordinates were axis-swapped. Dimensions now come from
the compression result, which reports display space on both the
compressed and passthrough paths; parseImageMeta remains only the
format/mime gate.

* feat(agent-core): add image compression and crop telemetry

Every image ingestion path now reports an image_compress event —
outcome (compressed / passthrough fast, guard, unsupported, unhelpful,
error), input/output formats, byte and pixel sizes, EXIF transposition,
and duration — and region readback reports an image_crop event with a
failure classification and the region's share of the original area.

Wiring is per call site via a new CompressImageOptions.telemetry
option, so the outcome split and timing are measured inside the
compressor while each caller only names its source: ReadMediaFile
(tool construction, like GrepTool), MCP tool results (McpOutputOptions),
server prompt ingestion (ICoreProcessService now exposes the host
telemetry client), ACP prompts (session track adapter), and TUI paste
(host.track adapter). Properties are numeric/enum only — never paths
or content — and a throwing client can never affect the compression
result.

* fix(agent-core): run the full JPEG quality ladder at fallback sizes

The fallback rescales encoded only at quality 20, so a JPEG whose
ladder failed at the fitted size collapsed straight to the lowest
quality even when the smaller size left budget headroom for a higher
rung (the realistic window is the 1000px step, where the 4x pixel
drop pays for q80/q60). Each fallback edge now walks the same
q80-to-q20 ladder as the fitted size.

* test: shrink heavy JPEG fixtures and add explicit timeouts

The fallback-ladder test runs ~11 pure-JS JPEG encodes and the EXIF
paste test decodes, rotates, and re-encodes a 6.5MP frame; both sat at
the edge of the 5s vitest timeout on CI runners. Narrower fixtures cut
the pixel area (the ladder test keeps its width above 2000px so the
full fallback chain still runs) and explicit 15s timeouts absorb runner
variance.

* fix(server): scope prompt image compression telemetry to the session

The prompt-ingestion image_compress events were emitted with the bare
host telemetry client, while every agent-side source inherits a
session-scoped client — so prompt_inline/prompt_file events could not
be correlated with their session. The route now wraps the client with
withTelemetryContext({ sessionId }) like rpc/core-impl does for
session telemetry.

* chore(changeset): consolidate image compression changesets

One entry covering the cap raise and the EXIF dimension fix, listed
for both the CLI and the SDK so the SDK changelog's compression
description (previously pinned at 2000px) stays accurate.

* fix: count goal creation turn (#1477)

* feat(kosong): support structured response formats (#1397)

* fix: clarify goal blocked audit guidance (#1481)

* feat(agent-core): discard loaded tool schemas on compaction (#1471)

Align progressive tool disclosure with the discard-on-compaction model:
compaction no longer rebuilds loaded dynamic tool schemas. The boundary
announcement re-lists every loadable name, the model re-selects what it
still needs, and a from-memory call to a no-longer-loaded tool is
rejected by preflight with select guidance.

This removes the keep-all rebuild and its half-trigger budget heuristics
entirely: the post-compaction floor is back to users + summary, which is
structurally outside the auto-compaction trigger band, and the guard
baseline degenerates to summary + reinjected reminders. Every downstream
mechanism already treated the empty loaded set as its consistent base
state (ledger scan, pending clear at the compaction boundary, deferred
extras, preflight wording), so this is a strict simplification.

Co-authored-by: fengchenchen <fengchenchen@moonshot.ai>

* fix(kimi-code): exit 1 when a headless (-p) turn fails (#1483)

Headless (`kimi -p`) failures could exit with code 0 when the event loop
drained during the shutdown cleanup (e.g. telemetry's unref'd retry backoff
when the network is blocked), because the rejection never reached the
process.exit(1) call. Set the failure exit code before any await in both
the run-prompt catch and the main catch, and keep the cleanup timeout ref'd
so the loop stays alive long enough for the rejection to propagate.

* feat(plugins): add Vercel plugin to marketplace (#1489)

* feat(web): support Enter key to confirm archive and other dialogs (#1490)

* feat(web): redesign cron reminder as a message bubble (#1480)

* feat(web): redesign cron reminder as a message bubble

Restyle the cron trigger notice as a right-aligned user-style message bubble that shows the scheduled prompt in full (wrapping across lines), with a small meta row beneath it for the schedule, status, job id and run time. Extract a shared MessageTime component used by both user messages and the cron reminder so the timestamp format and click-to-expand behavior stay consistent, and give the CronCreate/CronList/CronDelete tools distinct calendar icons.

* refactor(web): render cron reminders only as standalone turns

Remove the embedded cron block path from the web transcript projector so cron reminder fires always render through the standalone right-aligned bubble path.

* chore(web): simplify cron redesign changeset

* fix(web): composer model switch also updates global default model (#1491)

* fix(web): composer model switch also updates global default model

The composer model switcher still switches the active session's model via
POST /sessions/{id}/profile (awaited, so the model pill reflects the result),
and additionally fires POST /api/v1/config with { default_model } as a
fire-and-forget side effect so new sessions inherit the chosen default. The
config request is skipped when the model already matches the current default.

* fix(web): route ModelPicker overlay selection through the default-model update

The overlay opened from the composer's "More models" row (and /model) is a
continuation of the same switch flow, so its selection now also bumps the
global default model instead of only switching the active session.

* fix(web): only persist the default model after a confirmed session switch

setModel now returns whether the switch was accepted (true for the draft
path), so the composer flow no longer writes a stale or invalid model alias
into the global config when the session-level switch failed and rolled back.

* feat(web): prefix skill slash commands with skill: to distinguish them from built-in commands (#1492)

* ci: release packages (#1468)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix: surface provider auth error for unavailable models (#1506)

* fix: surface provider auth error for unavailable models

When an OAuth-managed model returns 401 after a forced token refresh, the token is valid but the provider rejected it for that model (the account lacks access). Emit provider.auth_error carrying the provider's message instead of auth.login_required with a misleading "OAuth login expired. Send /login" prompt.

* fix(agent-core): preserve provider auth errors through compaction

Treat provider.auth_error like auth.login_required in the compaction path so an auth rejection during compaction surfaces the provider's message instead of being wrapped as a generic compaction failure.

* ci: release packages (#1507)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* docs(changelog): sync 0.23.3 and shorten OAuth error entry (#1509)

* feat(kimi-web): add status-aware browser notifications (#1479)

* feat(kimi-web): add approval notification storage key and i18n copy

* feat(kimi-web): add approval notification helpers and tests

* feat(kimi-web): wire approval notifications and guard completion alerts

* fix(kimi-web): extract shouldNotifyCompletion helper and add tests

* feat(kimi-web): add approval notification settings toggle

* chore(kimi-web): add changeset and tidy notification module comment

- Align approval notification tag with spec (kimi-approval-${approvalId})

- Update module header to describe all three notification kinds

* fix(kimi-web): make notifications fire reliably

- Key completion notification tags by turn (sid + promptId) and question
  tags by request id, so a stale notification left in the notification
  center no longer swallows every follow-up alert in the same session
- Suppress notifications only while the window is actually focused, not
  merely visible (document.hasFocus() on top of visibilityState)
- Play the attention sound when a tool needs approval, matching the
  completion and question sounds

* chore(kimi-web): simplify changeset

* fix(agent-core-v2): serialize concurrent model catalog refreshes

Port v1 #1207's _refreshChain so a scheduled refresh and a manual one (or two overlapping manual ones) never race on reading/patching the persisted config.

Applied to both refresh entry points: ModelCatalogService.refreshProviderModels (scheduler + all/single-provider) and OAuthService.refreshOAuthProviderModels (OAuth-only, a separate service in v2).

* fix(agent-core-v2): dedupe workspace registry entries by root

Port v1 #1221: collapse registered workspaces that share a root in list(), preferring the entry whose id matches the current canonical encodeWorkDirKey, so a legacy workspaces.json (v1-compatible) does not render the same folder twice through GET /workspaces.

* fix(agent-core-v2): apply KIMI_CODE_CUSTOM_HEADERS and host identity headers

Port v1's provider-manager outbound header logic to agent-core-v2 so
`KIMI_CODE_CUSTOM_HEADERS` and host identity headers are applied to
outbound LLM requests, closing the migration gap from #1186:

- env `KIMI_CODE_CUSTOM_HEADERS` is the lowest-precedence header layer;
- host identity headers (User-Agent + X-Msh-*) are sent for Kimi
  providers, only the User-Agent for every other provider — a Kimi
  provider routed through the Anthropic protocol still gets the full
  set, matching v1;
- provider `customHeaders` always win on conflict.

Host headers are seeded by the CLI via `createKimiDefaultHeaders` and a
new `IHostRequestHeaders` App-scope token (defaulting to empty), so the
model resolver can layer them without the host threading them through
every call site.

* chore(agent-core-review): rename skill from kc-review to agent-core-review

* feat(kap-server): surface originating stack trace on error envelopes

- add optional `stack` field to errEnvelope and the envelope schema/interface; omitted when undefined so the wire shape stays byte-identical for callers without a stack
- thread `err.stack` through route error mappers plus the global and transport error handlers
- preserve `details` on `session.undo_unavailable` while adding its stack
- update tests to assert stacks are surfaced, reversing the prior no-leak contract

* fix: align v2 media and task compaction handling

* feat(agent-core-v2): sync shell mode and skill config parity (#1514)

* feat(agent-core-v2): record shell command context

- add ShellCommandOrigin and compaction handoff disposition
- extract IAgentShellCommandService from AgentRPCService
- keep AgentRPCService as a thin shell:run facade

* fix(agent-core-v2): align skill priority and sync docs

- restore project > user > plugin > builtin skill precedence
- sync Skill tool description and parameter docs from v1
- update write-goal and custom-theme builtin skill copy

* fix(agent-core-v2): restore undo and thinking telemetry

- track conversation_undo after undoHistory
- emit thinking_toggle with enabled/effort/from payload
- add coverage for both telemetry events

* feat(agent-core-v2): add skill directory config

- add extraSkillDirs and mergeAllAvailableSkills config sections
- introduce extra skill source and shared source priorities
- align kap-server workspace skill preview with session catalog

* feat(agent-core-v2): support explicit skill dirs

- add ISkillCatalogRuntimeOptions for SDK-style explicit skill dirs
- suppress default user/project discovery when explicitDirs are set
- resolve explicit dirs per session workDir via explicitFileSkillSource

* fix(agent-core-v2): fix configured skill dir resolution

- expand ~ using OS home for configured skill dirs
- honor explicitDirs in kap-server workspace skill preview

* fix(agent-core-v2): await config ready before skill discovery

- wait for config.ready before reading extraSkillDirs
- wait for config.ready before reading mergeAllAvailableSkills
- cover extra skill dir loading behind config readiness

* fix(agent-core-v2): keep skill config live after changes

- await config.ready in kap-server workspace skill preview
- reload user and workspace skill sources when mergeAllAvailableSkills changes

* fix(agent-core-v2): align goal budget handling

* fix(agent-core-v2): forbid model goal pauses

* fix(agent-core-v2): cap detached process output

* fix(kimi-code): drain v2 print subagents before exit

* fix: restore kap-server video upload compatibility

* fix(agent-core-v2): charge only output tokens against goal token budgets

Goal parity gap G2: v1 charges only per-step output tokens against a
goal's tokenBudget, while v2 summed all four usage buckets (cache read,
cache creation, other input, output), exhausting budgets orders of
magnitude faster under prompt caching and skewing persisted tokensUsed
counters. Align goal token accounting to output-only and drop the
unused tokenUsageTotal helper.

* fix: align server-v2 media file handling

* feat(agent-core-v2): allow coder profile to use MCP tools

* refactor(agent-core): introduce activity kernel and migrate turn lane

- add `activity` domain: `IAgentActivityService` (Agent turn lane machine),
  `ISessionActivityKernel` (Session admission, PR1 placeholder), and the
  `ActivityLease` that owns the turn `AbortSignal`
- turnService launches and cancels through the kernel lease; `Turn` now
  exposes `signal` instead of `abortController`
- agentLifecycle.remove drives `beginDisposal`/`settled` and waits for the
  in-flight turn to drain before releasing the agent scope
- add `activity.*` error codes; deprecate `turn.agent_busy` in favor of
  `activity.agent_busy`

* refactor(sessionLegacy): remove fork/compact/abort/archive pass-throughs

These four legacy session actions were thin delegations to the native v2
services (ISessionLifecycleService.fork/archive,
IAgentFullCompactionService.begin, IAgentRPCService.cancel) with no v1-only
projection to centralize. Drop them from ISessionLegacyService and call the
native services directly from the kap-server sessions route. updateProfile,
createChild, listChildren, undo and status stay in the adapter since they
carry real v1 adaptation logic.

* refactor(cli): run print-mode v2 on native agent-core-v2 services

- add native v2 print runner (v2/run-v2-print.ts) that consumes agent-core-v2
  DI services and awaits Turn.result directly
- extract shared print-mode rendering into prompt-render.ts for v1 and v2
- remove the V2PromptHarness/V2Session shim and v2->v1 event translation
- decouple initializeCliTelemetry from PromptHarness (homeDir/auth/track)
- add IAgentPromptLegacyService.submitAndSettle for authoritative completion

* refactor(session): serve v1 undo and children via native v2 services

- make IAgentPromptService.undo throw session.undo_unavailable with a structured
  reason; move the precheck into contextMemory
- add ISessionLifecycleService.createChild (fork + child markers) and
  ISessionIndex.list({ childOf })
- slim ISessionLegacyService to updateProfile/status (drop createChild,
  listChildren, undo)
- rewire kap-server session routes to the native services and map
  SESSION_UNDO_UNAVAILABLE

* refactor(cli): drop v1 sdk and telemetry deps from v2 print

- run-v2-print: use core ITelemetryService + CloudAppender instead of
  kimi-telemetry; remove kimi-code-sdk import (auth via IOAuthToolkit,
  config path from bootstrap, hook result via structural type)
- prompt-render: replace SDK HookResultEvent with a structural type so
  the shared renderer does not depend on the v1 SDK event shape
- telemetry: revert initializeCliTelemetry to its original signature now
  that v2 no longer calls it; keep v1 callers and assertions untouched
- update run-prompt and v2-run-print tests for the new wiring

* feat(activity): add session lane machine and agent snapshot projector

- implement SessionActivityKernel lane machine (restoring→active⇄quiescing→closing→disposed) with admission table, atomic quiesce+drain, beginClosing/settled, markActive
- start AgentActivityService lane at initializing; add markReady driven by agentLifecycle.create after bootstrap
- project LaneModel + EventBus facts into structured AgentActivitySnapshot (ActivityModel / setActivitySnapshot Op) with pending-approval and active-tool-call sets; emit agent.activity.updated
- add IAgentTurnService.launchWithLease; goal continuation acquires the lane before appending its prompt
- resolve pending interactions on turn.ended to avoid stranded awaiting_approval
- fullCompaction registers a background activity and checks the activity lane
- extract contextMemory publishSplice / isFullyUndoable / recoverFoldedLength helpers
- kap-server: map activity snapshot into legacy status and sessionEventBroadcaster

* fix(agent-core-v2): truncate over-long goal completion criteria

Goal parity gap G11: v1 silently truncates a goal's completionCriterion
to 4000 characters (the objective cap) before persisting, so an
over-long criterion never fails creation and cannot bloat every goal
reminder and record. v2 only trimmed whitespace and persisted arbitrary
lengths verbatim. Cap the normalized criterion at
MAX_GOAL_COMPLETION_CRITERION_LENGTH to match v1.

* fix(agent-core-v2): add goal error catalog info metadata

Align the GoalErrors domain with V1 by attaching the info block for the
seven goal.* error codes (title, retryable, public, action hints) so
errorInfo() surfaces them. Entries copied verbatim from the V1 error
catalog.

Gap: G43

* chore(nix): update pnpm deps hash

* fix(agent-core-v2): retain queued steers when a turn ends cancelled or failed

Align the prompt layer with V1's steer-buffer semantics: buffered steer
input now survives a turn that ends cancelled or failed and is flushed
into the next launched turn by the existing beforeStep hook, instead of
being silently dropped. The turn-result observation in the prompt
service existed only to perform that discard, so it is removed along
with the now-trivial launch wrapper; explicit clear() still discards
the queue.

Gap: G24

* fix(agent-core-v2): remove ask-user background mode

* fix(kap-server): align archived session restore

* chore(lint): fix type-aware lint errors

* chore(agent-core-v2): drop stray doResume debug log

* fix(agent-core-v2): defer prompts and steers while a full compaction is in flight

Align with V1's compaction gating: input arriving while a full compaction
holds the context (and no turn is active) used to launch a turn
immediately, appending assistant output that forced the in-flight
compaction to cancel. The prompt service now buffers such input and
replays it from a new onDidFinishCompaction hook that the compaction
worker runs in a finally, so the buffer drains on completion,
cancellation, and failure alike — the first deferred item launches a
turn and the rest join the steer queue.

The compaction service is resolved lazily instead of constructor-
injected: materializing it during prompt-service construction reorders
loop-hook registration and moves the full-compaction beforeStep hook
ahead of the hooks that let a freshly launched prompt land in context
before the auto-compaction check snapshots history.

Gap: G23

* fix(agent-core-v2): re-inject the goal reminder after full compaction

Align with V1: after a compaction rewrites the context, re-arm the
per-turn context injectors and run them before the compaction is marked
complete, so the first post-compaction request — including a replayed
deferred prompt's — already carries the goal reminder the summary
folded away. The injector service exposes injectAfterCompaction, which
re-arms the new-turn flag and injects immediately; the compaction
worker calls it after the system-prompt refresh and raises the
post-compaction token floor to include the re-injected reminders (the
pre-injection floor stays as the fallback when reinjection throws), so
the nothing-new-since-compaction guard does not re-trigger against a
shape that cannot shrink.

The injector is resolved lazily from the compaction service to keep
loop-hook registration order untouched across the dependency cascade.

Matches V1 verbatim including the existing quirk where an idle manual
compact yields a second reminder copy on the next turn's per-turn
injection; the parity test pins that behavior.

Gap: G14

* test(agent-core-v2): cover goal pause classification for provider errors

Port the missing end-to-end coverage: goal-driven turn failures pause
the goal with the exact per-class reason strings — provider rate limit,
provider connection error, provider authentication error, provider
safety policy block, and model configuration error (including the
forced 'LLM not set' substitution). Failures are driven through a real
turn with a throwing generate stub so the raw-error classification
feeding the pause reason is exercised, not just the mapper.

No source changes: the existing classification already matches the
reference strings verbatim.

Gap: G35

* feat: add progressive tool disclosure

* feat(cli): gate print-mode v2 behind KIMI_MODEL_EXPERIMENT_FLAG

- add KIMI_PRINT_V2_ENV / isPrintV2Enabled so `kimi -p` routes to the
  native agent-core-v2 runner through its own switch
- keep `kimi server run` server-v2 routing on isKimiV2Enabled
  (KIMI_CODE_EXPERIMENTAL_FLAG), decoupling the two
- update print-mode tests and comments to reference the new switch

* feat(cli): add KIMI_MODEL_OUTPUT_FORMAT for print-mode default

- resolve the effective `-p` format via resolveOutputFormat: the
  --output-format flag wins, then KIMI_MODEL_OUTPUT_FORMAT (prompt mode
  only), then text
- ignore the env outside prompt mode and reject invalid values eagerly
  through the friendly validation path
- apply the resolver on both the v1 and v2 print runners

* fix(agent-core-v2): count the goal-creating turn as the first goal turn

Goal parity gap G5: when the model creates or resumes a goal mid-turn,
v1 counts that ordinary turn as goal turn 1 at turn end (with a budget
re-check before the continuation driver takes over) and charges its
remaining step output tokens against the token budget. v2 only flagged
turns whose goal was already active at launch, leaving turnsUsed and
tokensUsed off by one turn in the model-initiated flow. Adopt the live
turn as a goal starter turn on activation: charge its post-creation
step output, count it once at turn end via incrementTurn, and block
instead of launching a continuation when that count exhausts the turn
budget.

* fix(agent-core-v2): remove model-initiated paused status from UpdateGoal

Goal parity gap G6: v1 reserves pausing for the user and runtime — its
UpdateGoal tool only accepts active/complete/blocked and rejects other
statuses with an invalid-status error. v2 still carried a leftover
'paused' enum option, a model pauseGoal branch, and matching tool
description wording from before v1 removed them. Drop the paused
option, port v1's runtime invalid-status guard, and align the tool
description with v1's.

* fix(agent-core-v2): deliver goal outcome prompts through the UpdateGoal tool result

Goal parity gap G7: when the model completes or blocks a goal, v1
returns the outcome prompt (stats plus final-message instructions) as
the UpdateGoal tool result with stopTurn, keys the one-shot final-
message continuation on that terminal tool result, and guards it with
the per-turn step budget so a capped turn ends 'completed' instead of
dying on max steps. v2 still used a pre-change leftover channel: terse
tool outputs plus goal_completion_summary / goal_blocked_reason system
reminders and a last-message-reminder continuation with no step-budget
check. Return the outcome prompts as tool output, drop the reminder
appends and their detection, key the continuation on the terminal
UpdateGoal result observed via the tool executor hook, and mirror v1's
hasStepBudgetRemaining guard. Also closes audit gaps G17 (max-steps
death) and G27 (actor-conditional reminders).

* fix(agent-core-v2): fail UpdateGoal as a tool error when no goal matches

Goal parity gap G8 (with user modification): v1 returns friendly
success-flagged no-op outputs when UpdateGoal targets a missing or
non-active goal, while v2 either let GOAL_NOT_FOUND escape from
resumeGoal or reported false success with stopTurn for complete and
blocked on a non-active goal. Per the user's decision these cases now
return error-flagged tool results in the same shape as the Edit tool's
old-string-not-found failure - v1's message texts ('Goal not resumed:
no current goal.', 'Goal not completed: no active goal.', 'Goal not
blocked: no active goal.') with isError and no stopTurn, so the model
sees a non-fatal failure and the turn continues normally.

* fix(agent-core-v2): settle active goals when the continuation relaunch fails

Goal parity gap P-B: the turn-ended subscriber that relaunches goal
continuation turns discarded every rejection, so a failed launch (for
example losing a race to a queued prompt) stranded the goal in status
active with nothing driving it. Keep the event-driven per-turn
continuation model but settle deterministically on failure: any
rejection out of the turn-ended handling now pauses the active goal as
actor system with reason 'Paused after goal continuation failure:
<message>', emitting the normal goal.updated event; the settle itself
never throws into the event bus. The busy-skip needs no settle: the
turn service clears its active turn before publishing turn.ended, so
the other live turn's own end reliably re-runs the relaunch check.

* fix(agent-core-v2): restore the fork-cleared goal system reminder

Goal parity gap G12: after a session fork, v1 tells the model the fork
has no current goal so it ignores stale active-goal reminders copied
from the source session; v2 cleared the goal silently through the
forked wire op and dropped the reminder. Track the fork boundary in a
derived (never persisted) wire model folded on both dispatch and
replay: a forked record that clears a copied goal marks the reminder
pending, and the post-replay pass appends v1's verbatim reminder text
with origin goal_fork_cleared exactly once - the appended reminder
record acknowledges the pending flag on later replays, so resumes never
duplicate it. Forks of sessions without a goal append nothing. The
forkGoal op itself stays pure.

* fix(agent-core-v2): preserve turn result details (#1531)

* feat(agent-core-v2): align defaultProvider fallback and clear-on-delete

- ModelResolverService falls back to the top-level defaultProvider config
  when a model pins neither providerId/provider nor an inline baseUrl
  (v1 parity).
- ProviderService.delete clears defaultProvider when removing the provider
  it points at, replacing v1's scattered call-site cleanups.
- defaultProvider rides as an unregistered top-level scalar config section,
  mirroring defaultModel (no schema, generic snake/camel passthrough).

* feat(agent-core-v2): synthesize legacy prompt lifecycle events

- emit prompt.completed/aborted/steered on the per-agent IEventBus so the v1-compatible WS edge can forward them (v2 core only emits turn.ended)
- bridge per-agent turn.ended into SessionInteractionService.cancelPendingForTurn via AgentLifecycleService (bus is Agent-scoped, no direct injection)
- extract agent create() helpers: assertCanCreate, buildAgentScopeExtras, igniteEagerServices, bindBootstrap
- finish assistant writer on PromptTranscriptWriter.flushAssistant
- ungate live session status idle->running->idle e2e test (v2 backend pulls real status)

* chore(agent-core-v2): align cron and skill prompts with agent-core

Drop the KIMI_CRON_NO_JITTER / KIMI_CRON_NO_STALE notes from the
CronCreate and CronList descriptions and the MAX_SKILL_QUERY_DEPTH
sentence from the Skill description, matching agent-core (v1) where
these were trimmed from the model-facing text in #1102. Code behavior
is unchanged; the env bypasses and the depth cap still exist in both
implementations. ULID/8-hex wording is left as-is for now.

* chore(agent-core-v2): drop legacy 8-hex mention from cron prompts

CronDelete and CronList descriptions now describe the task id as a ULID
only, removing the "(or legacy 8-hex)" qualifier from the model-facing
text. Code and tests are unchanged.

* chore(agent-core-v2): reorganize tests to mirror src layout

Move every file under test/ so its path mirrors the corresponding file
under src/ one-to-one (agent/, session/, app/, os/, persistence/,
_base/, activity/, wire/), and rename test files to match the basename
of the source file they cover. Test-only infrastructure with no src
counterpart (harness, snapshot, lint, dep-graph, tools/fixtures) stays
at the top level.

Rewrite imports after the move: src references use the #/ alias, while
test-to-test and cross-package relative imports are recomputed for the
new locations. No behavior changes.

* feat(config): add default permission/plan mode and yolo alias

- register `defaultPermissionMode` and `defaultPlanMode` config sections
- apply `defaultPermissionMode` when creating the main agent
- enter plan mode on fresh sessions when `defaultPlanMode` is true
- fold `yolo: true` into `default_permission_mode` on kap-server config write, derive `yolo` on read (yolo stays wire sugar, never a persisted domain)

* feat(agent-core-v2): add background mode to AskUserQuestion

Align with v1: the model can pass `background: true` to get a task_id
immediately while the question waits in the background for the user's
answer; completion is delivered to the agent automatically through the
task service's terminal notification.

- New QuestionBackgroundTask (AgentTask kind 'question') that runs the
  question request on a detached task and settles completed/failed/killed.
- AskUserQuestionTool gains the optional `background` schema field, the
  description suffix, an IAgentTaskService dependency, and a background
  execution branch whose output block matches v1 verbatim.
- Tests: harness injects a task-service stub, two legacy 'no background'
  assertions are flipped to the v1-aligned behavior, and three background
  cases (immediate task_id, settle completed, abort killed) are added.

* fix(agent-core-v2): synthesize default baseUrl for env-model provider

Align with v1: when KIMI_MODEL_NAME is set without KIMI_MODEL_BASE_URL,
the reserved __kimi_env__ provider now gets a per-type default baseUrl
(kimi -> api.moonshot.ai/v1, openai -> api.openai.com/v1; anthropic is
left unset so the SDK picks its default). Previously v2 left baseUrl
empty and later threw "missing a base URL" for the openai env-model
path, regressing v1's out-of-the-box behavior. An explicit
KIMI_MODEL_BASE_URL still wins.

* fix(agent-core-v2): restore UpdateGoal completion/blocked prompts and no-goal fallbacks

Align with v1: completing or blocking a goal now returns the dynamic
summary/blocked-reason prompt (buildGoalCompletionSummaryPrompt /
buildGoalBlockedReasonPrompt, already present in outcome-prompts.ts but
unused) instead of the static "Goal marked complete/blocked." text, and
all three statuses report the "no active/current goal" fallback when
there is nothing to transition.

* feat(agent-core-v2): add [image] config section for image compression

- add media-owned `image` config section (`max_edge_px`, `read_byte_budget`)
  with `KIMI_IMAGE_MAX_EDGE_PX` / `KIMI_IMAGE_READ_BYTE_BUDGET` env bindings
  (env > config.toml > default)
- add Agent-scope `ImageConfigBridge` that pushes the env-resolved section
  into the image-compress resolver seam on load and on change, so all call
  sites honor config without per-call wiring
- resolve `maxEdge` / read-byte-budget defaults in image-compress via the new
  seam; keep the support module config-agnostic
- apply the read-image byte budget in ReadMediaFile's default downscale path
  (previously fell back to the 3.75 MB provider ceiling)

* fix(agent-core-v2): align image compression defaults with v1 (#1508)

Port v1 #1508 into v2: lower the longest-edge downscale cap back to
2000px (v2 was stuck on the 3000px it had ported from an earlier v1
change) and make it overridable, add the 256 KB read-image byte budget
used by ReadMediaFile, and widen the over-budget fallback ladder to
[2000, 1000, 768, 512, 384, 256].

- MAX_IMAGE_EDGE_PX 3000 -> 2000, with KIMI_IMAGE_MAX_EDGE_PX env and a
  config-pushed value resolved via resolveMaxImageEdgePx.
- READ_IMAGE_BYTE_BUDGET=256KB with KIMI_IMAGE_READ_BYTE_BUDGET env and
  resolveReadImageByteBudget; ReadMediaFile's default compress path now
  uses it (region / full_resolution still honor IMAGE_BYTE_BUDGET).
- Test expectations that hard-coded 3000px / 1500px updated to 2000 /
  1000 to match the v1 behavior.

The [image] config.toml section is intentionally not added: the env
vars already cover the override path, and wiring a config section plus
a runtime push would add v2-specific scaffolding beyond v1.

* fix(agent-core-v2): restore HEIC/HEIF conversion guidance in ReadMediaFile

Align with v1: a HEIC/HEIF read is now refused up front with an
os-specific conversion command (sips / heif-convert / ImageMagick) so the
unsupported format never reaches the provider (which would reject the
whole session once it lands in history). The two guidance builders are
ported verbatim from v1, and the check sits after the image-capability
guard (using IHostEnvironment.osKind).

Also give the existing EXIF-rotation test a longer timeout: it does
heavy jimp encode/decode and was flaking around the default 5s boundary.

* feat(agent-core-v2): wire startBtw into the agent RPC API

Align with v1: expose `startBtw` on AgentAPI and delegate it to the
existing ISessionBtwService (already implemented and DI-registered as
SessionBtwService), so the /btw slash command can fork a side-question
child agent once the server runs on v2.

* chore(agent-core-v2): tidy misplaced and throwaway test files

- Remove resume-debug.test.ts: a one-off diagnosis script with a
  hardcoded local path, not a real test.
- Move streamTiming.test.ts to app/model/modelImpl.test.ts: it only
  exercises buildStreamTiming in app/model/modelImpl.ts.
- Rename wire/store.test.ts to wire/wireServiceImpl.test.ts to match the
  module it covers (there is no store.ts in src).

* fix(agent-core-v2): restore JSON Schema format validation in tool-args

Align with v1: replace the hand-rolled subset validator with v1's Ajv-
based implementation (draft-07/2019/2020 + ajv-formats), so tool-call
argument validation once again honors the JSON Schema `format` keyword
(and the full keyword set), not just the previously hard-coded subset.

- args-validator.ts is now byte-identical to v1 (93 lines, replacing the
  289-line hand-rolled subset).
- Adds ajv@^8.18.0 and ajv-formats@^3.0.1 (same versions as v1) plus the
  pnpm-lock.yaml update.
- The two call sites (compileToolArgsValidator -> validateToolArgs) keep
  working unchanged; a small test locks in format / required /
  additionalProperties / subset behavior.

* fix(agent-core-v2): restore JSON Schema format validation in tool-args

Align with v1: replace the hand-rolled subset validator with v1's Ajv-
based implementation (draft-07/2019/2020 + ajv-formats), so tool-call
argument validation once again honors the JSON Schema `format` keyword
(and the full keyword set), not just the previously hard-coded subset.

- args-validator.ts is now byte-identical to v1 (93 lines, replacing the
  289-line hand-rolled subset).
- Adds ajv@^8.18.0 and ajv-formats@^3.0.1 (same versions as v1) plus the
  pnpm-lock.yaml update.
- The two call sites (compileToolArgsValidator -> validateToolArgs) keep
  working unchanged; a small test locks in format / required /
  additionalProperties / subset behavior.

* chore(agent-core-v2): drop legacy 8-hex mention from CronDelete/CronList tool source

Match the prompt change in 5cc8e520f: the CronDelete parameter
description and invalid-id error now say "ULID" only (not
"ULID or legacy 8-hex"), and the CronList id doc comment likewise.
The validation regex is unchanged so loading any legacy 8-hex tasks
from disk still works.

* chore(agent-core-v2): remove resume-roundtrip test

It round-tripped restore over a hardcoded local dataset
(kimi-code-mini-bench/.vitest-results) that is not in the repo, so it
vacuously passed everywhere else. Removed at the original author's
request.

* test(agent-core-v2): raise timeout for slow image-compress invariant test

The fuzz-style invariant test now drives the full v1 fallback ladder
([2000, 1000, 768, 512, 384, 256]) for over-budget inputs, which takes
longer than the default 5s boundary in this environment. Give it 30s.

* chore(agent-core-v2): rename ambiguous test files

- agent/task/manager.test.ts -> taskManager.test.ts (no manager.ts in
  agent/task; disambiguate from taskService.test.ts).
- app/cron/persist.test.ts -> cronTaskPersistenceService.test.ts (mirrors
  the module it covers; agent/task/persist.test.ts mirrors
  agent/task/persist.ts, so it is left as-is).
- agent/contextMemory/message.test.ts -> message-history.test.ts (its
  describe is 'message history (IAgentContextMemoryService)'; the dir
  already names the domain).

* fix(agent-core-v2): preserve usage for aborted steps

* fix(agent-core-v2): resume-safe session reads and in-memory transcript

- sessionLifecycle: get/list no longer return a session whose cold resume is
  still in flight, so callers never observe a half-initialized handle; resume
  remains the way to await a fully restored handle
- messageLegacy: reduce the transcript from the main agent's in-memory wire
  journal instead of re-reading wire.jsonl; AgentWireRecordService now keeps
  the journal current with live dispatch so cold and live sessions both read a
  consistent, full transcript

* chore(agent-core-v2): drop stale micro-compaction references in comments

micro-compaction only exists in legacy agent-core; v2 has no such mechanism. Update two comments that still cited it:

- fullCompactionService: the real reason not to project here is that llmRequester already projects once.

- swarmService: context.spliced consumers no longer include micro-compaction bookkeeping.

* fix(agent-core-v2): report skill discovery diagnostics

* test(agent-core-v2): align plan mode parity fixtures

* fix: distinguish task output timeout from cancellation

* chore: fix test name

* fix(agent-core-v2): flush the wire persist queue before the record log

Since d5e1d76fc every wire append rides the async persist queue whenever
a blob service is registered, but AgentWireRecordService.flush() only
awaited the log store. Callers - including the session-close path -
could complete a flush while records were still in flight on the queue,
and record-order assertions in tests raced it. Await the wire service
flush, which drains the persist queue, before flushing the log.

* test(agent-core-v2): align the goal reminder boundary test with the continuation driver

The per-turn-boundary reminder test predates the goal continuation
driver and never passed: its second explicit prompt raced the
auto-launched continuation turn, which correctly holds the turn lane.
Treat the continuation turn as the second boundary and end it
deterministically by completing the goal through UpdateGoal, keeping
the once-per-boundary (never per-step) assertion.

* fix(agent-core-v2): stop goal turns gracefully when a hard budget is exhausted

Previously a goal whose hard budget was reached mid-turn was only
flipped to blocked while the turn kept running unbounded: the loop
continues unconditionally after a tool-calls step, and steer flushes or
Stop hooks could extend the turn indefinitely past the budget.

Now, when the over-budget step requested tool calls, a goal_budget_stop
system reminder is appended after the tool results telling the model the
goal is blocked (resumable via /goal resume), to stop immediately, that
further tool calls will be rejected, and to write a brief final status
message. The model gets exactly one grace step, during which tool calls
are answered with a soft rejection instead of executing. After the grace
step - or when the over-budget step had no pending tool calls - a new
AfterStepContext.stopTurn flag ends the turn, honored by the loop with
precedence over tool_calls and hook-set continue so nothing can extend
past the stop. The grace grant also respects maxStepsPerTurn so it can
never turn a budget stop into a max-steps turn failure.

Turn launch is gated as well: a prompt arriving while the active goal is
already over budget (e.g. after resuming an exhausted goal) blocks the
goal before the turn is marked goal-driven, so turnsUsed no longer
drifts, no spurious goal_continued telemetry fires, and the prompt runs
as an ordinary turn with the blocked-goal note injected.

This deliberately diverges from agent-core v1, which hard-stops with
zero grace and answers a prompt on an exhausted goal with a synthetic
model-less turn: budget overshoot is now bounded at one closing step in
exchange for consumed tool results and a user-facing wrap-up message.

* fix(agent-loop): stop looping on bare tool_calls signal

- remap tool_calls finishReason to 'other' when the provider emitted no tool call structure
- prevents re-issuing the model call until maxSteps on a bare tool_calls signal
- add loop test covering the v1 'unknown' turn-lifecycle behavior

* fix(kap-server): emit legacy background.task.* alias on /api/v1 ws

The v2 engine emits background-task lifecycle as `task.started` /
`task.terminated`, but v1 consumers (kimi-code TUI / `kimi -p`, node-sdk)
only handle `background.task.*` and silently dropped every task event when
talking to server-v2, while kimi-web handles the native spelling and has the
legacy one registered as known-but-unhandled.

Fan the legacy spelling out next to the native event in
SessionEventBroadcaster, reusing the same volatility so replay, journal and
the per-agent filter stay coherent between the two. kimi-web keeps the native
event and ignores the alias; the native /api/v2 stream is left unchanged.

Add a SessionEventBroadcaster test asserting both spellings are emitted.

* feat(agent-core-v2): port /init command from v1

- add Session-scope ISessionInitService that spawns the coder subagent,
  mirrors the run onto the main agent, reloads AGENTS.md and appends an
  init-variant system reminder, then flushes records
- add SESSION_INIT_FAILED error code and register the sessionInit domain in
  the layer map, package index and DI dependency graph
- drop the stale "flat (no subdirectories)" rule from the agent-core-dev skill

* feat(api-v2): add klient SDK and reflection-based channel registry

- replace per-method actionMap (resource:action) with a channel registry: each Service registers once by decorator id and all methods are invoked by reflection
- routes move from /api/v2/:sa to /api/v2/:service/:method across HTTP routes and the WS protocol
- add @moonshot-ai/klient: typed core/session/agent client over the HTTP channel that reuses agent-core-v2 service interfaces
- register klient in flake.nix and pnpm-lock; refresh kap-server tests, e2e, and the apiSurface snapshot

* refactor(agent-core-v2): drive turns by draining a StepRequest queue

- add StepRequest / StepRequestQueue: the loop drains one batch per step,
  folding mergeable requests (steers) into the driver's step; a step that
  ran tools enqueues a ContinuationStepRequest, a plain message enqueues
  none, so the turn completes when the queue empties
- replace AfterStepContext.continue with explicit enqueue; a failed step is
  retried by head-inserting its driver request
- move prompt's private steer queue onto the loop via PromptStepRequest /
  SteerStepRequest / RetryStepRequest, which materialize their context
  messages at pop time (image-compression captions reroute to reminders
  on materialization)
- goal and externalHooks orchestrate continuations by enqueueing requests
  instead of setting ctx.continue; a request's message only lands when the
  loop pops it, so skipped or aborted launches leave no orphan messages
- remove the now-unused CancellationError
- delete the reworked turn tests (turn.test.ts, turn-ready.test.ts) and the
  cron test suites

* refactor(agent-core-v2): translate provider errors at the model boundary

- add translateProviderError in app/protocol/errors and apply it once in
  ModelImpl.request: raw provider failures become coded KimiErrors with the
  raw error preserved as cause and HTTP fields in details; abort shapes pass
  through untouched
- move provider-error mapping out of _base/errors/serialize so _base no
  longer imports llmProtocol; toErrorPayload/fromErrorPayload now round-trip
  cause chains recursively, capped at depth 8
- move context.overflow from LoopErrors to ProtocolErrors (wire code
  unchanged); move LoopError and the max-steps helpers into loop/loop.ts
- consolidate isAbortError into _base/utils/abort, dropping the duplicates in
  retry, cloudTransport, and the question/subagent task tools
- add unwrapErrorCause; classify retryability and HTTP status on the
  unwrapped cause in llmRequester and full-compaction
- align task-notification tests with enqueue-only delivery: drain the loop
  queue with one turn and assert on task.notified instead of prompt steer
- protocol: add recursive cause to KimiErrorPayload with a lazy zod schema

* docs(agent-core-dev): add commit-align workflow, drop DI dependency map

- add commit-align.md subskill: triage one main-branch commit against v2
  (aligned / partial / missing / not-applicable) and link it from SKILL.md
- delete docs/di-scope-domains.puml and the rendered svg, and drop the
  keep-the-map-in-sync requirement from verify.md, align.md, commit-align.md,
  and packages/agent-core-v2/AGENTS.md

* refactor(agent-core-v2): move turn lifecycle into agent loop

- make loop admission, cancellation, and completion own turn execution
- extract step retry into a loop error recovery service
- preserve failed-step context and expose retry delay events

* refactor(agent-core-v2): centralize loop turn scheduling

- add queued turn and step lifecycle handles with explicit admission modes
- move continuation and retry scheduling behind the loop service
- consolidate legacy prompt scheduling into the prompt domain
- align kap-server routes and tests with the new loop contract

* feat(klient): add WebSocket transport for calls and event streams

- add WsSocket: persistent /api/v2/ws transport with hello handshake,
  heartbeat answers, per-call timeouts, and auto-reconnect that
  re-subscribes active listens; bearer token rides the
  kimi-code.bearer.<token> subprotocol for browser compatibility
- add WsKlient / WsChannel exposing core/session/agent scopes and
  listen(event, handler) over the shared socket
- add Klient#ws() lazy singleton with WebSocketImpl injection
- bind global fetch in HttpChannel to avoid "Illegal invocation" in browsers

* refactor(agent-core-v2): unify hook names to on{Will,Did}Xxx convention

- loop: beforeStep -> onWillBeginStep, afterStep -> onDidFinishStep
- toolExecutor: onWillExecuteTool -> onBeforeExecuteTool (ToolWillExecuteContext -> ToolBeforeExecuteContext)
- prompt: onWillSubmitPrompt -> onBeforeSubmitPrompt
- permissionMode: onChanged -> onDidChangeMode
- wireRecord: onRestoredRecord -> onDidRestoreRecord, onResumeEnded -> onDidFinishResume
- terminal: onData -> onProcessData, onExit -> onProcessExit

* feat(kap-server): synchronously refresh all providers before listing models

- GET /api/v1/models now awaits refreshProviderModels({ scope: 'all' })
  before returning the model list, so the response always reflects the
  latest provider model metadata
- refresh failures are logged and swallowed, falling back to the
  persisted catalog instead of failing the request

* refactor(agent-core-v2): convert one-way notification hooks to Events

Replace fire-and-forget OrderedHookSlot hooks with Emitter/Event-based
notifications for consumers that only observe, never intercept:

- usage: hooks.onDidRecord -> onDidRecord event
- permissionMode: hooks.onDidChangeMode -> onDidChangeMode event
- fullCompaction: hooks.onDidFinishCompaction -> onDidFinishCompaction event
- wireRecord: hooks.onDidFinishResume -> onDidFinishResume event
- agentLifecycle: hooks.onDidStopAgentTask -> onDidStopAgentTask event,
  announced via new notifyAgentTaskStopped() called by mirrorAgentRun

Interception-capable slots (onWillStartAgentTask, onWillCompact,
onDidRestoreRecord) stay as ordered hooks.

* fix(agent-core-v2): route FetchURL through the Moonshot fetch service when logged in

When the managed Kimi provider has an oauth ref, WebFetchService builds a MoonshotFetchURLProvider (bearer token + host identity headers) with the local fetcher as fallback, re-reading login state on every call; logged-out setups keep the local fetcher.

* fix(agent-core-v2): forward host identity headers with WebSearch requests

WebSearchProviderService now passes the host's IHostRequestHeaders (User-Agent + X-Msh-* device identity) as default headers to the Moonshot search provider, mirroring v1's kimiRequestHeaders.

* fix(cli): seed host identity headers into the experimental v2 server

The v2 boot path (kimi server run with the experimental flag) now seeds the CLI's Kimi identity headers (User-Agent + X-Msh-* device identity) into the engine through kap-server's seeds option, so outbound model, WebSearch, and FetchURL requests carry the same identity as direct CLI runs. kap-server's own package version is 0.0.0, so the identity has to come from the CLI.

* feat: validate workspace roots and auto-launch task notification turns

- task: idle terminal notifications now use activeOrNewTurn admission,
  launching their own turn instead of waiting for the next user prompt
  (matches v1 turn.steer)
- workspaceRegistry: createOrTouch rejects missing or non-directory roots
  with fs.path_not_found, so a phantom cwd never reaches session creation
- kap-server: map FS_PATH_NOT_FOUND to protocol error 40409 on the session
  and RPC surfaces
- server-e2e: migrate the v2 smoke test from the local ServerClient to the
  typed Klient
- misc: switch loop/prompt clear() iteration to .slice(), align terminal
  event handler names, and tidy klient examples

* fix(kap-server): emit idle/aborted session status on turn end

The v1 WS broadcaster only re-emitted event.session.status_changed(running)
on turn.started and never emitted the idle/aborted transition on turn.ended.
kimi-web treats that event as the single source of session status (its
turn.ended projector deliberately does not synthesize idle), so a session
stuck at 'running' after the turn finished — most visibly for background
tasks, where ISessionActivity keeps reporting non-idle while the detached
task lives and even a REST pull never corrected it.

Re-emit event.session.status_changed after turn.ended on the same dispatch
queue, mapping reason cancelled/failed/blocked to aborted and otherwise
idle (previous_status 'running'), matching v1's _computeStatus. Update the
broadcaster tests and harden the wsV1Resync test helper so non-matching
frames no longer strand a waiter's timeout.

* feat(ws): add Service event streaming with waitUntil handshake

- listen messages accept a service name; kap-server resolves the Service
  via resolveService and subscribes through its onUpperCase member
- add listen_result acknowledgement and per-listen error reporting
  (onDidListenError) so failed subscriptions surface to the client
- support onWill-style events: payload carries eventId/signal/waitUntil,
  the client replies with event_result and the server can event_cancel
- klient proxy maps onUpperCase members to channel.listen; WsChannel
  shares one remote subscription across first/last listeners
- channel.call now forwards the complete argument array

* feat(agent-core-v2): add typed telemetry event registry

- register business telemetry events with compile-time property contracts
- redact sensitive values and reject invalid cloud properties
- centralize cloud appender construction and version context

* feat(kap-server): add channel introspection endpoint

- add describeChannels() to channelRegistry: scope derived from the scoped
  DI registry, public methods/getters enumerated from the prototype chain
  (framework plumbing and events excluded)
- export ChannelDescriptor / ChannelMethodDescriptor from the contract
- serve GET /api/v2/channels so clients (kimi-inspect) can render a
  dynamic service browser without handwritten method lists
- cover with rpc test, e2e channel registry test, and API surface snapshot

* feat(kap-server): expose declared parameter names in channel descriptors

- add `params` field to ChannelMethodDescriptor, parsed from function source
- extract declared parameter list via Function#toString with paren-depth tracking
- cover param introspection in rpc and server-e2e channel registry tests

* fix: adapt v2 print runner and tests to enqueue prompt API

- run-v2-print: drive turns via IAgentPromptService.enqueue() and
  handle.launched; detect hook-blocked prompts via handle.completion;
  read the LoopRunResult type discriminator in formatNativeTurnFailure
- bootstrap stubs: add clientVersion required by IBootstrapService
- v2-run-print test: mock enqueue, stub IBootstrapService for
  createCloudAppender, add track2 to the telemetry stub
- node-sdk test: cover prompt.completed/aborted/steered in the
  exhaustive event switch

* feat(agent-core-v2): introduce graded error taxonomy for os, storage, and wire layers

- add `os.fs.*` codes with `HostFsError` and the `toHostFsError` boundary translator
- add `os.process.*`, `storage.*`, and `wire.*` domains with coded error classes
- register new codes in the protocol `KimiErrorCode` union
- translate raw OS and parse failures into domain codes across services, persistence backends, and kap-server transport
- rename `KimiError` to `Error2` in the v2 base errors
- remove obsolete kimi-csdk init example

* test(agent-core-v2): wait for MCP connectAll instead of a fixed tick

The MCP initial-connect assertion used a single setTimeout(0) tick, but
connectAll is gated on Promise.all([resolveSessionMcpConfig(...),
enabledMcpServers()]); the session-config side walks the real filesystem
(project-root search + mcp.json reads), which does not settle within one
macrotask under CI load. Use vi.waitFor so the test is robust on CI.

* fix(minidb): publish WAL value pointers only after the frame is durable

In valueMode 'disk' the write path installed a disk ValueLoc using the
predicted WAL offset before the frame's bytes were flushed (appendLoc
returns the offset synchronously; the writev lands on a later tick). Under
load a concurrent compaction snapshot could read a pointer past the WAL end
and fail with a short read. Apply the record as an in-memory ref first and
only publish the disk pointer after appended.done resolves, guarded against
WAL rotation and stale record seqs.

* fix(kap-server): report missing agents as agent.not_found

- resolveScope now throws Error2 for missing session/agent instead of
  returning undefined, distinguishing agent.not_found from session.not_found
- map AGENT_NOT_FOUND onto the session-not-found protocol envelope for v1
  parity

* refactor(cli): gate print-mode v2 on KIMI_CODE_EXPERIMENTAL_FLAG

- remove KIMI_PRINT_V2_ENV / isPrintV2Enabled and the dedicated
  KIMI_CODE_EXPERIMENT_FLAG switch
- route `kimi -p` to the agent-core-v2 runner via isKimiV2Enabled,
  the same master switch that gates server-v2

* fix(agent-core-v2): map hostFs/storage error codes at server boundaries

- unwrap the HostFsError cause before matching EISDIR in FileEditService,
  restoring the "is not a file" edit output broken by the error taxonomy
- map os.fs.* codes to the closest v1 wire codes in the kap-server fs
  route and /api/v2 transport instead of collapsing to INTERNAL_ERROR
- map storage.io_failed / storage.locked to PERSISTENCE_FAILURE in the
  /api/v2 transport

* fix(agent-core-v2): serialize config writes and reloads

A User-target set/replace mutates raw/rawSnake, awaits persist(), then
rebuilds effective, while a reload() replaces all three wholesale from disk.
Without serialization a reload whose file read resolves inside a write's
persist window (before the atomic rename lands) restores the stale pre-write
state, so the write's post-persist rebuild drops the just-written domain from
effective. This surfaced as POST /config responses missing the field they had
just written when the startup model-catalog refresh's reload() raced the
write. Run User-target writes and reloads through a promise chain so they can
no longer interleave.

* feat(agent-core-v2): align telemetry with the v1 wire format

- rename tool_call_dedupe_detected to tool_call_dedup_detected
- emit turn_ended on every turn end; add mode/provider/protocol tags
  and interrupt_reason to turn_started/turn_interrupted
- enrich api_error with alias, protocol tags, and input_tokens
- tag tool_call with dup_type via an executor-side map (avoids the
  executor/dedupe DI cycle)
- rename compaction usage fields to input_tokens/output_tokens
- add context_projection_repaired, session_started, and
  session_load_failed events

* feat(agent-core-v2): add lifecycle transition machine

- add guarded synchronous and asynchronous state transitions
- support commit, rollback, cleanup, and compensation actions
- cover transition conflicts, action ordering, and failure aggregation

* fix(agent-core-v2): harden plugin load, install, and update check paths

- Degrade plugin consumption reads to empty when installed.json fails to
  load, surface plugin.load_failed with a repair hint on management calls,
  and recover after an explicit reload; serialize the initial load and
  mutations so concurrent first callers share one load.
- Clean up zip temp dirs on every failure path, report the original
  source in zip/github manifest errors, and roll back to the previous
  managed copy when an install or persist fails.
- Restore managed Kimi endpoint env injection for stdio plugin MCP
  servers.
- Check plugin updates concurrently with per-repo failure isolation and
  10s timeouts, track branch installs by commit SHA, and stop false
  update reports for tag/SHA pins.
- Throw plugin.not_found from getPluginInfo and the manager's
  not-installed paths.
- Count plugin skills through the real skill discovery path.
- Re-sync context injection positions after silent wire replay so cold
  resumes do not duplicate injections, and fire plugin session-start
  reminders only when the plugin skill source finishes refreshing.

* fix(agent-core-v2): use the v2 coded error type for plugins

* fix(cli): align print session with background completion API

* fix(kap-server): stabilize catalog and session status updates

- keep model catalog reads free of provider refresh side effects
- broadcast deduplicated session lifecycle and interaction statuses
- cover catalog loops and global session status fan-out

* test: stabilize CI integration cleanup

---------

Co-authored-by: _Kerman <kermanx@qq.com>
Co-authored-by: 7Sageer <7sageer@djwcb.cn>
Co-authored-by: qer <wbxl2000@outlook.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Kaiyi <me@kaiyi.cool>
Co-authored-by: Luyu Cheng <2239547+chengluyu@users.noreply.github.com>
Co-authored-by: STAR-QUAKE <99738745+starquakee@users.noreply.github.com>
Co-authored-by: fengchenchen <fengchenchen@moonshot.ai>
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
2026-07-12 21:44:04 +08:00
github-actions[bot]
b5c236d00f
ci: release packages (#1546)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-12 20:28:38 +08:00
Haozhe
c6e02daf42
feat(background): add print_background_mode steer for multi-turn -p runs (#1497)
* feat(background): add print_background_mode with steer for multi-turn -p runs

- add `[background].print_background_mode` (`exit`/`drain`/`steer`) and
  `print_max_turns`; when unset, falls back to `keep_alive_on_exit = true`
  mapping to `drain`, preserving existing behavior
- core: `Session.handlePrintMainTurnCompleted()` returns `finish`/`continue`;
  in `steer` mode the run stays alive so a background-task completion
  `turn.steer`s the main agent into a new turn (matching background
  subagents), bounded by `print_wait_ceiling_s` and `print_max_turns`
- cli: print driver follows every main turn instead of only the first and
  defers `finish()` until the run quiesces or a limit is hit
- plumb `handlePrintMainTurnCompleted` through node-sdk RPC; docs + tests
2026-07-12 10:47:35 +08:00