Commit graph

1240 commits

Author SHA1 Message Date
7Sageer
03e1e114db fix(mcp): drain OAuth refreshes during shutdown 2026-08-20 00:42:03 +08:00
7Sageer
fce8716ad8 fix(mcp): honor workspace CRUD context and refresh timing 2026-08-19 23:59:04 +08:00
7Sageer
430879b3f3 fix(klient): normalize MCP OAuth errors 2026-08-19 23:16:50 +08:00
7Sageer
39ea35b477 fix(agent-core-v2): isolate stdio MCP probes 2026-08-19 22:59:56 +08:00
7Sageer
a9121216de test(node-sdk): cover offline MCP auth statuses 2026-08-19 21:34:55 +08:00
7Sageer
4805c7bebe fix(agent-core-v2): harden MCP management readiness 2026-08-19 21:12:01 +08:00
7Sageer
4715a8bda5 refactor(agent-core-v2): strip comments from the MCP management plane files 2026-08-19 18:53:31 +08:00
7Sageer
21b3011be6 refactor(node-sdk): write session MCP persists through the engine config store 2026-08-19 18:53:31 +08:00
7Sageer
07b03eaa74 fix(agent-core-v2): settle early and cancelled MCP OAuth callbacks 2026-08-19 18:53:22 +08:00
7Sageer
29ca69a42d merge: main into feat/v2-mcp-management-plane 2026-08-19 18:23:49 +08:00
7Sageer
2ea2ef62e4
feat(agent-core-v2): report the bound model alias in subagent_created telemetry (#3086) 2026-08-19 17:13:03 +08:00
Haozhe
16499408d5
refactor(agent-core-v2): extract externalHooks into a scope-organized feature (#2805)
Move the external hook services out of app/externalHooksRunner,
session/externalHooks, and agent/externalHooks into
features/externalHooks, assembled as the ExternalHooksFeature unit:

- services live under per-scope subdirectories (app/, session/, agent/);
  shared pure helpers (types, hook matching/dispatch, process spawn,
  prompt result rendering) live under internal/
- the runner and the two observers are contributed through the Feature
  seams (ScopeUnits materialization); the hooks config section stays on
  the static import=register channel
- update the package entry leaf exports, the plugin domain imports, the
  kap-server events-zod import, and the affected tests; regenerate the
  state manifest
2026-08-19 16:52:51 +08:00
Haozhe
cb8a7e5f81
feat(kap-server): expose engine feature list on /api/v1/meta (#3085) 2026-08-19 16:32:45 +08:00
7Sageer
571bcc2f75
fix(agent-core-v2): register needs-auth MCP authenticate tool regardless of settle timing (#3083) 2026-08-19 16:32:26 +08:00
Rick
35befdcef2
fix(vscode): multi-select question jumps to next after only one answe… (#3079)
* fix(vscode): multi-select question jumps to next after only one answer selected

* chore: add changeset

---------

Co-authored-by: gaoyuan <gaoyuan@moonshot.ai>
2026-08-19 16:27:07 +08:00
qer
be8e017597
fix(agent-core-v2): emit subagent.spawned after task registration (#3005)
* fix(agent-core-v2): emit subagent.spawned after task registration

The spawned signal previously fired at launch, before the run's task
registration, so clients learned the agent id with no task id to bind
cancel/status actions to; a failed registration also left a spawned row
behind for a run that never registered. Emit it only after registerTask
succeeds and carry the task id on the event.

* fix(agent-core-v2): keep spawned ahead of started for Agent-tool runs

The TUI drops subagent.started until spawned has established the row,
and a failed registration must not leave a started row behind with no
terminal event. Defer the mirrored started dispatch so the Agent tool
can emit it itself after registration and spawned.

* fix(agent-core-v2): void the deferred started dispatch

* fix(kap-server): key Agent-tool transcript rows by the registered task id

Transcript-protocol clients suppress the raw task.*/subagent.* session
events, so they only saw a subagent row keyed by agent id that cannot
address /tasks/{id}, plus a second row once task.started landed. Key the
spawned row by the task id it now carries, fold task.started and the
subagent lifecycle back into it, and keep the agent-id path for spawns
without a registration (swarm/session-init/tower). Statement-level
ordering notes move to the file headers per package convention.

* test(agent-core-v2): split the spawned/started ordering contract into its own test

* fix(kap-server): keep subagent result details across task termination and drop stale task mappings on taskless respawns

* fix(kap-server): recover the agent-to-task association from a backfilled task.started

* fix(kap-server): seed pre-attach Agent task mappings on the transcript binding

* fix(kap-server): seed the full in-flight task row on transcript bind, not only its id

* docs(agent-core-v2): name the state-domain event dispatcher in the Agent tool header

* style(kap-server): drop comments in transcript services per the no-comments lint rule
2026-08-19 14:58:55 +08:00
Luyu Cheng
01eeacb59b
feat(kimi-code): specialize the WaitFor tool's transcript display (#3066)
* feat(kimi-code): specialize the WaitFor tool's transcript display

* feat(agent-core-v2): emit status progress while WaitFor is pending

* fix(kimi-code): route WaitFor dimming through the TUI theme

* feat(kimi-code): support replaceable status updates in tool progress

* fix(kimi-code): forward status progress to subagent activity surfaces

* fix(agent-core-v2): drop the redundant undefined from ToolUpdate.replace

* fix(kimi-code): honor replace semantics in the subagent live status path

* test(agent-core-v2): drive the WaitFor progress test through a manual tick

* fix(kap-server): mirror ToolUpdate.replace in the ws event schema

* refactor(agent-core-v2): expose the WaitFor progress scheduler as a public seam

* fix(kimi-code): pass child wait statuses without the trailing newline

* feat(agent-core-v2): tick the WaitFor progress status every second

* feat(agent-core-v2): format WaitFor progress durations as 1m 15s

* feat(agent-core-v2): omit zero seconds and minutes in WaitFor durations
2026-08-19 14:15:38 +08:00
7Sageer
c908a39e32
refactor(agent-core-v2): unify the loop-event fold into one core with two materializations (#3018)
* refactor(agent-core-v2): unify the loop-event fold into one core with two materializations

The loop-event stream was reduced by two hand-mirrored state machines:
loopEventFold.ts for the live/replayed context and contextTranscript.ts
for the full transcript behind the messages endpoints, kept in sync by
comments alone and already drifted (transcript dropped tool-result note
metadata and never closed a dangling tool exchange at step.end).

createLoopEventFold now owns the shared state machine once (settle,
pending tool exchanges, deferred appends, vacuous tracking) and both
views plug in as LoopEventFoldSink materializations. New parity tests
pin the foldedLength === live length invariant the endpoints splice on.

* fix(agent-core-v2): drop every removed prompt's injections on multi-turn transcript undo

The transcript undo only walked prompt-owned injections off the oldest
counted anchor, so with count > 1 an injection owned by a newer removed
prompt (e.g. an image-compression caption) survived the display undo
while the live context removed it. Collect every counted anchor's id
during the walk and sweep their owned injections afterwards, keeping
the transcript's 'prompt-owned ones leave with their prompt' contract
for every count and matching the live view.

* refactor(agent-core-v2): drop module headers from the context fold modules

The comment-free zone lint only allows JSDoc on exported symbols.

* fix(agent-core-v2): recover fold state after rehydration

* fix(agent-core-v2): scope undo injections to their prompt

* fix(agent-core-v2): settle open transcript frames when compaction lands mid-fold

An overflow-triggered compaction arrives with the failed attempt's
frame still open. The transcript appended the summary marker and reset
the fold but left the frame, so a vacuous partial stayed in the entries
while the live context dropped it, and a pending tool exchange lost its
interrupted result. Settle through the shared fold core at the marker
instead: close pending tool calls, drop or seal the open frame, then
append the summary. recoverFoldedLength recomputes the absolute count
right after either way.

* fix(agent-core-v2): keep legacy compaction recovery on the pre-settlement count

A legacy context.apply_compaction record (compactedCount without
keptUserMessageCount) recovers foldedLength as 1 + (foldedLength -
compactedCount), and the live legacy tail shape keeps the unsettled
open frame inside history.slice(compactedCount). Settling the fold for
those records shifted foldedLength by the settlement delta before the
recovery read it, leaving the transcript count one off the live
context. Gate the settle to modern records; legacy records keep the
previous freeze-and-reset behavior.
2026-08-19 14:09:22 +08:00
7Sageer
f13f379044
fix(agent-core-v2): stop advertising unavailable ReadMediaFile to non-multimodal models (#3046)
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-vscode-legacy (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
* fix(agent-core-v2): stop advertising unavailable ReadMediaFile to non-multimodal models

* fix(agent-core-v2): honor the effective tool policy before advertising ReadMediaFile

* fix(agent-core-v2): keep media-unavailable guidance reason-neutral and within the active toolset

* fix(agent-core-v2): recommend MCP fallbacks only when an active MCP tool exists

* fix(agent-core-v2): stop naming other tools in Read descriptions and errors

* fix(agent-core-v2): drop tool roster from plan agent prompt
2026-08-19 12:43:50 +08:00
Kai
6595a6989a
fix(kosong): emit null content for assistant messages with no text in chat completions (#3052)
Assistant messages carrying only tool calls were serialized without a
content key (JSON.stringify drops undefined), which strict
chat-completions validators such as LiteLLM reject with a 422,
permanently poisoning the session. Emit content: null for such messages
in both the kosong and agent-core-v2 converters, matching the shape
OpenAI responses use alongside tool_calls. The think-only empty-string
behavior in agent-core-v2 and both Kimi providers' deliberate content
omission are unchanged.
2026-08-19 11:39:32 +08:00
Luyu Cheng
8440801de4
feat(agent-core-v2): add the WaitFor tool for waiting on background tasks (#3060)
* feat(agent-core-v2): add the WaitFor tool for waiting on background tasks

* fix(agent-core-v2): mark WaitFor deliveries only after formatting succeeds

* fix(agent-core-v2): cancel losing waits once the WaitFor race resolves

* test(node-sdk): project WaitFor out of the v1-v2 resume parity roster

* fix(agent-core-v2): gate WaitFor goal guidance behind the wait_for flag

* fix(agent-core-v2): gate WaitFor goal guidance on actual tool availability

* fix(agent-core-v2): enforce the wait_for flag at WaitFor execution time

* fix(agent-core-v2): consult the live tool policy in the WaitFor availability check
2026-08-19 11:26:59 +08:00
qer
cdaa80b778
docs(changelog): sync 0.37.2 from apps/kimi-code/CHANGELOG.md (#3063)
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-vscode-legacy (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
2026-08-19 02:05:30 +08:00
github-actions[bot]
c41fadf0f7
ci: release packages (#3062)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-19 01:37:58 +08:00
qer
5c661f4610
chore: sync web dist from code-app (#3061)
code-app: 221c548c98587fa1bd2aef458e1e20aaceece587
2026-08-19 01:33:29 +08:00
Haozhe
b478e95a2c
feat(agent-core-v2): reject duplicate scoped service registrations (#3057) 2026-08-19 00:17:12 +08:00
liruifengv
c11da4fbd8
docs(changelog): sync 0.37.1 from apps/kimi-code/CHANGELOG.md (#3055) 2026-08-18 22:50:19 +08:00
github-actions[bot]
1e553fc73b
ci: release packages (#3049)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-18 22:29:43 +08:00
7Sageer
95cede82b4
fix(agent-core-v2): stop the legacy video resolver from shadowing the media resolver (#3053)
#2593 replaced AgentVideoResolverService with the image+video
AgentMediaResolverService and reduced videoResolverService.ts to a pure
deprecated alias with no DI registration. #2909's squash merge restored
the pre-#2593 file wholesale, bringing back the legacy class and its
registerScopedService call. Both classes then registered the same token
('agentVideoResolverService') at the Agent scope and the legacy
video-only resolver won on the production import order, so image
kimi-file:// references reached the provider unresolved. Gateways
reject the unknown scheme with a 400 ("unsupported image url"), the
media-strip fallback then hid the image from the model, and pasted
images only worked on undo-resend via the inline base64 fallback.

Delete the legacy alias files and their index exports, drop the stale
alias assertion, and pin the behavior with a klient e2e regression:
a kimi-file image prompt part must reach the provider as a data: URL,
never verbatim.
2026-08-18 22:18:49 +08:00
7Sageer
c9c34ae5a8
fix(kimi-code): upload pasted videos to the daemon file store (#3047)
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-vscode-legacy (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(kimi-code): upload pasted videos to the daemon file store

Video paste staged a cache copy and submitted a bare file:// video_url,
which the v2 engine no longer resolves, so the submission failed and the
persisted history retried it on every turn. Mirror the image flow
instead: upload the paste to the daemon file store in the background and
submit a kimi-file:// reference that the engine's prompt intake
materializes. A video whose upload is still in flight, failed, or
expired now refuses the submission with an actionable error, since video
bytes have no inline fallback form.

* test(kimi-code): fix MessageDriver recallStashedMedia signature
2026-08-18 20:52:27 +08:00
liruifengv
589ed5467c
docs(changelog): sync 0.37.0 from apps/kimi-code/CHANGELOG.md (#3048)
* docs(changelog): sync 0.37.0 from apps/kimi-code/CHANGELOG.md

* docs(changelog): align English 0.37.0 entries with reviewed wording
2026-08-18 19:49:36 +08:00
github-actions[bot]
04944f380a
ci: release packages (#2932)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-18 19:19:18 +08:00
liruifengv
e31b3a335e
chore: sync web dist from code-app (#3043)
* chore: sync web dist from code-app

code-app: 1fb57f0ee3c424675d8768dcc70e56f5c953cd9f

* chore: sync web dist from code-app

code-app: 93da508d079b0118cc0338da97dcb738f0a3d46f

Adds the web session admin page. Built from the code-app PR #261 branch tip before its merge; the squash-merged main tree is expected to be identical (will be checked at merge time and rebuilt here if not).

* chore: correct the code-app trailer of the previous sync commit

The previous commit's trailer had a mistyped code-app SHA. The dist content is byte-identical to a build of code-app main at the merge below (tree verified identical to the branch tip it was built from), so the watermark for the next sync is:

code-app: 025805b33f1e87a4bc479574971c6177ad1403a0
2026-08-18 19:15:01 +08:00
liruifengv
8c865f4817
feat(kimi-code): support automatic updates for native installations via staged swap (#2994)
* feat(kimi-code): support automatic updates for native installations via staged swap

Native (SEA) installs previously could not self-update on Windows and
relied on 'curl | bash' re-install on Unix. Replace both with a staged
swap updater:

- startup swaps in a staged binary (verified against the release
  manifest sha256, smoke-checked via --version) and re-execs it, so the
  running process never replaces itself (Windows-safe)
- downloads run in a self-spawned hidden sub-command, in the background
  from the update preflight or in the foreground from 'kimi upgrade'
- rollback from .bak on any swap failure; install failures keep the
  existing retry/prompt thresholds

* fix(kimi-code): fully clean staged artifacts on swap discard paths

Real-binary smoke testing on macOS surfaced two cleanup gaps in the
discard path: the claimed metadata file was unlinked after the staging
dir rmdir (so the empty dir survived), and the staged exe was
rediscovered via the already-claimed staged.json (so it leaked on the
downgrade-guard path). Pass the known metadata through and order the
unlink before the rmdir.

* fix(kimi-code): restore staged metadata on swap failure and sweep update leftovers at startup

* fix(kimi-code): address codex review on lock contention and swap crash window

- The background native install no longer takes the outer install lock:
  the self-spawned downloader holds it for the whole download, and the
  parent's spawn-time lock raced the child into a false lastSuccess.
- Smoke-check the staged exe before moving anything, so a bad staged
  binary is discarded with the install path never left empty; the
  remaining crash window is two adjacent atomic renames (documented,
  recoverable via the .bak or by re-running the install script).

* test(kimi-code): align swap test expectation with smoke-before-rename order

The restore-on-failure case now observes the early smoke check's
--version spawn; only the re-exec spawn must be absent.

* fix(kimi-code): stage the bare CDN binary instead of unzipping

The published per-release artifacts are the bare platform binaries
(kimi-code-<target>[.exe]), not zip archives — the staging flow now
streams the download straight to the staged exe after the manifest
sha256 check, and the zip reader is dropped. Verified end-to-end on
macOS against the live CDN: download -> sha256 match -> swap ->
re-exec into the real released binary.

* fix(kimi-code): address second codex review round

- re-exec: forward 128 + signo when the swapped-in child dies by signal
  instead of reporting exit 0
- __update_download: only exit 0 without staging when the lock holder is
  staging the SAME version; a different in-flight version (or a vanished
  lock) no longer surfaces as a successful foreground upgrade
- staging: sweep orphaned .part downloads and unreferenced staged exes
  before downloading, preserving live swap claims and their payloads

* feat(kimi-code): show download progress for native updates

The foreground 'kimi upgrade' path streamed 180 MB with a single static
'Downloading…' line. Render progress instead: a throttled in-place
percentage line on a TTY, one line per 32 MB when piped, and plain MB
counts when Content-Length is unknown.

* fix(kimi-code): bound native update downloads with an idle timeout

Codex review: the manifest fetch cleared its timer once headers arrived,
so a stalled response body hung the worker forever, and the binary
download had no abort at all. The manifest timeout now covers body
consumption, and the binary stream aborts after 30 s without a chunk
(total duration stays unbounded for slow networks). The idle timeout is
injectable for tests.

* fix(kimi-code): retry native updates blocked by an orphaned active record

Windows real-machine verification surfaced that a parent exiting before
the downloader's exit event leaves a fresh-looking 'active' record that
silently blocks every background retry for the 6 h TTL. For native
installs, lock liveness is the truth past a 60 s spawn grace window:
a held lock means a download is running, a free lock means the record
is an orphan and a new attempt may start. Package-manager sources keep
the TTL behavior (no lock to prove liveness).

* fix: skip staged swap while another instance holds a fresh claim

sweepStaleNativeUpdateArtifacts already detected an in-progress swap in
a concurrent instance, but the result stayed inside the cleanup helper:
startup still claimed a newly published staged.json and ran a second
swap, so the two launchers could rename the install path and delete each
other's rollback backup. Propagate the in-progress signal and skip
claiming until the existing claim is released or goes stale.

* fix: keep the install lock while its holder process is alive

The install lock went stale purely by age (30 min), but the native
downloader is idle-bounded, not duration-bounded: a slow link can
legitimately take longer. Another startup would then sweep the lock and
spawn a second downloader, and both would write and clean the same
.staging paths. Past the age threshold, fall back to a pid liveness
probe (signal 0) — the lock is stale only when the holder is gone.

* fix: keep recovery artifacts on rollback failure and wait out same-version downloads

Two robustness fixes from review:

- native-swap: when moving the staged exe into place fails AND the
  rollback rename fails too (transient lock, AV), the install path is
  left absent and no next launch can start. Discarding the staged
  payload and claim on top of that removes the second recovery copy.
  rollback() now reports its result; on a double failure the swap keeps
  the .bak (which IS the old exe), the staged exe and the claim so
  manual recovery or a re-install still works.
- update-download: a foreground `kimi upgrade` racing a background
  downloader of the same version exited 0 immediately, so the CLI
  printed a success message for a download that could still fail. The
  worker now waits while the same-version holder is in flight, adopts
  the verified staged result (staged.json lands before the lock is
  released), and takes over the download when the holder finished
  without staging.

* fix: stamp the swap claim with a fresh mtime when claiming

rename() preserves the staged metadata's mtime, which can be arbitrarily
old — the background download often finishes hours before the next
launch claims it. A concurrent launch's sweep would then classify the
live claim as crash residue (older than the 5-minute window) and delete
the claim, the staged exe, and eventually the first swap's rollback
backup. Stamp the claim file with the claim time so the staleness check
measures the swap's liveness, not the download's age.

* fix: stamp the claim before the rename so it is born fresh

Stamping after the rename left a window: a concurrent launch could
inspect the claim between the two syscalls, see the staged metadata's
old mtime, and delete the staged executable mid-swap. utimes the state
file first so the claim carries a fresh timestamp from the instant it is
published — no fresh-looking-later intermediate state exists.

* fix: chmod the staged download before publishing it at its final name

A swap claims only the staged METADATA; the staged exe stays in
.staging/. A concurrent same-version downloader (possible because swaps
do not hold the install lock) then re-downloads and renames its .part
over that path. If the swap moves the file into the install path between
the downloader's rename and its post-publish chmod, the chmod lands on a
path that is already gone and the installation is left non-executable —
every future launch fails. Apply the executable mode to the private
.part file before the publishing rename so the staged exe is executable
from the instant it appears.

* fix: publish the install lock atomically via hard link

The 'wx' open exposed a momentarily empty lock file before its contents
were written. A concurrent acquirer reading in that window got a
SyntaxError, treated the lock as stale, swept it and also won — two
"holders" then ran stageNativeUpdate against the same .staging paths.
Write the lock contents to a unique temp file and hard-link it into
place: link() fails when the destination exists (same exclusivity as
'wx') and the lock path only ever appears fully written.

* fix: serialize stale-lock takeover through a secondary lock

A pathname-level delete can never be conditioned on the file still being
the inspected stale instance, so a plain compare-and-delete still loses
exclusivity: two workers classifying the same stale lock could interleave
unlink and publish such that both won (proven by a 20-way contention
test). Takeovers now go through a secondary create-if-absent lock
(install.lock.takeover): the delete+publish section only ever runs in
one process, staleness is re-validated inside it, and a fast-path creator
that wins the briefly-free path simply beats the takeover. The takeover
lock itself is age-swept (a live section lasts microseconds), and handles
only release the lock instance they own.

* fix: verify lock ownership after publish and preserve freshly staged exes

Two more race fixes from review:

- install-lock: the stale-marker sweep repeats the inspect-then-delete
  race one level up — two contenders sweeping the same aged takeover
  marker could both win and enter the main-lock section together.
  Pathname APIs offer no conditional delete, so both the takeover marker
  and the main lock now verify ownership after publishing (unique marker
  content, read-back compare): a racing sweep converts to a single
  survivor instead of two holders. The irreducible residual (a delete
  landing in the microsecond link-to-verify window) degrades to a wasted
  download cycle, never a corrupt install — swap claims guard the exe
  independently.
- native-swap: sweeping a stale swap claim deleted the exe it referenced
  even when a FRESH staged.json referenced the same version-derived name
  (a downloader re-staged the version after the swap crashed), throwing
  away a verified ~180 MB stage. The sweep now preserves any exe the
  current staged metadata still references.

* fix: reject mismatched manifests, take over from dead holders, unique .part names

Three robustness fixes from review:

- native-manifest: the per-release endpoint can answer with ANOTHER
  release's manifest (stale cache, mispublish); its checksums would then
  be applied to this version's binary and fail verification on every
  attempt. Compare the parsed manifest version with the requested one.
- install-lock/update-download: a killed lock holder skips its finally
  and never releases, stranding a waiting foreground `kimi upgrade`
  forever. A lock whose recorded pid is dead is now stale at any age
  (the atomic publish guarantees the pid was alive when written), and
  the same-version wait loop polls the acquisition itself, so a dead
  holder's lock is taken over within one poll instead of never.
  Package-manager spawns are unaffected: they hold the lock only around
  the spawn, and the active-record bookkeeping guards that layer.
- native-stage: the download intermediate is now unique per worker
  (`.part` carries pid + counter), so overlapping same-version workers
  can no longer interleave writes into the same file.

* fix: restrict staging cleanup to updater-owned names and retry short writes

- cleanupStagingOrphans recursively deleted anything it did not
  recognize; the staging dir sits next to the exe and can contain files
  belonging to the user or another tool. Deletion now requires a
  positive match on updater-owned artifact names (staged exes and .part
  intermediates) and only ever unlinks files.
- FileHandle.write may persist fewer bytes than requested (short write,
  e.g. near disk exhaustion) while the running hash and size already
  accounted for the whole chunk — publishing a truncated binary under a
  valid checksum. The chunk write now loops until fully persisted.

* fix: scope failure cleanup, recognize all semvers, reverify staged checksums

Three fixes from review:

- native-stage failure cleanup deleted whatever staged update was
  currently published — including a concurrent worker's valid result
  that its caller had already reported as success. The catch path now
  removes only this attempt's own artifacts: its unique .part file and
  its staged exe name when the current metadata does not reference it.
- The orphan-cleanup ownership check only matched stable x.y.z names;
  prerelease/build-metadata versions (1.2.3-rc.1, 1.2.3+build) would
  never be cleaned and accumulate ~180 MB each. Ownership now derives
  from the semver contract via the semver package's valid().
- The swap path trusted a staged exe whose size matched, though the
  metadata records the release checksum; post-download on-disk damage
  could pass the --version smoke check with corrupted bytes.
  claimStagedUpdate now re-verifies the staged exe's sha256 before
  claiming and discards the stage (for a later re-download) on mismatch
  — paid only when an update is actually pending.

* fix: validate versions before path derivation and honor the update opt-out in the swap

- native-stage: stageNativeUpdate derived staging paths (including the
  cleanup rm targets) from the version before fetchNativeReleaseManifest
  rejected it; a traversal string like `x/../../kimi` would resolve the
  staged-exe cleanup onto the running installation. The semver check now
  happens before any path is derived, and the staged-metadata schema
  constrains exeFileName to a plain file name.
- native-swap: the startup swap ran before the update preflight, so
  KIMI_CODE_NO_AUTO_UPDATE / KIMI_CLI_NO_AUTO_UPDATE stopped gating
  update behavior once a payload was pending. The swap now honors the
  same opt-out: the staged payload stays in place for a later launch
  without the variable, and the current exe starts.

* fix: restrict backup cleanup to updater-owned .bak names

cleanupBackups treated every <exe>.*.bak sibling as swap residue, so a
user's own backup like kimi.config.bak in a shared bin directory was
silently deleted on startup. Only the exact <exe>.bak and the numeric
PID fallback <exe>.<pid>.bak are updater-created — cleanup now
positively matches those two formats.

* fix: claim staged metadata before validating it and let manual upgrades bypass the opt-out

- native-swap: claimStagedUpdate validated the metadata and hashed the
  staged exe BEFORE the atomic rename, so a concurrent downloader
  superseding staged.json in between could get its fresh metadata
  claimed under the older object — the smoke check then failed and
  discard() deleted the newly published stage, recording a failure for
  the wrong version. The claim (utimes + rename) now happens first and
  validation acts on exactly the claimed file; discards use a new
  discardClaimedUpdate that never removes anything a meanwhile-published
  stage references.
- The auto-update env opt-out gated the startup swap unconditionally,
  so an explicit `kimi upgrade` with the variable set staged the
  version but no launch ever applied it. Stages now record
  `manual: true` when they answer a user-initiated install
  (`__update_download --manual`, threaded from installUpdate through
  the hidden sub-command), and the swap applies manual stages even when
  automatic updates are opted out.

* fix: promote adopted stages to manual and preserve claim-referenced payloads

Three follow-up fixes from review:

- An explicit `kimi upgrade` adopting an auto-staged payload (already
  on disk, or still downloading via the wait path) returned before the
  manual marker applied, so under the env opt-out the swap still skipped
  it despite the success message. Both adoption paths now promote the
  staged metadata to manual: true via a new promoteStagedUpdateToManual.
- The download-failure cleanup checked only the current staged metadata,
  but a live swap holds the metadata renamed aside as its claim — a
  failing same-version downloader could delete the exe an active swap
  was about to move into place. The catch path now also preserves names
  referenced by any live swap claim.
- Restoring a claimed stage after a failed exe move used rename, which
  on POSIX replaces a newer staged.json a downloader published during
  the smoke check. The restore is now a create-if-absent hard link: it
  only lands when the state-file path is still free, and the older claim
  is discarded when a newer stage has taken it.

* fix: drop exe deletion from stale-claim cleanup

The stale-claim sweep deleted the referenced exe based on a metadata
snapshot taken before the loop; a downloader republishing the same
version between the read and the unlink would have its fresh payload
deleted after reporting success. Publication can never be synchronized
with a pathname-level snapshot, so the sweep now removes only the claim
files themselves — genuinely unreferenced exes are reaped by the
downloader's own orphan cleanup (keep-set aware) before its next stage.

* fix: never delete the staged exe when discarding a claim

The same publication race existed one level down: a same-version
downloader can rename its fresh payload onto the shared exe path after
the discard's metadata snapshot but before the unlink (payloads publish
before their metadata), and the discard would delete a download whose
caller then reports success with nothing behind it. discardClaimedUpdate
now removes only the claimed metadata file; unreferenced exes are reaped
by the downloader's own orphan cleanup before its next stage.

* fix: only reap staging orphans old enough to be abandoned

The orphan sweep could delete a concurrent worker's freshly renamed
staged exe in the gap before its staged.json lands (payloads publish
before their metadata), turning the admitted duplicate-worker race into
a successful stage with no payload behind it. Unreferenced artifacts are
now only deleted once older than a one-hour grace period — publication
takes milliseconds, so unreferenced AND old means definitively
abandoned.

* fix: honor the persisted auto-update preference in the swap and drop claim-unsafe deletions

- The startup swap gated only on the env opt-out, so a payload staged
  automatically still installed after the user disabled automatic
  updates via [upgrade] auto_install = false. The swap now loads the
  persisted preference (only when an automatic stage is actually
  pending) and skips it, exactly like the env opt-out; manual stages
  still always apply.
- Superseding a staged version deleted its exe through an uncoordinated
  read-then-remove that could pull the payload from a live swap. The
  supersede now removes only the old metadata record — the metadata
  write atomically replaces it, and an unreferenced exe is reaped by a
  later orphan cleanup. removeStagedNativeUpdate, left with no callers,
  is removed.
- docs: the kimi upgrade reference (en + zh) no longer claims Windows
  native installations cannot upgrade automatically; native installs
  download and verify in the foreground and swap on the next start.

* fix: gate on claimed metadata, stop shared-path deletes on failure, exact smoke match

- The opt-out gate evaluated a pre-claim snapshot of the staged
  metadata, but the claim could pick up a different (automatic) stage a
  downloader published in between — smuggling it past the gate. The
  env/preference check now runs on the CLAIMED metadata; when disabled,
  the claim is restored via create-if-absent link so a newer stage is
  never overwritten and a later launch can still apply it. The checksum
  re-verify moves after the gates so opted-out launches stop paying for
  the hash.
- The download-failure cleanup still deleted the shared staged-exe path
  based on snapshot reference checks — the same publication race as the
  paths already fixed. It now removes only the attempt's privately owned
  .part file; the shared exe is left for the age-gated orphan cleanup.
- The smoke check accepted the staged version as a substring of the
  --version output, so a mispublished 1.2.30 binary would satisfy a
  1.2.3 target with a matching manifest checksum. It now requires the
  trimmed output to equal the staged version exactly.

* fix: confirm the manual marker before reporting stage adoption

promoteStagedUpdateToManual silently no-oped when a startup swap had
claimed the state file, while the adoption paths still reported success
with manual: true synthesized — under the env opt-out the restored
automatic metadata would then be skipped on every later launch despite
the upgrade's success message. The helper now verifies the marker with a
confirming read (one retry) and returns whether it persisted; the
already-staged branch falls through to a fresh stage when it does not,
and the same-version wait loop only adopts after a confirmed promotion.

* fix(cli): verify the staged payload digest before adopting it as already-staged

readStagedNativeUpdate checks only the recorded size, so a same-size
corruption after the download was adopted and reported as success, only
for the startup swap's claim-time re-verify to reject and discard it.
Compare the actual sha256 before returning already-staged; a mismatch
falls through and re-stages from the CDN.

* fix(cli): keep staged metadata until its replacement is ready

Two related races around staged.json, both reported against the
duplicate-downloader residual:

- stageNativeUpdate deleted the previous record before downloading its
  replacement; a pathname-only delete can remove a concurrent worker's
  freshly published record, orphaning a payload whose worker already
  reported success. The old record now stays until the final atomic
  metadata write replaces it.
- promoteStagedUpdateToManual wrote the marker unconditionally onto
  whichever generation owned staged.json. It now takes the adopted record
  and promotes only while the on-disk metadata still matches it, and the
  post-write confirmation requires the promoted candidate itself.

* fix(cli): preserve the exe referenced by the current staged record during orphan cleanup

Since the supersede path now keeps the previous staged.json until the
final atomic write replaces it, an aged staged exe is still the
applicable update while its replacement downloads — but
cleanupStagingOrphans only pinned exes referenced by swap claim files,
so a payload older than the grace period was unlinked out from under
its own record. Read staged.json itself in the pinning pass so the
current record's exe is preserved like any live claim's.

* chore(kimi-code): reword the native auto-update changeset

* chore(kimi-code): trim the native auto-update changeset

* fix(cli): support update locking on filesystems without hard links

link() fails with ENOTSUP/ENOSYS/EPERM on FAT/exFAT and some network
mounts, which aborted every native update before the download. Add a
shared createFileIfAbsent primitive (hard-link a fully written temp
file, falling back to an exclusive create + write) and use it for the
install lock, its takeover marker, and the swap's claim restore. The
fallback's create->write gap is observable, so the lock inspection now
grants young unparseable content a publish grace before sweeping it as
crash residue.

* fix(cli): publish staged exes under unique names and recover orphaned claims

Two related robustness fixes in the staged swap flow:

- A staged executable is now published under a unique per-worker name
  (kimi-<version>.<pid>.<epoch-ms>.<n>[.exe]) and never replaced; the
  atomic metadata write retargets the pointer. The pathname a swap
  validates at claim time can no longer be exchanged by a concurrent
  same-version publisher between validation and install.
- restoreClaimedUpdate only drops the claim when the restore landed or a
  newer stage holds the state-file path; transient failures retain it.
  The stale-claim sweep now restores aged claims (create-if-absent)
  instead of deleting them, so a stage orphaned by a dead swap or a
  transient restore failure is retried on a later launch.

* fix(cli): verify the staged payload digest in the lock-wait adoption path

waitForStagedUpdate relied on readStagedNativeUpdate, which checks only
the recorded size: while a holder re-stages a same-size-corrupted
payload (its metadata is replaced only when the repaired generation
publishes), a waiter could promote and report the corrupt stage as
downloaded, and startup would later reject its checksum. Apply the same
integrity bar as stageNativeUpdate's already-staged path — adopt only a
payload that hashes to its recorded checksum; a mismatch falls through
to the lock poll, which takes over once the holder finishes without
repairing it.

* fix(cli): serialize swap critical sections and preserve in-flight publishes

- The fresh-claim sweep is only a directory snapshot: two processes could
  both pass it before either claimed, then rename the same installed exe
  concurrently and delete each other's rollback backup. A create-if-absent
  swap mutex (swap.lock, age-gated like the takeover marker) now serializes
  the executable-renaming section; the loser restores its claim and defers.
  The mutex is released as soon as the new exe is in place, before the
  re-exec, so it is never held for the child session's lifetime.
- claimStagedUpdate no longer destroys a claimed record that is unparseable
  but was young at claim time: on filesystems without hard links the
  exclusive-create publish is observable mid-write, and discarding it would
  orphan the staged exe while the writer reports success. Such a record is
  put back with the same inode so the writer completes it; aged corrupt
  residue and well-formed records with a missing/changed exe are still
  discarded.

* fix(cli): keep backup cleanup inside the swap mutex

The early release let a subsequent swap rename the just-installed exe to
the shared .bak path while the previous swap's cleanup was still about to
unlink that same path, destroying the second swap's rollback source. The
mutex now covers the backup cleanup; the cosmetic staging-dir rmdir and
the re-exec stay outside it.
2026-08-18 17:17:18 +08:00
7Sageer
86674ac895
test: remove wording-pinning tests of model-facing prose across both suites (#3031)
* test: replace prose-pinning system-prompt tests with a structural sharing check

The two removed tests pinned exact sentences of the default system prompt
('reversibility and blast radius', 'premature abstraction', optional-tool
phrasings that must not appear, ...). They broke on any intentional
wording change while only catching regressions that reused the same words.

The one real contract underneath — shared, ungated sections must render
byte-identically in the root agent and every subagent profile — is now
checked structurally by slicing the section out of the root prompt and
asserting the other profiles contain it, regardless of its wording.

* test: remove wording-pinning tests of model-facing prose across both suites

Sweep of the class identified in #3030: assertions pinning the exact
English wording of product model-facing text (system prompt, reminder
and injection .md files, tool descriptions, shipped profile/skill
bodies). They break on any intentional rewording yet only catch
regressions that reuse the same words.

Across 35 files (~60 test cases, net -1131 lines):
- deleted dedicated wording tests: 'exposes current metadata and
  schema' description pins, goal/plan/todo reminder content tests,
  tower skill-body prose pins, goal-outcome.test.ts;
- trimmed wording assertions from behavioral tests that otherwise
  stand alone; kept identifiers (tool names, XML tags, section
  markers), structural properties (wrapping/escaping/gating/cadence),
  fixture data, tool outputs and error messages;
- re-anchored a few gating tests on exported constants
  (WINDOWS_PATH_HINT, DEFAULT_REPLY_STYLE_GUIDE) instead of prose
  literals.

Deferred for a follow-up decision: ~15 tests whose prose pin is the
only discriminator of which reminder/budget-band fired (constants not
exported). Wire baselines and snapshot machinery untouched.
2026-08-18 16:36:57 +08:00
7Sageer
5c8df5973e
test: drop the assertion for the removed ambiguous-means-task example (#3030)
#3028 removed the 'treat ambiguous requests as tasks' rule and its
'locate the method in the code' example from the default system prompt.
The profile test pinned that example verbatim, so it now fails on main.
The removal was intentional; update the test to the new contract.
2026-08-18 15:02:17 +08:00
Luyu Cheng
d6021fa036
feat(kap-server): accept bundled skill activations on the prompt submission route (#2982)
* feat(kap-server): accept bundled skill activations on the prompt submission route

The bundled-submission capability was only reachable through the
in-process klient transports; the App talks to kap-server over /api/v1.
The submit-prompt route now accepts an optional non-empty skills field
and delegates to IAgentSkillService.promptWithSkills — same validation,
events, and single bundled user message as the TUI path — skipping its
own prompt-metadata update (the engine owns it there) and mapping
skill.not_found / skill.type_unsupported onto the skills route's codes.
To return the submission's queue identity, the engine's promptWithSkills
now resolves with prompt_id / user_message_id / created_at / state (plus
turn_id once launched), mirrored through the klient contract.

* refactor(agent-core-v2): slim the promptWithSkills result contract

Drop the user_message_id field (it is always the same identity as
prompt_id — the route duplicates it) and narrow state to the
running/queued/blocked vocabulary, mapped at the engine edge instead of
exposing the internal seven-state PromptState on the wire.

* fix(kap-server): harden bundled skill submissions against review findings

- Validate bundled skill names and types before any media materialization
  or control override, so a rejected bundle leaves session state untouched
  (the engine still re-validates authoritatively).
- Declare the 40415/40912 outcomes on the submit route so the generated
  API documentation includes them.
- The klient output schema no longer tolerates a missing promptWithSkills
  result (a transport-level absence now raises instead of resolving
  undefined), and a failed launch surfaces as an error rather than a
  successful running result.
- Add the changeset for the new public API field.

* fix(kap-server): preflight bundled skills before agent materialization and stabilize listed content

- Skill preflight now runs on the session's catalog before the main agent
  is resolved, so a rejected bundle cannot mutate session metadata by
  registering main (regression test on a cold session without an agent).
- The prompts list projection strips the stored skill blocks from a
  bundled prompt, so GET /prompts returns the same caller-only content as
  the submit response.

* fix(kap-server): reject bundled prompt_id combos at preflight and clean queued staging

- The skills + prompt_id incompatibility rejection now runs at the initial
  bundled preflight, before the main agent is materialized or any
  override binds (previously a yolo override could bind before the 40001).
- Queued bundles no longer skip staging cleanup forever: the discard is
  deferred to the bundle's prompt.completed / prompt.aborted lifecycle
  event, mirroring the plain path's launch-raced cleanup.

* fix(kap-server): clean queued bundle staging on the steer path too

A queued bundle steered into the active turn is consumed at steer time,
but the engine publishes prompt.completed/aborted only for the parent —
the deferred cleanup never fired and its subscription leaked. The
prompt.steered event (matching promptIds) now counts as the child's
intake-completion signal.

* fix(agent-core-v2): materialize daemon-ref media on the steer and inject paths

startNext materializes daemon file references into the session media
store before a prompt's turn, but steer() and inject() enqueued the same
references without that intake, leaving the staging upload as the only
copy — any staging cleanup at steer time would delete the media the
turn is about to consume. Both paths now run the same intake before the
SteerStepRequest is created, so prompt.steered is a truthful
intake-complete signal.

* fix(kap-server): defer staging cleanup to turn settlement, never to steer time

Prompt-intake materialization is best-effort: when it degrades, the
daemon upload is the request-time resolver's fallback source. Discarding
staging at prompt.steered could therefore delete the only readable copy
before the parent's request ran. Cleanup is now uniformly event-driven —
the bundle's own prompt.completed/aborted, or the steer parent's — so
the upload always outlives the request it feeds.

* fix(kap-server): install settlement tracking before bundled enqueue

A hook-blocked bundle completes synchronously inside the submission
call, and an exceptionally fast launch can settle just as early — a
post-call subscription misses the only settlement event and leaks both
the staging blob and the listener. The tracker now subscribes before
enqueueing, buffers lifecycle events, and settles against the returned
prompt id (or its steer parent's).

* fix(kap-server): scope settlement tracking to the owning agent and dispose on rejection

- The tracker now subscribes through the agent-scoped IEventBus instead of
  the App-scoped IEventService: prompt lifecycle events from other
  sessions never reach it, so a colliding client-chosen prompt id cannot
  trigger a foreign settlement (and the steer re-target only follows this
  agent's parent).
- A bundled submission that rejects after the tracker was installed now
  disposes it on the error path instead of leaking a permanent listener.

* fix(agent-core-v2): keep steered prompts queued until their media intake finishes

Materializing a steered prompt's daemon-ref media awaits a file copy
during which the active turn may finish. Records are now spliced out of
the queue only after that copy completes, and when the turn is gone by
enqueue time they are restored to pending so startNext can launch them
as fresh prompts — their handles always launch or settle.

* fix(agent-core-v2): revalidate the queue and active turn after steer media intake

The daemon-ref copy yields, so settle/abort can consume selected records
and the active turn can rotate meanwhile. Only records still pending are
steered, and only into the turn that was active at entry; records that
vanish from the queue are left to their own launch path, and a missing
turn restores them to pending instead of splicing an unrelated tail
prompt. The intake/queue-preservation contract is documented in the
module header.

* fix(agent-core-v2): steer only the surviving records and keep their media truthful

- The steered content is rebuilt from the records that are still pending
  after the media intake, so an aborted or concurrently consumed record's
  text is never injected (or injected twice) alongside the surviving
  handles.
- The enqueue is wrapped so an activeTurnOnly rejection restores the
  records to pending (the loop throws instead of resolving a missing
  turn, which made the previous rollback unreachable).
- The merged origin now carries the union of every record's bundled
  skillActivations, and prompt.steered publishes the caller-only content,
  so the skill instructions reach the model with their metadata intact
  while the event projection stops leaking internal skill markdown.

* fix(agent-core-v2): harden steer rollback and register bundled prompt ids

* fix(agent-core-v2): strip bundled blocks from prompt.queued and reject partial steers

* fix(kap-server): update session metadata for bundled prompts routed to subagents

* fix(agent-core-v2): restart queue after raced steer rollback and prefix skill blocks in merged steer

* fix(agent-core-v2): block queue advancement during steer admission

* chore: drop the changeset for server-only protocol plumbing
2026-08-18 14:57:13 +08:00
7Sageer
40e1784089
fix: tone down over-proactiveness in the default system prompt (#3028)
* fix: tone down over-proactiveness in the default system prompt

The default system prompt pushed the agent to act before discussing:
ambiguous requests were explicitly resolved to tasks, the opening framed
the primary goal as taking action, and 'default to making progress, not
to asking' discouraged clarifying questions.

Trim both copies (agent-core and agent-core-v2) by deletion only:
the ambiguous-means-task rule and its example, the action-framed opening
clause, the 'default to taking action with tools' paragraph, the
duplicated must-use-tools sentence (kept once in Ultimate Reminders),
and the 'default to making progress, not to asking' bullet. Operational
guidance and the execution guards stay untouched.

* Delete .changeset/tame-system-prompt-proactiveness.md

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

* fix: drop the tool-use and no-placeholder bullets from the default system prompt

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-08-18 14:47:52 +08:00
bj456736
d3150fe947
chore: remove internal-network references from comments and test fixtures (#3029)
* chore: remove internal-network references from comments and test fixtures

- Reword two comments that named the internal free-tokens model
  registration flow; the generic OAuth / managed wording carries the
  same meaning
- Replace the qianxun.example placeholder base URL in google-genai and
  runtime-provider tests with genai-gateway.example
- Swap realistic-looking LAN fixture IPs in the kimi web banner tests
  (192.168.98.66, 10.8.12.216) for RFC 5737 documentation addresses
  (192.0.2.66, 198.51.100.216)

* chore: retrigger CI (flaky kap-server searchRoute title-indexing test)

---------

Co-authored-by: bj456736 <bj456736@users.noreply.github.com>
2026-08-18 14:14:56 +08:00
liruifengv
eaa3969dd3
feat(kap-server): add page mode, updated_before, and batch archive/restore to v2 sessions (#2983)
* feat(kap-server): add page-number mode and total to GET /api/v2/sessions

The v2 session list gains a stateless 1-based `page` parameter beside the
opaque page_token cursor for admin-style lists that jump arbitrarily:
each request stays a full independent snapshot, no token is minted, and
`page` + `page_token` together fail 40001. Every response now carries
`total` (the filtered/sorted set size) in both pagination modes.

* feat(kap-server): add meta.updated_before filter to GET /api/v2/sessions

Symmetric with meta.updated_after (inclusive boundary, Unix ms), applied
at the edge over the drained set and bound into the page_token query
fingerprint like every other condition.

* feat(kap-server): add POST /api/v2/sessions:archive and :restore batch endpoints

Batch archive/restore for session-management views: { ids } (non-empty,
≤5000 unique after dedup) answers per-item results in input order with
succeeded/failed counts — only a body validation failure fails the whole
request, and an unknown id folds into its own item as 40401.

The live/cold split keeps the batch cheap: a session with a live handle
goes through the full ISessionLifecycleService chain (agents drain,
scope teardown, mirror drain), while a cold session is never
materialized — the new setColdSessionArchived helper in agent-core-v2
patches the persisted state.json (archived/archivedAt, updatedAt
preserved, mirroring setArchived's touchUpdatedAt: false semantics),
mirrors the flipped summary into the read-model queue, and republishes
the same event.session.archived bus event the live lifecycle emits
(:restore publishes nothing, matching the live restore). Hot items run
with bounded concurrency and the batch ends with one shared
ISessionIndexMirror.drain().

* docs(server-api): document v2 sessions page mode, total, updated_before, and batch archive/restore

* fix(kap-server): deep-import workspace lifecycle symbols in the v2 sessions route

CI's tsgo/rolldown (Linux) fail to bind liveHandlerForSession and
IWorkspaceLifecycleService through the agent-core-v2 package-root
barrel even though it re-exports them; the same files use the
established deep-import pattern already used for the git domain.

* fix(kap-server): inline the live-handler lookup in the batch route

The previous deep imports still fail to resolve on CI's Linux toolchain
(tsgo TS2307, rolldown MISSING_EXPORT) while every other module path
from the same package binds fine. Keep the route self-contained: the
hot-path lookup is a five-line loop over IWorkspaceLifecycleService's
handlers (mirrors agent-core-v2's liveHandlerForSession), and the tests
assert non-materialization behaviorally via the live map instead of
importing the same two symbols for spies.

* fix(kap-server): drive the batch hot path through getLiveSessionById

The phantom only hits the workspaceLifecycle-group symbols in these two
files on CI's Linux toolchain; getLiveSessionById is observed to bind
fine there. It returns the session's live scope directly (no resume),
which is exactly what the batch hot path needs.

* refactor(kap-server): move the batch live/cold split into agent-core-v2

setSessionArchivedBatch owns the split next to the cold patch: live
sessions go through the full lifecycle chain via the workspace handler
accessor (the v1-proven resolution path), cold sessions through the
direct write. The route becomes a thin wire-code adapter, and the batch
tests assert the live chain behaviorally (disposal, events, index)
instead of spying through scope accessors.

* fix(agent-core-v2): import sessionLookup relatively from coldSessionArchive

The '#/app/workspaceLifecycle/*' specifier resolves from src/ and
src/app/* files on CI's Linux toolchain but not from
src/workspace/sessionLifecycle/ (tsgo TS2307, rolldown follows); a
relative import bypasses the package-imports mapping.

* fix(agent-core-v2): migrate the batch hot path to ISessionManager

Main's workspace/session DI refactor removed the workspaceLifecycle
lookup modules; the live branch now goes through the App-level
ISessionManager (the same entry the v1 action route uses post-refactor)
with getLiveSessionById from the new sessionManager lookup.

* feat(kap-server): add the id,archived item projection to GET /api/v2/sessions

fields=id,archived trims each item to { id, archived } for
select-all-matching flows (the session admin page's Gmail-style
select-all). Only that projection gets the relaxed page_size ceiling
(10000); unknown fields, non-pair subsets, and include=git combinations
are 40001, and the projection binds into the page_token fingerprint so
shapes never flip mid-pagination.

* fix(agent-core-v2): serialize the batch cold write against in-flight resumes

Codex review on #2983: while a resume is in flight the live registry
hides the handle, so the batch route could classify the session as cold
and its direct write would race the materializing metadata service (its
stale in-memory document wins the next write, silently un-archiving the
session after the endpoint reported success).

The batch now settles the resume first: SessionManager registers the
whole resume promise synchronously at the App level (controllerForSession
is async, so the controller's own resuming map learns about it a few
microtasks late) and whenResumeSettled awaits it before classification —
a settled resume lands the item on the live chain, a failed one falls
back to the cold path. Also folds the module header down to the
package's external-role comment convention.

* fix(agent-core-v2): publish SessionArchived as an Event2 class in cold archive

* fix(agent-core-v2): serialize batch archive/restore with session lifecycle transitions

* fix(agent-core-v2): serialize session delete with the lifecycle chain

* fix(agent-core-v2): mirror the persisted metadata on cold archive, not the index summary

* docs(agent-core-v2): bring sessionManager comments and new tests to package conventions

* fix(agent-core-v2): normalize legacy session metadata before the cold archive write

* fix(kap-server): serialize the v1 single-session archive with the lifecycle chain

* chore: drop changesets for internal-only protocol work

* fix(agent-core-v2): encode cold-archived metadata for v1 readers

* fix(agent-core-v2): serialize fork and createChild with the source session's chain

* refactor(agent-core-v2): chain every session lifecycle method and hand batch sections unguarded ops

* fix(agent-core-v2): propagate failed resumes to the next settle

* fix(agent-core-v2): roll back the unannounced handle when a resume fails mid-materialization

* fix(agent-core-v2): read and migrate the legacy session-meta location on cold archive

* fix(agent-core-v2): serialize explicit-id session creation with the lifecycle chain

create() with a caller-supplied sessionId bypassed the per-session chain,
so a concurrent batch archive could classify the half-created session as
cold and write archived state that the live metadata service later
overwrites. Creation now queues on the target id's chain whenever an
explicit id is present.

Also type the resume-failure maps as Error and normalize at the catch
site, satisfying only-throw-error.

* style(kap-server): strip comments from the session routes per the no-comments convention

* fix(agent-core-v2): serialize explicit fork and child target ids on the lifecycle chain

fork() and createChild() with a newSessionId locked only the source id, so
a batch archive of the target could slip into the creation window: the
index already knows the half-created session, the batch writes archived
state to its document, and the fork's in-memory metadata later overwrites
it. Both operations now acquire the deduped, sorted key set so multi-key
sections always take locks in one deterministic order.
2026-08-18 13:57:37 +08:00
Haozhe
5ae82cd5bc
feat(agent-core-v2): disable the tower feature entirely (#3023) 2026-08-18 13:38:13 +08:00
bj456736
13857f3832
chore: rewrite pending changesets for the new changelog conventions (#3026)
* Rewrite pending changesets for the new changelog conventions

* chore: drop the /tower changeset per reviewer request

* chore: trim pending changeset entries further per reviewer feedback

---------

Co-authored-by: bj456736 <bj456736@users.noreply.github.com>
2026-08-18 13:06:14 +08:00
bj456736
8f5090782c
chore: simplify the gen-changesets skill (#3024)
* Simplify the gen-changesets skill

* chore: state only what changed, drop explanatory trailing clauses

* docs: require strict adherence to the changeset rules in AGENTS.md

---------

Co-authored-by: bj456736 <bj456736@users.noreply.github.com>
2026-08-18 13:05:50 +08:00
Haozhe
98ebda840a
fix(kimi-code): revert the todo panel to its pre-turn state on undo (#3016)
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-vscode-legacy (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(kimi-code): revert the todo panel to its pre-turn state on undo

* fix(kimi-code): hide all-done todo lists on undo refresh and detach SDK todo state
2026-08-18 12:00:19 +08:00
Haozhe
8267bb8fce
feat(kap-server): add workspace fs:suggest file completion endpoint (#3019) 2026-08-18 11:53:48 +08:00
Haozhe
3ded08084a
fix(protocol): expose turn ended event time (#3011)
* fix(protocol): expose turn ended event time

* fix(protocol): expose turn ended event time

* chore(changeset): remove patch release entry

* test(node-sdk): align background task parity expectations
2026-08-18 10:16:12 +08:00
Haozhe
1ab19190e9
refactor(agent-core-v2): strip comments from agent-core-v2, kap-server, and transcript (#3010)
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-vscode-legacy (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
2026-08-18 00:30:49 +08:00
bj456736
a7dc1ea284
fix(agent-core-v2): degrade media tool registration when the bound model alias is stale (#2985)
* fix(agent-core-v2): degrade media tool registration when the bound model alias is stale

A restored session replays its persisted profile.bind without catalog
validation, so the profile can carry a model alias that no longer
resolves (e.g. the managed kimi-code models were removed from
config.toml on logout). AgentMediaToolsRegistrar.refresh() called
modelCatalog.getRequester() unguarded on that alias; the throw escaped
the agent.status.updated listener and was reported as an [unexpected]
Error2 (config.invalid) on startup.

Catch the resolution failure and degrade to "no model": media tools
stay registered off the profile-reported capabilities, just without a
model-bound video uploader, matching the tryResolveRawModel style used
elsewhere in the profile service.

* test(agent-core-v2): reproduce the stale-alias regression with production-consistent collaborators

A stale alias makes the real AgentProfileService report
UNKNOWN_CAPABILITY, so the regression now binds unknown capabilities,
asserts the tool stays unregistered without surfacing an [unexpected]
error, and covers recovery once the alias resolves again. The rationale
moves into the mediaToolsRegistrar file header per the package comment
conventions.

---------

Co-authored-by: Mira <bj456736@users.noreply.github.com>
2026-08-17 21:46:52 +08:00
7Sageer
5dffed2545
refactor(agent-core-v2): rebuild context projection as a staged block pipeline (#3001)
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-vscode-legacy (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
CI / typecheck (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
* refactor(agent-core-v2): carry the context fold cursor in state and converge fold/projection internals

- ContextModel state is now { messages, fold }: the loop-event fold cursor
  (openStepUuid / pending / deferred) lives in the state instead of a
  module-level WeakMap keyed by array identity, so wholesale replacements
  (undo / clear / compaction / swarm exit) reset it structurally via
  EMPTY_FOLD instead of a manual resetFold at five call sites.
- The display transcript and the wire model now share one generic fold
  kernel (FoldFrame / FoldEntryAdapter), eliminating the mirrored second
  implementation. Events tagged with a non-open step uuid are dropped and
  step.end settles only the step it names — defensive in abnormal streams,
  identical on well-formed ones (v1 replay unaffected).
- IAgentContextProjectorService converges to project(messages, policy) with
  a ProjectionPolicy data object; llmRequester builds the policy from retry
  state instead of selecting among four methods.
- Blob rehydrate now also covers messages still deferred in the fold cursor.
- ContextState is deeply frozen at the op boundary to preserve the consumer
  immutability the wire's shallow freeze gave the bare array state.

* test(agent-core-v2): move fold parity rationales into the test file header

* docs(agent-core-v2): move fold declaration comments into module headers

* refactor(agent-core-v2): merge FoldFrame into generic ContextState

* refactor(agent-core-v2): compile-enforce part/event handling decisions in context memory

- isVacuousContentPart and dehydrateRecord now switch exhaustively over
  ContentPart / LoopRecordedEvent variants, so a new variant fails
  compilation until it takes an explicit position
- the transcript/model parity comparator spreads whole messages and masks
  only summary content, so new ContextMessage fields join the comparison
  automatically
- correct two stale header comments: local message ids persist with
  append_message records, and undo's prompt-owned-injection pairing
  depends on them after a resume

* refactor(agent-core-v2): converge undo-cut decision in conversationTime

The model Op and the display transcript each walked the undo anchors with
their own loop, and the transcript partially removed the tail when an undo
was blocked (compaction summary / clear floor / too few anchors) while the
model side no-ops at the precheck. Move the walk into conversationTime as
computeUndoCut/computeUndoCutFrom applied destructively by the context.undo
Op and non-destructively by the transcript reducer, so a blocked undo reads
identically on both sides.

Also: make isUndoAnchor exhaustive over origin kinds with a never assertion,
mirrors the compaction result message count via compactionHandoff, and
extend UndoCut with anchorIndex distinguishing the counted anchor from the
injection-extended cut point.

* fix(agent-core-v2): drop removed prompts' injections on multi-turn transcript undo

The transcript's kept-loop retained every injection after the oldest
counted anchor, so with count > 1 a prompt-owned injection of a newer
removed prompt (e.g. an image-compression caption) survived the display
undo while the model Op removed it. Collect the removed anchors' ids on
the same pass and keep only injections not owned by them, so the header's
'prompt-owned ones leave with their prompt' holds for every count.

* refactor(agent-core-v2): accumulate request projection repairs as policy

The llmRequester retry chain kept a RequestProjection union and translated
it into a ProjectionPolicy per attempt; repairs were mutually exclusive,
so a strict resend rejected again for body size or image format either
aborted or silently dropped the strict repair. Retry state is now the
ProjectionPolicy itself: each rejection adds its repair on its own axis
(media: 413 -> degraded -> strip; wire: structure -> strict) without
discarding the other, requestInput's translation layer and the unreachable
snapshot ??= disappear, and the persisted llm.request projection name
derives from the policy (the op enum gains strict-media-degraded /
strict-media-stripped). Also narrows ProjectionPolicy to the variants
actually produced (wire 'strict'; media 'degraded' | { strip }), dropping
the dead 'default'/'keep' literals and their guard.

* refactor(agent-core-v2): derive the visible context window from an append-only log

context.apply_compaction now appends a summary marker carrying the record
fields as CompactionMeta instead of replacing the folded history; the
model-visible window is derived at read time (visibleWindow) with the same
[head, elision?, tail, summary] layout, one deterministic derivation for
live dispatch and replay alike. The record format is unchanged, so v1- and
v2-written sessions keep replaying identically both ways.

- undo maps the visible-window cut back to a log position (the verbatim
  legacy-summary edge falls back to the pre-append-only destructive cut)
- replay rehydrate only loads blobs the window derivation can surface
- contextInjector drops position tracking; injection positions become a
  read-time scan that splices can never desync
- fullCompaction's safety check moves to the stable-identity log

* fix(agent-core-v2): rehydrate exactly the messages the visible window surfaces

The first append-only rehydrate rule kept pre-marker real user input plus
markers, but a legacyTail derivation keeps window.slice(compactedCount)
visible — assistant/tool media in that range stayed blobref after replay
and could be resent unresolved. Decide survivors by identity membership in
the derived window instead, which covers every derivation branch at once
and skips the unselected pre-marker pool as a bonus.

* fix(agent-core-v2): pop the visible-tail swarm reminder behind a legacy marker

SwarmService.exit decides the pop on the derived window's tail, but the
swarm_mode.exit reducer tested the raw log tail — after a legacyTail
compaction the survivor reminder is visible at the window tail while the
marker is the log tail, so the pop silently skipped and the stale reminder
stayed in the model context. Mirror the visible-tail decision in the
reducer and remove the entry by its stable log identity.

* fix(agent-core-v2): settle open transcript frames when compaction lands mid-fold

An overflow-triggered compaction arrives with the failed attempt's vacuous
partial still open. The transcript appended the summary marker and reset
the fold but left the frame; the retried step's step.begin then settled it
(-1) alongside the new frame (+1), so foldedLength stayed one short of the
model-visible window and kap-server's live-tail merge could duplicate the
retry tail. Settle the frame at the marker through the shared kernel;
recoverFoldedLength recomputes the absolute count right after either way.

* fix(agent-core-v2): settle open frames at the compaction marker

Apply settleModelOpenStep inside context.apply_compaction so a marker only
ever lands on a settled frame: no partial survives a marker and nothing
mutates the log behind one, making the append-only invariant structural
(the identity-prefix check in historySafeToCompact relied on it) instead
of timing-dependent. Mirrors the transcript's settle-at-marker.

Also freeze the derived visible window before caching it (an in-place
consumer mutation now throws instead of silently polluting the shared
cache), and drop the production-unreachable legacy branch of
buildContextCompactionShape so the legacy tail layout lives only in
deriveCompactionWindow.

* refactor(agent-core-v2): tighten naming and comments in context memory internals

- Slim file headers to the package header-only comment convention
- Rename PR-introduced identifiers for clarity: getMessageLog,
  ProjectionPolicy.structure, pendingToolCallIds/deferredEntries,
  removedEntryCount, deriveVisibleWindowAfterCompaction,
  compactedWindowMessageCount
- Extract nextProjectionPolicyForError, removeUndoOwnedEntries and
  summarizeProjectionRepairs; name fold intermediates after their
  business stage
- Regroup splice-replay tests by topic and unify projection-call
  recording in llmRequester tests

* fix(agent-core-v2): preserve bounded context state

* docs(agent-core-v2): restore the domain identity line in the compactionHandoff header

* refactor(agent-core-v2): rebuild context projection as a staged block pipeline

Split the 650-line projector service into three modules by concern:
mediaProjection (read-side media degrade/strip fallbacks), projection
(the structural transform), and the service (DI binding plus repair
reporting). Rebuild the structural projection as a two-stage pipeline:
pairBlocks groups tool exchanges into blocks that own their calls'
results, flattenBlocks serializes them back to wire order and merges
consecutive user prompts. The shared slot sentinel and index
back-patching are gone; the trailing-close and sizing-slice rules are
named and documented in the module header. Behavior is pinned unchanged
by the existing projector and llmRequester suites.

* docs(agent-core-v2): trim the projection helper header to its external role

* docs(agent-core-v2): keep the contextProjector module headers at the external-role level
2026-08-17 20:34:39 +08:00
7Sageer
2304554da4 feat(agent-core-v2): add the unified MCP management plane
Port the v1 MCP management plane (#2858) onto the v2 DI x Scope engine:

- App-scope IMcpOAuthService shared by every workspace handler and
  session overlay: credential events, single-flight refresh, proactive
  refresh timers, OAuthTokenTransaction-serialized writes, offline
  tokenState, shutdown. Providers read tokens through the store so
  grants written or revoked by another process are honored immediately;
  http/sse transports ride the transaction fetch.
- IMcpConfigStore: the single write point for the user-level mcp.json
  over the filesystem byte store, byte-identical to v1's format, with
  per-entry validation, name normalization, __proto__-safe parsing, a
  mutation tail, and an onDidWrite event.
- IMcpRegistryService: the unified read view over the layered config
  files (with per-entry origins) and plugin manifests (full descriptors
  incl. disabled, with provenance); collisions stay visible and runtime
  resolution ranks an enabled plugin above the file layers.
- IMcpManagementService: guarded CRUD, connection-test probes, the
  locator-addressed inspection/auth-status surface, and
  locator-addressed OAuth begin/complete/cancel/reset with ambiguity
  rejection. Engine services stay ungated; the mcp_management flag
  gates the edge exposure.
- Workspace runtime aligns with v1 precedence (an enabled plugin entry
  wins over the file layers, shadows revive), and management writes
  reload immediately via onDidWrite instead of the watch debounce.
- node-sdk v2 facade delegates to the engine service (deleting its
  in-process duplication); kap-server exposes /api/v2/mcp/* and klient
  gains global.mcp.*, both flag-gated.
2026-08-17 20:26:54 +08:00
bj456736
09976b0914
feat(cli): add --web-title and expose it via /meta (#2989)
* feat(cli): add --web-title and expose it via /meta

* refactor(kap-server): pass optional web_title directly in /meta

Per the repo rule for optional object properties, pass undefined directly
instead of a conditional spread; serialization omits the unset value.

* fix(cli): sync web bundle with instance tab title support

The committed dist-web bundle predates the document title feature, so a
released `kimi web --web-title` served a client that never read web_title.
Rebuilt from code-app (feat/web-document-title) via sync:web; the bundle
now titles tabs from web_title or the active workspace directory.

* ci: retrigger checks after flaky harness cleanup failure

---------

Co-authored-by: wbxl2000 <wbxl2000@outlook.com>
Co-authored-by: bj456736 <bj456736@users.noreply.github.com>
2026-08-17 20:19:15 +08:00