Commit graph

175 commits

Author SHA1 Message Date
7Sageer
d40d0d305d
refactor(agent-core-v2): make undo domain-owned (#2055)
* refactor(agent-core-v2): rebuild undo as wire-level journal rewind

Replace the compensating context.undo op with a wire-layer rewind
primitive: a log.cut control record with a persisted target, applied
uniformly by the wire during fold. Turn boundaries become first-class
(TurnIndexModel indexing turn.prompt record positions), models declare
a temporal classification (rewindable), and a single
IAgentRewindService owns the undo pipeline (quiesce -> precheck ->
cut -> reconcile) with all entry points converged.

- wire: log.cut record, rewindable model flag, re-fold rebuild;
  OpApplyContext.recordIndex for position-aware reducers
- rewind service: aborts the active turn, cancels in-flight
  compaction, preserves the pending queue, rebases measured tokens,
  reconciles lastPrompt, tracks conversation_undo
- todo list, plan mode, task-notification delivery and the turn index
  now rewind together with the undone turns
- transcript reducer applies cut ranges so snapshot/messages surfaces
  stay consistent with the model context
- REST/RPC/debug undo entry points converge on the rewind service;
  TUI parses the v2 undo-unavailable error shape
- legacy context.undo records keep replaying for old journals

* refactor(agent-core-v2): keep undo domain-owned

* refactor: enhance /undo functionality for consistency and safety, including todo list rollback and improved event handling

* chore: clean up undo changeset artifacts

* refactor: rebuild rewind consistency

* fix: make conversation undo durable and consistent

* fix(agent-core-v2): stabilize undo restoration

* fix: keep TUI undo on legacy error contract

* refactor(agent-core-v2): drop unused full compaction cancel API

Undo now rejects with session.busy while compaction runs instead of
cancelling it, so the awaitable cancel() added for the earlier rewind
semantics has no callers left. Remove it from the interface and
implementation; the RPC cancel path keeps using the task abort
controller directly.

* fix(agent-core-v2): remove injected context on undo

* chore(agent-core-v2): regenerate wire manifest

* docs(agent-core-dev): rename rewind to undo in layer table

* fix(agent-core-v2): undo prompt-owned image reminders

* refactor: remove transcript undo reconciliation

* fix(kap-server): map undo busy errors

* refactor(agent-core-v2): rename undo participant registry and attribute checkpoint depth

- Rename IAgentConversationUndoReconciliationRegistry to
  IAgentConversationUndoParticipantRegistry (conversationUndoParticipants).
- Return the limiting model from checkpointDepth and include it in the
  SESSION_UNDO_UNAVAILABLE details; report checkpoint_lost instead of
  compaction_boundary when no compaction explains the missing depth.
- Add a registry invariant test: every model reacting to context.* ops
  must be registered via defineCheckpointedModel or explicitly exempt.

* Delete .changeset/fix-undo-injections.md

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

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-07-27 11:09:26 +08:00
Chen, Yicun
0d00a07c02
fix(web): preserve selected text when copying over HTTP (#2120)
Co-authored-by: chenyicun <chenyicun@msh.team>
2026-07-24 16:38:36 +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
qer
154e082488
fix(web): keep transparent images readable over a checkerboard canvas (#2022)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
White-on-transparent images disappeared on light surfaces and
black-on-transparent ones on dark surfaces (markdown inline images, tool
result media, and the file preview's large view). Add a shared
--media-alpha-canvas token — a checkerboard of two mid-gray squares mixed
from --color-bg/--color-text, each >=3:1 against both white and black —
and paint it directly on <img> so opaque images stay pixel-identical and
only transparent pixels reveal the canvas.

Also let check-style's no-gradient-text skip custom-property definitions
(a token is never rendered text), and register the new tokens in the
design system view.
2026-07-21 22:20:21 +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
qer
d71bf9e5a5
feat(web): add a cache invalidation note to the model switcher (#1940)
* feat(web): add a cache invalidation note to the model switcher

* feat(web): show the cache note in the mobile settings sheet
2026-07-20 13:43:05 +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
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
qer
9e1248416f
fix(web): remember the thinking level per model (#1838)
* fix(web): remember the thinking level per model

Persist kimi-web.thinking as a JSON map of model id to level instead of a
single global value, and resolve the active level against the model's
catalog (stored pick when still declared, else the model default) at
loadModels, setModel, and on active-model changes via a watcher.

Fixes the empty, unresponsive thinking picker shown for a model that does
not declare a previously stored level (e.g. a max-only model with a stale
global 'low').

* fix(web): resolve a submitted prompt's thinking from its own model

submitPromptInternal and the steer path read the single active-session
rawState.thinking, so a queue drain for a background session submitted the
level of whichever session the user had switched to since enqueueing —
the same cross-model leak on the submit path. Thinking now joins model and
the per-session modes in being resolved from the prompt's own session
model (its stored pick when declared, else the catalog default), falling
back to the active value only when the model has left the catalog.

* fix(web): keep model switches from persisting derived thinking defaults

setModel routed the resolved level through applyThinkingLevel, which
writes per-model storage unconditionally — a switch to a model with no
saved pick stored the catalog default as if it were an explicit choice,
pinning the user to it across later default changes, and the rollback
path did the same write for a switch that never happened. Model switches
now update the in-memory level only; storage writes stay with
setThinking, the explicit picker path.

* fix(web): resolve thinking per target session on the BTW and skill paths

sendSideChatPromptOn combined the captured parent's model with the
active-session level, so a session switch during the startBtw await sent
the BTW first turn at the wrong model's effort — resolve it from the
parent's own model, falling back to the active value off-catalog, same
as the other submit paths.

activateSkill carries no thinking either, so the daemon ran skills at
the session profile effort, which can predate the per-model restore the
picker now shows. Persist the resolved level to the session profile
first, mirroring the new-session skill path; that path itself now
resolves against the new session's model instead of the raw active
value.

* fix(web): keep per-model thinking picks in memory as the runtime truth

The resolver re-read localStorage on every submission, letting storage —
not the displayed state — decide what the daemon receives: with storage
unavailable (policy/quota) an explicit pick reached the UI while every
submit path fell back to the catalog default, and a pick made in another
tab silently changed what this tab submits mid-session.

Per-model picks now live in an in-memory map hydrated from localStorage
at startup; explicit picks update it first and persist best-effort
(read-modify-write merge, so concurrent tabs' entries still survive).
localStorage is only hydration plus persistence — another tab's pick can
no longer alter this tab's runtime level.

* fix(web): carry the legacy global thinking pick forward as a fallback

Pre-map installs stored a single global level as a raw string; the map
parser dropped it, silently resetting the user's explicit preference to
the catalog default on upgrade. The legacy value is now carried as a
fallback for models without their own entry — validated against each
model's catalog at resolution, so effort models keep the user's pick
while a max-only model still falls through to its default and can never
be trapped by it.

* fix(web): keep the legacy thinking fallback across the first map rewrite

The first explicit pick after an upgrade rewrote the raw legacy value
into a map containing only that one model, so the next reload saw a
nonempty map and dropped the legacy fallback for every other model.
The migrated value now lives inside the map under a '*' key that no
real model id can collide with: per-model entries override it, and
rewrites persist it alongside them instead of deleting it.

* fix(web): persist only the changed thinking pick on write

Overlaying the whole in-memory map on write could revert a newer pick
made in another tab for a model this tab still held a stale copy of.
Write the changed entry alone (delta-style, like saveUnread), carrying
only the migrated legacy '*' fallback along so it survives the first
rewrite into map format.

* fix(web): abort skill activation when the thinking profile persist fails

persistSessionProfile surfaces failures itself and resolves, so awaiting
it never blocked a following activation: a failed /profile write still
launched the skill at the session's stale effort. It now resolves a
success flag; both activation paths (existing session and new-session
draft) gate on it and skip activating when the persist fails, without
reporting a second, synthetic error.

* refactor(web): persist the new-session skill profile's thinking once

startSessionAndActivateSkill persisted the resolved thinking and then
activateSkill persisted it again unconditionally — a redundant profile
update and status refresh whose transient failure would false-veto an
activation whose prerequisite profile was already applied. Thinking is
now written by activateSkill alone (the single, gated writer); the
draft patch carries only model, plan/swarm and permission.

* fix(web): throw an Error instance for the profile-persist sentinel

oxlint --type-aware (only-throw-error) rejects throwing a Symbol; the
identity-based sentinel works the same as a shared Error instance.

* fix(web): resolve an empty session model through the default before skills

session.model can be '' transiently (daemon profile echo), so
activateSkill fell back to the raw active-view level; in the new-session
flow a concurrent switch could persist another model's effort onto the
target session. Normalize '' through the configured default_model first,
same as the prompt/BTW/steer paths.
2026-07-17 19:56:26 +08:00
qer
03021b6db7
fix(kimi-web): stop the prompt queue from ghost-sending stale attachments (#1833)
* fix(kimi-web): stop the prompt queue from ghost-sending stale attachments

Sending while a turn was running queues prompts locally. A failed flush
left entries stuck, and every later session open silently re-submitted
them with their old file attachments. Gate the drain on locally
witnessed turns, re-drive stuck entries FIFO from real events with a
failure budget, restore merged entries on steer failure, persist the
queue per session so a refresh loses nothing, and converge queues
across tabs via storage events.

* fix(kimi-web): merge cross-tab queue updates by entry id (review P1/P2)

Whole-record adoption could silently discard a prompt another tab
enqueued concurrently. Queue entries now carry a stable id and enqueue
timestamp; adoption union-merges snapshots by id, a shared TTL'd removal
set stops flushed/discarded entries from resurrecting (and being
flushed twice), an in-flight marker covers the submit window, and
manual reorders re-stamp timestamps so they survive merges. The flush
failure budget is also tracked per entry instead of per session, so
removing or reordering the head no longer hands its strikes to the
next entry.

* fix(kimi-web): single-flusher queue entries, merge order convergence, forget guard (review round 2)

Turn-end events reach every open tab and the server accepts concurrent
submissions as distinct prompts, so two tabs holding the same adopted
entry could both submit it. Entries now record their owner tab and only
the owner flushes (ownerless legacy entries flush anywhere); an idle
send adopts entries left behind by closed tabs so a stranded queue can
still drain. Cross-tab merges now keep the newer enqueuedAt copy per id
so a manual reorder converges instead of ping-ponging writes, and the
flush failure callback no longer resurrects a queue whose session was
forgotten while the submit was pending.

* refactor(kimi-web): drop the cross-tab queue persistence, keep the minimal fix

Cross-tab queue sync over localStorage is a distributed-systems problem
(claim/lease, conflict merge, ownership) that keeps generating review
findings far beyond this PR's scope. Remove the persistence/hydration/
adoption machinery wholesale; keep the bug fix proper: gated queue
drain, event-driven FIFO retry with a per-entry failure budget, steer
queue restore, and the forgotten-session flush guard. Durable queued
prompts will be designed together with the server-side prompt queue.

* chore: align the changeset wording with the reduced scope

* fix(kimi-web): never duplicate on ambiguous submit failures; advance after drop

Two review findings: (1) restoring merged queue entries after ANY steer
failure could re-submit prompts the daemon had already accepted when the
failure was a lost response — submits now report ok/rejected/uncertain
and restores + flush re-queues happen only on definitive daemon
rejections, while ambiguous failures drop the entry (the failure toast
still tells the user); (2) dropping an exhausted queue head no longer
strands the entries behind it — the new head is submitted immediately,
carrying its own failure budget.
2026-07-17 18:57:38 +08:00
Haozhe
56a321d4d1
fix(workspace): dedupe workspaces across Windows path spelling variants (#1809)
* fix(workspace): dedupe workspaces across Windows path spelling variants

The same directory reached the workspace registry as distinct strings on
Windows (drive-letter casing, typed vs on-disk casing, slash style), and
every identity check compared exact strings, so one folder could appear
as multiple workspaces with sessions split across hash-keyed buckets.

- add workspaceRootKey (slash-normalize + case-fold Windows-shaped
  paths) in agent-core, agent-core-v2, and the web app, and compare
  roots by identity key everywhere instead of exact strings
- registry createOrTouch folds alias spellings onto the existing entry
  instead of minting a new workspace id; session buckets reuse the
  registered id via a resolver in the v1 session store
- list endpoints expand alias buckets (resolveAliasIds /
  resolveAliasWorkDirs, including session-index-only spellings) so
  previously split workspaces list all sessions and counts under one
  merged group; session_index entries use the registry-resolved id

* fix(workspace): fold the runtime touch path and drive-root identity keys

Two gaps in the Windows path-spelling folding, both reachable in the
v1 session-create flow:

- touchWorkspaceRegistry minted the alias spelling's id outright; the
  freshly persisted alias entry then became the resolver's preferred id
  on the next create, splitting sessions into a duplicate bucket again.
  It now folds onto the identity-matching existing entry, mirroring the
  registry service.
- workspaceRootKey stripped trailing separators before testing the
  Windows shape, so a drive root (C:\) collapsed to C: and escaped the
  case-fold. The shape test now runs before the strip in all three
  copies (agent-core, agent-core-v2, web).

* fix(workspace): unfold symmetric operations that escaped the identity key

Two asymmetric spots left the folded comparison one-sided:

- the web app matched hidden roots by folded key but cleared them on
  re-add by exact string, so hiding C:\Foo and re-adding c:\foo kept
  the workspace hidden forever; clearing now folds too
- registry delete (both engines) removed and tombstoned only the exact
  id, so a legacy split sibling resurfaced as the directory's
  representative on the next list; delete now removes every registered
  spelling sharing the root's identity key and tombstones the full
  alias set (registered ids plus session-index spelling mints), so the
  session-index merge cannot resurrect the directory either
2026-07-17 14:56:06 +08:00
Haozhe
319001ae5c
refactor: remove git detection from workspace wire and folder browse (#1787) 2026-07-16 21:05:19 +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
Haozhe
78967e283d
refactor(model-catalog): drop WS catalog-changed event; refresh on picker open (#1772)
- kap-server: remove event.model_catalog.changed from the v1 WS union,
  broadcaster forwarding, and the zod event registry
- web: refresh all providers (POST /providers:refresh) before loading
  models when the model picker opens, replacing the event-driven refresh
- keep domain publishers, the protocol schema, and the web receiver for
  compatibility with older daemons
2026-07-16 16:34:32 +08:00
_Kerman
7042af3571
fix(web): keep the sidebar resize handle above the chat composer background (#1766) 2026-07-16 16:17:20 +08:00
_Kerman
df75a0f5c2
refactor(agent-core-v2): derive session busy from agent activity (#1751) 2026-07-15 23:33:58 +08:00
qer
e885aec7ff
feat(web): show detailed diagnostics for model request failures (#1756)
Surface the coded provider error the daemon already sends: a semantic
title per error code, the provider's raw message, and expandable
diagnostics (error code, HTTP status, request ID, SDK error name) with
copy support, instead of a bare text-only toast.
2026-07-15 23:07:38 +08:00
qer
1186686554
fix(web): dedupe background subagent rows in the agents dock (#1754)
* fix(web): dedupe background subagent rows in the agents dock

* fix(web): seed agent identity on late task registration and prefer REST output in task fold

* fix(web): backfill terminal output to folded background subagent rows

* fix(web): sync subagent phase when the REST fold makes a row terminal
2026-07-15 23:07:23 +08:00
Haozhe
0b790cdc05
feat(web): allow attaching any file type and fix CSP on non-loopback binds (#1731)
* feat(web): allow attaching any file type in chat

- Composer, paste, and drop no longer filter out non-media files;
  arbitrary files upload as generic icon chips and are submitted as
  file content parts
- kap-server materializes file parts into the session's attachments
  dir and replaces them with a path reference, so the model opens the
  file with the Read tool on demand instead of receiving inline bytes
- Images rejected by the provider format gate (SVG, AVIF, ...) are now
  persisted and referenced by path instead of being dropped with a
  notice; uploaded file names are sanitized before hitting disk

* fix(server): stop CSP from blocking web bootstrap script and fonts

- Move the anti-FOUC bootstrap from an inline <script> in index.html
  to /boot.js: CSP 'self' never covers inline scripts, while a classic
  same-origin script keeps the same render-blocking timing
- Allow data: in font-src — KaTeX and the Inter / JetBrains Mono
  Variable fonts ship @font-face data URIs in their distributed CSS
- Set explicit form-action, base-uri, and frame-ancestors, which do
  not fall back to default-src
- Add a regression test asserting the served index.html carries no
  inline scripts or inline event handlers

* fix(web): normalize empty attachment MIME so extensionless files submit

Files with an empty File.type (Makefile, LICENSE, other extensionless
or unknown types) stored mediaType: '' on the chip, and the submit
fallback used ?? which does not catch empty strings — the wire schema
requires a non-empty media_type, so the prompt was rejected. Normalize
to application/octet-stream at attachment creation, adopt the
server-recorded MIME after upload completes, and make both submit
mappings use || so reloaded chips with '' are covered too.

* feat(web): render all user-turn attachments as chips

* feat(web): attach files by dropping them anywhere in the window

* refactor(web): share one attachment chip between composer and chat bubble

* fix(web): neutral attachment chips, paperclip attach icon, and clickable file chips

* fix(web): drop the extension badge from attachment chips

* fix(web): use the tabler paperclip for the attach button

* fix(web): whitelist attachment previews, reject active document types

Clicking a file chip navigated a new tab to a blob: URL of the uploaded
bytes whenever the type looked browser-renderable. blob: inherits the
web origin, so a text/html or image/svg+xml attachment would execute
same-origin script with the daemon credential (localStorage) and a live
window.opener.

Preview is now restricted to inert types (pdf, non-SVG images, video,
audio, non-HTML text), the blob is re-wrapped with the whitelisted MIME
instead of trusting the recorded content-type, and window.opener is
severed. Non-whitelisted types no longer silently download: the chip
reports 'unsupported' and the pane shows a transient hint.

* fix(web): recover file attachment chips from the server notice

The kap-server prompt route replaces file parts with an "Attached file
…" text notice before enqueueing, so after any snapshot resync the
attachment chip degraded into raw notice text leaking the absolute
server path — unlike image/video uploads, which already recover their
chip from the <video|image path> tag. Parse the notice the same way:
the materialized basename carries the file id, so the chip becomes
clickable again (and editable back into the composer); inline-base64
notices (content-hash named, no file id) still collapse into a
non-clickable chip instead of raw text.

The notice wording is now a client/server contract — flagged on
buildAttachedFileNotice.

* fix(web): preserve UUID file ids when rebuilding attachment chips

* fix(web): skip unresendable file chips when loading attachments for edit

---------

Co-authored-by: qer <wbxl2000@outlook.com>
2026-07-15 21:54:48 +08:00
qer
b89d385fa5
fix(web): confirm dialogs respond to Enter and await async actions (#1744)
Some checks are pending
CI / test (3) (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 (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Desktop release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
* fix(web): confirm dialogs respond to Enter and await async actions

The confirm dialog's initial focus was resolved from the Button
component's $el, which is a text node in dev builds (the component has
a template-root comment, so it renders as a fragment). Focus fell back
to the header close button, so Enter cancelled instead of confirming.
Resolve the initial focus with a CSS selector on the confirm button
instead.

ConfirmOptions now accepts an async action: the dialog stays open with
a loading state (cancel/Esc/overlay suppressed) until the work settles.
The archive-session, remove-workspace, and delete-provider confirms
move from the menu components into App.vue so the dialog can await the
actual client call.

* fix(web): block superseding a confirm dialog while its action runs

A second confirm() during an in-flight action would replace the busy
dialog and inherit the global busy state, opening inert until the first
action settled. Resolve the new request unconfirmed instead.
2026-07-15 20:04:50 +08:00
qer
6eb8e13417
fix(kimi-web): improve mobile safe-area handling (#1459)
* fix(kimi-web): improve mobile safe-area handling

* fix(kimi-web): restore dock-height fallback where ChatDock is absent

* fix(kimi-web): pin the app shell to the visual viewport height

* fix(kimi-web): pin the app shell to the visual viewport

* chore: add changeset for mobile safe-area fixes
2026-07-15 14:50:01 +08:00
qer
b6ae0a1054
fix(web): surface session list load failures (#1641)
* fix(web): surface session list load failures

* fix(web): preserve partial session pages
2026-07-15 13:58:03 +08:00
qer
d8d4e8ceb5
fix(web): keep long streams responsive (#1643)
* fix(web): keep long streams responsive

* fix(web): drop queued events for archived sessions
2026-07-15 13:47:11 +08:00
qer
b24a347e20
fix(kap-server): carry the live subagent roster in the session snapshot (#1719)
* fix(kap-server): carry the live subagent roster in the session snapshot

* fix(kap-server): clear the subagent roster on the next main turn start

* fix(kap-server): exclude background subagents from the snapshot roster

* fix(kap-server): finalize live roster entries when the main turn aborts

* fix(kap-server): drop roster entries when foreground subagents detach

* fix(web): expand the swarm card by default while subagents are running

* docs(kap-server): qualify the roster-clearing durability claim
2026-07-15 12:40:30 +08:00
qer
de493aeec9
fix(web): use upward chevron for dock card expand buttons (#1715)
* fix(web): use upward chevron for plan card expand button

* fix(web): use upward chevron for question card expand button
2026-07-14 23:06:15 +08:00
qer
20b69724aa
fix(web): make code block copy work over plain HTTP (#1714) 2026-07-14 22:31:36 +08:00
qer
9eff230f97
fix(web): show errors for failed actions and add daemon request/operation logging (#1711)
Some checks are pending
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (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 / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
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
CI / typecheck (push) Waiting to run
CI / lint (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
* fix(web): show errors for failed actions and add daemon request/operation logging

* fix(web): dedupe identical operation-failure toasts to avoid flooding

* Revert "fix(web): dedupe identical operation-failure toasts to avoid flooding"

This reverts commit 54fcfdcc97a034d7236d61cbb3664292a8966c64.
2026-07-14 19:17:28 +08:00
qer
ab22a2adf0
fix(web): show just the thinking level name in the model pill (#1689)
Drop the "thinking:" / "思考:" prefix from the effort suffix and
capitalize the level via effortLabel, matching the segment labels.
2026-07-14 17:00:09 +08:00
qer
0f64b4dcc4
fix(web): submit thinking level verbatim and drop the hardcoded default (#1673)
* fix(web): submit thinking level verbatim and drop the hardcoded default

Align kimi-web's thinking-level handling with the TUI:

- Submit the stored level as-is on every prompt path (prompt, steer,
  skill activation, BTW side chat) instead of coercing it onto the
  target model's declared efforts.
- No stored preference (undefined) instead of a hardcoded 'high'
  default: prompts omit the thinking override and the daemon resolves
  the config/model default, same as an unset [thinking] in the TUI.
- Model switcher pre-selects the target model's own default level when
  switching models; re-selecting the current model keeps the level.
- Display the effective level (stored value, else the model default)
  in the composer, mobile sheet, and /status panel.

* chore(web): remove the dead dev:stub script

The stub daemon (dev/stub-daemon.mjs) no longer exists, so the
dev:stub npm script and its docs references were dead weight.

* fix(web): pin the model default thinking level and persist picks globally

- With no stored preference, loadModels() pins the active model's
  catalog default_effort as a concrete in-memory value, so what the
  UI shows, what prompts submit, and what the session runs always
  agree. localStorage stays reserved for levels the user picked.
- setThinking and model switches now also write the daemon-wide
  [thinking] config (same mapping as the TUI's thinkingEffortToConfig),
  so sessions created by other clients inherit the pick.
2026-07-14 15:04:28 +08:00
Luyu Cheng
490303db16
fix(web): refine goal mode controls (#1669)
* fix(web): refine goal mode controls

* fix(web): hide goal progress without budget

* fix(web): use design system for goal cancellation

* docs: document web goal controls

* fix(web): remove collapsed goal actions from tab order
2026-07-14 13:34:37 +08:00
qer
5eb62178b3
feat(web): add session diagnostic export (#1646)
* feat(web): add session diagnostic export

* fix(web): bound session export resources

* fix(web): make session export atomic

* feat(web): add session export entry to session menus with item icons
2026-07-14 12:48:28 +08:00
Haozhe
0303b82c3e
fix: align v1 protocol handshake and tool_result media passthrough (#1630)
* fix(protocol): make server_hello heartbeat_ms optional

kap-server dropped the server-initiated WS heartbeat and no longer emits
heartbeat_ms in server_hello, but the published v1 schema still required
it, so spec-compliant clients rejected the handshake before subscribing.

- mark heartbeat_ms optional in serverHelloPayloadSchema (advisory only)
- add a ws-control test for a server_hello without heartbeat_ms
- align kimi-web WireServerHello and the server-e2e handshake assertion

* fix(agent-core-v2): pass media parts through tool_result projection

- keep raw kosong content-part array for tool results carrying
  image/video/audio parts instead of flattening to text
- restore ReadMediaFile media rendering after session reload/resume

* chore: add changeset for optional server_hello heartbeat_ms

* docs(agent-core-v2): move tool_result media rationale to module header

Per the agent-core-v2 comment conventions, comments live solely in the
top-of-file block — move the media-passthrough rationale out of the
buildProtocolContent JSDoc into the module header.
2026-07-13 23:13:48 +08:00
qer
e91a616f21
fix(web): dedupe optimistic user message against snapshot resync (#1620)
* fix(web): dedupe optimistic user message against snapshot resync

* fix(web): match snapshot messages by identity
2026-07-13 21:54:08 +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
Luyu Cheng
32cbd0cf61
fix(web): let workspace picker fit its content (#1611)
* fix(web): size workspace picker from full content

* chore: add web workspace picker changeset

* refactor(web): use intrinsic workspace picker sizing

* fix(web): cap workspace picker to conversation pane
2026-07-13 21:39:16 +08:00
liruifengv
098623ed9f
chore(web): drop the /help, /model, /provider, and /permission slash commands (#1615)
* chore(web): drop the /help, /model, /provider, and /permission slash commands

* chore: drop the changeset
2026-07-13 20:52:19 +08:00
qer
e223549a79
fix(web): make mid-turn delta offsets step-relative (#1609)
* fix(web): make mid-turn delta offsets step-relative

Reset in-flight text and client stream alignment at step boundaries so
resync seeds only the current step instead of duplicating prior steps.

* fix(web): dedupe resync-seeded messages by normalized content

The exact-JSON content signature missed duplicates when the two copies
differed by thinking signature, tool progress, part boundaries, or the
tool set (finished parallel tools leave running_tools). Reduce content
to concatenated stream text plus sorted tool-call ids and treat a
covered subset as the duplicate, merging the seed's tool progress into
the existing cards before dropping it.
2026-07-13 20:47:56 +08:00
qer
f338fcdac4
fix(web): restore swarm member list after page refresh (#1589)
* fix(web): restore swarm member list after page refresh

* chore: add changeset for swarm roster refresh fix
2026-07-13 20:40:52 +08:00
liruifengv
2da45fc419
fix(web): restore the goal card after a page refresh (#1606)
* fix(web): restore the goal card after a page refresh

* fix(web): assert goal endpoint URL without stringification

* fix(web): skip goal recovery write when a live goal event wins the race

* fix(web): track goal events with a per-session version so clears win the recovery race
2026-07-13 19:49:58 +08:00
_Kerman
dc309a7dfb
fix(web): keep context usage live on the v2 engine (#1601) 2026-07-13 18:00:58 +08:00
_Kerman
4feca6b073
fix(agent-core-v2): align rate-limit retries with v1 (#1598) 2026-07-13 17:50:23 +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
qer
49a8c84a49
feat(web): cap markdown table column width at 700px (#1587)
* feat(web): cap markdown table column width at 700px

* fix(web): clamp table column width through the cell content box
2026-07-13 13:35:07 +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
qer
6fc1deb453
fix(web): wide markdown tables scroll internally and break out on desktop (#1577)
* fix(web): scroll wide markdown tables inside their own wrapper

* feat(web): break wide markdown tables out of the reading column

* fix(web): sample the full TOC rail for wide-table occlusion

* refactor(web): use rect overlap for the TOC occlusion check
2026-07-12 20:08:57 +08:00
qer
b1942bd571
fix(web): keep the connecting splash and retry the first-load auth check (#1574)
* fix(web): retry the first-load auth check behind the connecting splash

* fix(web): fall back instead of retrying deterministic 4xx auth-check failures

* fix(web): show the connection error on the splash while retrying

* fix(web): keep the first quick auth-check failure silent on the splash

* chore: remove accidentally committed dist-web symlink

* fix(web): hold onboarding until first load settles; drop unsupported 4xx auth fallback
2026-07-12 19:54:21 +08:00
qer
3a7aad653f
fix(web): finish local prompt state from session snapshot after a reconnect (#1572) 2026-07-12 19:01:30 +08:00
qer
9d96b538bf
fix(web): scroll wide markdown tables inside their own wrapper (#1575) 2026-07-12 18:52:26 +08:00
qer
5a208cb041
fix(web): auto-enable default thinking effort when switching to an effort-capable model (#1475)
Some checks are pending
CI / typecheck (push) Waiting to run
CI / build (push) Waiting to run
CI / test (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
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 / Desktop release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(web): auto-enable default thinking effort when switching to an effort-capable model

* chore: add changeset

* fix(web): preserve thinking off when reselecting current model
2026-07-12 18:44:39 +08:00