Commit graph

77 commits

Author SHA1 Message Date
Haozhe
2502d2157f
revert(kap-server): drop the flat entity message protocol (v3 WS + history API) (#3532) (#3920)
This reverts 64505e36e3.

The v1 WS + legacy REST + transcript surfaces are alive on main and remain
the single protocol surface; kimi-inspect returns to the transcript-based
data model (keeping #3747's removal of the prompt input, which adapted to
the agent-core-v2 prompt-queue fold).
2026-09-19 12:40:31 +08:00
Grapedge
bbcdde0b13
feat(vscode): wire the extension into the shared telemetry pipeline (#3852) 2026-09-17 13:45:59 +08:00
Grapedge
faec32c7e7
feat(tui): render mermaid code blocks as diagrams in the terminal (#3851) 2026-09-17 12:56:40 +08:00
Grapedge
486dcd26c7
feat(kimi-code): re-baseline pi-tui on upstream v0.85.1 (#3780)
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
Publish VS Code extension / Publish VSIX to marketplaces (push) Waiting to run
2026-09-15 03:21:29 +08:00
Haozhe
64505e36e3
feat(kap-server): add flat entity message protocol (v3 WS + history API) (#3532)
* feat(kap-server): add flat entity message protocol (v3 WS + history API)

* feat(kap-server): add task notification user messages, skill origins, and history has_more

* fix(kap-server): page history by turns so oversized turns stay reachable

* fix(kap-server): synthesize foreground subagent tasks in the cold fold when no task records exist

* fix(kap-server): dedupe queued prompts for turn records without a prompt id

* fix(kap-server): adapt v3 projection to agent-core-v2 API changes and filter empty thinking

* feat(kap-server): align v3 protocol with design revision 1094 and add agent.state

---------

Co-authored-by: liruifengv <liruifeng1024@gmail.com>
2026-09-10 19:08:27 +08:00
Haozhe
9188d03c99
refactor(protocol): remove package and relocate wire types into agent-core-v2 (#3646) 2026-09-08 17:41:41 +08:00
Grapedge
586c4c2130
chore: initialize husky git hooks (#3632) 2026-09-08 12:37:31 +08:00
Haozhe
48f5dc9740
feat(kap-server): add remote-control runtime toggle API (#3594)
* feat(kap-server): add remote-control runtime toggle API

* feat(remote-control): manage tunnel lifecycle with an xstate state machine
2026-09-07 13:46:22 +08:00
Haozhe
3f967e1410
refactor(agent-core-v2): unify fs watching into a single xstate watch service (#3502) 2026-09-07 13:30:25 +08:00
Haozhe
bb16383aa1
refactor: remove the legacy agent-core v1 package (#3542)
* refactor: remove the legacy agent-core v1 package

Delete packages/agent-core (v1 engine) and migrate node-sdk fully onto
agent-core-v2:

- node-sdk owns its public contract (error protocol, KimiConfig schema
  and file IO, process-global logging facade, telemetry client
  interface); event and interaction types re-export from
  @moonshot-ai/protocol or agent-core-v2
- createKimiHarness is now the single harness factory, backed by the
  v2 client; the v1 SDKRpcClient is deleted
- CLI and vscode run agent-core-v2 unconditionally;
  KIMI_CODE_LEGACY_FLAG and useAgentCoreV1 are removed
- the wire.jsonl replay fold moves into agent-core-v2's replayBuilder
  (foldWireRecords), and the v2 journal now persists tool-call display
  so replay restores diff/todo displays
- TUI engineV2 branches collapse to the v2 path

* test(vscode): retry temp-dir teardown in replay-resume integration test
2026-09-07 11:17:51 +08:00
Haozhe
485594f65e
refactor(agent-core-v2): absorb the human layer and replace the agent loop with a facade over its turn state machine (#3580)
* refactor(agent-core-v2): absorb the human layer into src/human and rename vendored kosong to llm-adapter

* feat(agent-core-v2): preserve media attachment names in human message contract

* refactor(agent-core-v2): remove llm-adapter code duplicated in human/llm

* refactor(agent-core-v2): replace the agent loop with a facade over the human turn state machine

* refactor(agent-core-v2): rename the loop adapter layer to loop/machine

* test(acp-server): implement gateway resolve in the scripted provider stub

* refactor(agent-core-v2): restore v2 batch semantics in the loop machine tools adapter

- delegate each step's tool calls to the v2 tool executor as one whole
  batch: beginBatch arms the expected call set from the llm.done entry,
  per-actor execute calls collect into a pending barrier, and the full
  batch is submitted in tool-call order once complete
- drop the serial batchChain/batchStopped/skipped approximation so the
  executor's prepare-stage stopBatchAfterThis skip, veto short-circuit,
  and ToolScheduler access-based concurrency apply natively again

* fix(build): update flake pnpmDeps hash for the post-absorb lockfile

* fix(agent-core-v2): align loop facade reservations with the machine queue by id

* fix(agent-core-v2): drain persisted wire records before turn end

* fix(agent-core-v2): route executor-level tool errors to the loop error path and backfill aborted tool results on forced abort

* fix(agent-core-v2): keep turn step retrying in the facade step numbering space
2026-09-06 22:26:35 +08:00
Haozhe
04461960e9
fix(migration-legacy): faithfully migrate kimi-cli user data into kimi-code (#3446)
* fix(migration-legacy): faithfully migrate kimi-cli user data into kimi-code

* fix(migration-legacy): faithfully migrate kimi-cli user data into kimi-code
2026-09-02 11:32:23 +08:00
Grapedge
0fd89695f5
refactor(vscode): migrate the webview data layer from ahooks to TanStack Query (#3440)
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
Publish VS Code extension / Publish VSIX to marketplaces (push) Waiting to run
* fix(vscode): keep current and root work dirs visible in directory picker

* refactor(vscode): migrate the webview data layer from ahooks to TanStack Query
2026-09-02 01:42:43 +08:00
Haozhe
026e807cf8
refactor(vis): migrate vis from agent-core v1 to agent-core-v2 (#3438)
* refactor(vis): migrate vis from agent-core v1 to agent-core-v2

- swap vis-server engine deps to @moonshot-ai/agent-core-v2, drop kosong
- rebuild the wire record union from v2 durable payload types
- fold token_counting records into the context-window fill
- read cron state from wire records alongside legacy cron files
- read agent parent/swarm metadata from state.json labels
- align context projection and web rendering with the v2 wire vocabulary
- bundle agent-core-v2 into the vis-server build

* fix(vis): replay legacy compactions per engine rules, tolerate bad cron payloads

- treat every record without keptUserMessageCount (or with an explicit
  legacyTail flag) as a verbatim-tail compaction, matching the engine's
  readContextCompactionShapeInput instead of gating on compactedCount
- derive tokensAfter from the reconstructed shape when the record omits
  it, instead of keeping the stale pre-compaction fill
- skip malformed cron.delete / cron.cursor payloads instead of throwing
2026-09-02 00:00:29 +08:00
Haozhe
ae7a6dc6fb
refactor(acp): remove the legacy acp-adapter package (#3434) 2026-09-01 23:59:20 +08:00
liruifengv
7066950653
fix(oauth): show a cancelled state when authorization is denied on the web page (#3291)
* fix(oauth): show a cancelled state when authorization is denied on the web page

* chore: drop the changeset and the dist-web sync from this PR
2026-08-27 16:20:48 +08:00
Haozhe
f0a609487f
feat(kimi-code): add remote control web tunnel (#3034)
* feat(kimi-code): add remote control web tunnel

Add CLI and TUI entry points for exposing the local web UI remotely.
Bridge HTTP and WebSocket traffic with local authentication and reconnect handling.

* fix(kimi-code): prevent remote control websocket crash

* fix(kimi-code): align websocket dependency versions

* fix(kimi-code): harden remote control connection setup

Reconnect when management closes during the HTTP tunnel handshake.
Reject non-loopback Remote Control binds whose CSP blocks path bootstrap.

* fix(kimi-code): fix remote control rewriting, caching, and WS frame loss

* feat(kimi-code): add remote control QR output

* build: update pnpm dependencies hash

* refactor(kimi-code): remove the --allow-remote-terminals flag

* feat(kimi-code): add remote control lock, rc command, and QR fixes

* fix(kap-server): broadcast user prompts to all session clients on submit

- agent-core-v2: emit prompt.submitted (status running|queued) at enqueue and prompt.started when the turn launches
- kap-server: project prompt.submitted/prompt.started into transcript prompt entities and the live transcript REST response
- update flake.nix pnpmDeps hash for the PR lockfile

* fix(node-sdk): drop v2-only prompt.started from SDK event stream

- event-mapper: add prompt.started to the dropped v2-only prompt lifecycle types (parity with submitted/completed/aborted/steered)
- cli test: assert only visible sub-commands and stub the experimental flag env for determinism

* ci(pkg-pr-new): post custom install comment for npm 12 compatibility

* feat(kimi-code): render remote control QR as inline image on capable terminals

* feat(kimi-code): improve remote control terminal output

- add onboarding, security, device management, and help guidance
- show compact clickable links and QR image fallback details
- report relay and remote device connection lifecycle

* test(agent-core-v2): update tool event snapshot

* revert(ci): keep preview workflow unchanged in rc pr

---------

Co-authored-by: liruifengv <liruifeng1024@gmail.com>
2026-08-25 20:22:06 +08:00
Haozhe
368b4b7400
refactor(agent-core-v2): migrate agent domains to agent runtime architecture (#3175) 2026-08-23 17:36:59 +08:00
Haozhe
2265305e81
refactor(agent-core-v2): replace defineOp/Model with Event2 dispatch and replayable states (#2909)
* refactor(agent-core-v2): replace defineOp/Model with Event2 dispatch and replayable states

- replace defineOp/Op/OpDescriptor/toEvent and defineModel/defineCheckpointedModel with Event2 subclasses: durable classes declare static durable + schema, serialize() keeps the wire record shape byte-frozen, transient classes stay off the journal
- define states via defineState(...).replayable(...).on(Event2, fold): immer produceWithPatches folds with atomic prepare/commit, .undoable() trait drives prompt-submit checkpoints and context.undo, ephemeral kv keys keep imperative set
- degrade IWireService to a journal adapter; the agent event dispatcher owns the pipeline (fold -> set -> appendRecord -> publish) and silent restore
- align downstream event surfaces: kap-server WS envelope timestamp from event.time, klient event schemas gain time, node-sdk/acp-server/print wiring updated
- rewrite gen-wire-manifest/gen-state-manifest for the unified registry and replace the op-uniqueness lint with event-uniqueness

* fix(ci): repair Event2 prompt and media projections

- restore prompt admission and session media materialization
- align transcript, WS, SDK, and replayable media state projections
- update affected tests and generated state manifest

* fix(ci): update prompt event and projection expectations

- update snapshots for the durable prompt.accepted event
- normalize prompt.steered media in transcript projections
2026-08-17 17:38:50 +08:00
qer
741708f948
feat(kap-server): add plugin marketplace and capability REST routes (#2868)
* feat(agent-core-v2): surface a machine-key note from capability installs

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

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

Expose the App-scope plugin and capability services over the wire so
non-CLI hosts (desktop, web) can manage plugins and built-in
capabilities end to end:

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

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

* fix(kap-server): map plugin input errors to 4xx and correct the unsupported test code

- mapPluginError now translates the domain's validation.failed (40001)
  and fs.path_not_found (40409) instead of collapsing client-fixable
  input mistakes (relative source, nonexistent local path) into a
  50001 internal error
- the non-macOS capability install test expected 40923, which this
  branch assigns to capability.install_in_progress; the unsupported
  code is 40924 (macOS runners skip the case, which is why it only
  fails on Linux/Windows CI)

* fix(kap-server): resolve catalog-relative marketplace sources and widen the unsupported-test skip

- The production CDN catalog carries sources relative to the catalog
  URL (./official/*.zip); clients handing them back to POST /plugins
  would hit the local-path normalizer's 40001. Resolve entry sources
  against the configured catalog URL so every returned source is
  directly installable.
- The 40924 install-rejection test only skipped macOS, but kimi-cu is
  also supported on Windows x64 — running it there would start the
  real installer. Skip on every supported platform.

* fix(kap-server): accept the legacy url/downloadUrl marketplace source aliases

Custom catalogs that the CLI already accepts can carry an entry's source
under url or downloadUrl instead of source; the route's strict schema
rejected the whole catalog with 50001. Normalize the aliases before
validation (same precedence as the CLI parser) so those catalogs keep
working through /api/v1/plugins/marketplace.

* fix(kap-server): support local marketplace catalogs and drop conditional spreads

- KIMI_CODE_PLUGIN_MARKETPLACE_URL accepts a plain path or file://
  catalog in the CLI loader; the route only fetched over HTTP, so local
  catalogs 50001'd for desktop/web hosts. Read local catalogs from disk
  and resolve their relative sources against the catalog's directory.
- Replace the marketplace mapping's conditional spreads with direct
  possibly-undefined properties per the repo rule.

* fix: surface capability install notes through klient and convert file:// entry sources

- The klient capabilities contract omitted install.note, so zod parsing
  stripped it and facade callers (node-sdk, TUI) never saw
  'user-skill-migrated'. Add the field and pin it in the facade test
  fixture.
- A marketplace entry source given as a file:// URL fell through to the
  relative-branch and came back as a garbage path; convert with
  fileURLToPath so the advertised source stays installable.

* test(kap-server): keep the new route tests portable to Windows x64

- The capabilities list assertion treated every non-macOS host as
  unsupported, but kimi-cu is supported on Windows x64 — derive the
  expectation from the same platform predicate.
- file:///abs/... is not a valid absolute file URL on Windows (no drive
  root); build the fixture with pathToFileURL from a temp path instead.

* refactor: align the capability note and test helper with repo conventions

- agent-core-v2 keeps explanatory docs in the top-of-file block only;
  the note contract already lives in the capability types header, so
  drop the two member-level doc blocks.
- The plugins route test helper sets the optional fetch body directly
  instead of via a conditional spread.

* fix(kap-server): expand ~ in local marketplace catalog paths

The CLI loader expands ~/ against the home directory; the route read
the path literally, so KIMI_CODE_PLUGIN_MARKETPLACE_URL=~/catalog.json
50001'd for desktop/web hosts while working in the CLI. Share one
localCatalogPath helper (file:// conversion + tilde expansion) between
the catalog read and the relative-source resolver.

* fix(kap-server): expand home-relative marketplace entry sources

A catalog entry with source '~/...' fell through to the catalog-relative
branch and came back as <catalog-dir>/~/... — unresolvable by POST
/plugins. Expand ~ via the shared helper before the absolute/relative
decision.

* fix(kap-server): match CLI field semantics for source aliases and stub the Windows home

- A blank or non-string source no longer shadows the url/downloadUrl
  aliases; the first valid (non-blank, trimmed) of source/url/downloadUrl
  wins, mirroring the CLI parser's stringField.
- The tilde test also stubs USERPROFILE so os.homedir() resolves to the
  fixture home on Windows runners.

* fix(kap-server): read a blank marketplace tier as missing

The CLI parser trims tier and treats a blank as absent (third-party);
the route's enum rejected the whole catalog with 50001. Normalize the
tier alongside the source aliases in the same preprocess.

* fix(kap-server): derive marketplace versions from GitHub release sources

Entries that omit version but encode it in a GitHub release/tag (or
tree/commit) source never surfaced updateAvailable. Derive the version
from the resolved source — same URL shapes as the CLI parser, validated
with the route's strict x.y.z rule (no semver dependency).

* fix(kap-server): fail catalog validation on a source with no usable value

A whitespace-only source with no valid alias passed z.string().min(1)
untrimmed and resolved against the catalog URL into nonsense. Drop the
key during normalization so the schema reports the entry as missing its
source (same outcome as the CLI's 'must define source').

* fix(kap-server): resolve latest versions for bare GitHub marketplace entries

A catalog row whose source is a bare GitHub repo (the production curated
rows are shaped this way) kept version undefined, so updateAvailable
never fired for exactly the entries most likely to update. Resolve the
latest release tag through the /releases/latest redirect — the UI route,
not the rate-limited API — same as the CLI, degrading to no version on
any failure.

* docs(kap-server): note the marketplace version resolution in the plugins route header

* feat(kap-server): mark capability wiring rows in the marketplace response

A client following only /plugins/marketplace + POST /plugins would
install a capability's wiring plugin without its binary runtime, with
no wire-level way to tell. Entries whose id matches a capability's
wiring plugin now carry capabilityId, so clients route them through
/capabilities/{id}:install — the client-side routing pattern the CLI
established (the upstream design that replaced the server-side hook).

* fix(kap-server): fall back to the source-checkout catalog for the default location

When the marketplace location is the built-in default (no server option
or env override) and the fetch fails, read the repo checkout's own
plugins/marketplace.json — the CLI loader's behavior for offline
source-checkout dev. An explicitly configured catalog still fails hard
with 50001. Bundled installs have no checkout file, so the fallback
simply never fires there.

* fix(kap-server): resolve fallback catalog sources against the fallback file

readMarketplaceCatalog returned only the JSON, so entries from the
source-checkout fallback resolved their relative sources against the
(unreachable) CDN URL — coming back as unusable https paths instead of
local directories. The reader now returns the location actually read,
and source resolution uses it.

* fix(kap-server): honor the CLI's marketplace metadata aliases

Custom catalogs using name / shortDescription / websiteURL (accepted by
the CLI parser) lost those fields to schema stripping, falling back to
the entry id. Normalize the aliases in the same preprocess as the
source/tier normalization.

* fix(kap-server): filter marketplace keywords instead of rejecting the catalog

A keywords array with non-string or blank members failed the strict
schema and took the whole catalog down with 50001. Normalize to the CLI
parser's semantics: non-array reads as missing, arrays keep trimmed
non-blank strings only.

* fix(kap-server): treat a blank or non-string marketplace version as missing

The CLI parser reads version through its lenient stringField and falls
through to source-derived versions; the route's schema rejected a
numeric version with 50001 for the whole catalog. Normalize version in
the preprocess like the other fields — the gh-plugin fixture now
carries a numeric version and still derives 2.0.0 from its tag source.

* fix(kap-server): trim marketplace entry ids before the install-state join

A whitespace-padded id survived validation raw and never matched the
installed records (updateAvailable silently lost). Normalize the id in
the preprocess — trimmed, blank rejected — matching the CLI's
requiredString.

* fix(kap-server): gate capability markers to the default catalog

A custom catalog (env or server option) may legitimately carry a
same-id fork of a capability's wiring plugin; marking it capabilityId
would route users to the built-in install. Apply the marker only for
the default catalog (including the source-checkout fallback), matching
the CLI injecting built-in rows only for the default catalog.

* fix(kap-server): compare marketplace versions with real semver

The hand-rolled strict x.y.z check rejected valid semver the CLI
accepts (v-prefixed, prerelease tags), so updateAvailable diverged
between CLI and wire clients. Take the semver package (already in the
monorepo via the CLI) for the update check and the two source-derived
version validators.

* fix(kap-server): validate marketplace entry types and count the dev server as default

- Custom catalog rows with an unsupported type (e.g. integration) were
  stripped by the schema and advertised as installable plugins; the CLI
  rejects the catalog outright. Model the same plugin/managed/guide
  vocabulary.
- scripts/dev.mjs marks its repo-owned catalog with
  KIMI_CODE_PLUGIN_MARKETPLACE_FROM_DEV_SERVER=1 — honor the flag in
  the isDefault check so capability markers and the checkout fallback
  behave exactly like the CLI under the dev marketplace.

* fix(kap-server): join capability rows through their platform wiring plugin id

kimi-cu installs its wiring plugin as kimi-cu-win on Windows x64, so a
catalog row keyed kimi-cu never matched the installed record there (no
installed state, no updateAvailable). The row mapping now knows each
capability's wiring plugin ids and joins through them.

* fix(kap-server): map plugin load failures to 40001

An install source pointing at a directory/zip with a missing or invalid
manifest throws plugin.load_failed — a client-fixable input error that
fell through to 50001. Map it to validation.failed alongside the other
input mistakes.

* build(kap-server): align @types/semver with the workspace version

sherif rejects multiple workspace versions of one dependency; the CLI
pins @types/semver at ^7.7.0.

* refactor(agent-core-v2): share the plugin marketplace client/parser across hosts

The kap-server marketplace route grew its own copy of the CLI's catalog
loading/parsing logic (lenient aliases, blank-means-missing fields,
source resolution, GitHub version derivation) — two implementations of
a public, hand-writable format would drift on every catalog change.
Move the read/parse/version machinery into the plugin domain as
app/plugin/marketplace (pure functions, no DI): the CLI keeps a thin
wrapper owning configured-source resolution and its checkout fallback,
and the route keeps only the wire concerns (install-state merge,
capabilityId markers, error envelopes). plugins.ts drops ~230 lines of
duplicated machinery.

One deliberate behavior fix rides along: tilde entry sources now expand
against the home directory at parse time (the CLI previously passed
them through literally, failing later at install validation).

* docs(agent-core-v2): fold the marketplace module's member docs into the file header

The package convention keeps explanatory comments in the top-of-file
block only; the moved parser carried several function/member-level
JSDoc blocks from its CLI home. The header now carries the format
contract, leniency rules, source/version resolution order, built-in
masking semantics, and the fallback gating rule.

* docs(agent-core-v2): drop the remaining statement comments in the marketplace module

The header carries the rationale (update semantics, GitHub ref shapes,
the releases/latest choice); the convention allows nothing beside
statements.

* fix(kimi-code): import the shared marketplace module by its deep path

constant/app.ts is evaluated on every CLI invocation; re-exporting from
the agent-core-v2 root would pull the whole engine module graph into
startup. The package's wildcard subpath export lets both CLI files take
only the pure marketplace module (node builtins + semver).

* feat(kap-server): fan plugin and capability lifecycle out as global WS events

Clients currently poll the plugins/capabilities REST surfaces and can
hold stale rows while another client mutates the set. Publish two global
events instead:

- event.plugin.changed — fired off IPluginService.onDidReload, so any
  install/enable/disable/remove from any client reaches every host
- event.capability.changed — every capability install progress
  transition (CapabilityService gains onDidChangeInstall), so rows
  update live and settle is observable without polling

Both ride the existing global fan-out (no subscription needed) and are
documented in the wire schema registry.

* fix: register the lifecycle events in the wire union and tidy the contract header

- event.plugin.changed / event.capability.changed were declared but not
  part of agentEventSchema, leaving the wire catalog incomplete.
- The onDidChangeInstall member doc moves into the capability contract
  file header (package comment convention).

* feat(protocol): mirror the plugin/capability lifecycle events in the shared WS schema

Clients and e2e harnesses validating server frames against
@moonshot-ai/protocol would reject event.plugin.changed /
event.capability.changed. Register both in the shared catalog (TS
interfaces, zod schemas, and both unions), matching the
model_catalog.changed precedent for global events.

* fix(kap-server): prefer the platform wiring plugin when joining capability rows

A stale same-id record (e.g. a raw kimi-cu plugin next to the real
kimi-cu-win wiring on Windows x64) previously won the join, showing the
wrong installed state and update availability. Capability rows now join
through the wiring plugin ids in platform preference order before
falling back to the catalog id.

* fix(kap-server): put the github metadata of plugin summaries on the wire schema

GitHub-sourced plugin summaries carry github {owner, repo, ref,
installedSha} from the domain; the route serializes raw domain objects,
so the field reached clients undocumented. Declare it in
pluginSummarySchema so the OpenAPI surface matches reality.

* test(node-sdk): cover the new lifecycle events in the exhaustive switch

The event-type exhaustiveness test broke when the shared protocol union
gained event.plugin.changed / event.capability.changed.

* fix(kap-server): mark capability progress events volatile

Per-chunk download progress transitions ride the same fan-out as
durable frames and were being persisted to the __global__ journal —
hundreds of stale frames per install. event.capability.changed is
live-only state, so it joins the volatile list alongside
event.di.unit_changed; the settle frame stays recoverable via a direct
capability read. event.plugin.changed remains durable (rare, and a
reconnecting client should replay it).

* feat(kap-server): inject built-in capability rows into the default catalog response

The checked-in production catalog carries kimi-webbridge but not
kimi-cu — the CLI injects built-in rows client-side, so wire clients
never saw Kimi Computer Use in /plugins/marketplace. For the default
catalog the route now appends supported capabilities the catalog lacks
(static descriptors via ICapabilityService.describeCapabilities — no
detector probes), marked with capabilityId and a capability:<id>
sentinel source so installs still route through the capability
surface.

* fix(kap-server): run injected capability rows through the install-state join

The injected kimi-cu row hardcoded installed: undefined, so an
already-installed capability still read as installable. Injection now
happens before projection, so injected rows get the same backing-plugin
join (installed state, update badge, capabilityId marker) as catalog
rows. Also moves the describeCapabilities note into the contract header
(package comment convention).

* test(kap-server): gate the injected-row assertions on platform support

kimi-cu injects only where supported (macOS / Windows x64); on Linux CI
the row is correctly absent.

* fix(protocol): classify capability progress as volatile in the shared catalog

kap-server never journals event.capability.changed (it is in the
server-local volatile list); shared-protocol clients reading
isVolatileEventType would treat per-chunk progress frames as durable
and replayable. Mirror the classification.

* fix(kap-server): hide capability rows on unsupported platforms

Catalog-carried capability rows (kimi-webbridge in the default catalog)
were marked with capabilityId regardless of host support — on an
unsupported platform clients would route into an impossible capability
install. Rows whose capability is unsupported are now excluded from the
default-catalog response entirely (the CLI hides its built-in rows the
same way).
2026-08-14 15:45:34 +08:00
Haozhe
8c766a6c30
feat(agent-core-v2): add the L3 unit layer and the Feature seam (#2678)
* feat(agent-core-v2): add the L3 unit layer and the Feature seam

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

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

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

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

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

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

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

* build(nix): update the pnpmDeps fetch hash
2026-08-06 18:22:36 +08:00
liruifengv
541ddd2d89
chore(web): replace apps/kimi-web with the code-app web bundle (#2599)
* chore(web): remove apps/kimi-web in favor of the code-app web bundle

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

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

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

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

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

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

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

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

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

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

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

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

Bundle rebuilt from code-app main (upstream parity ports #175, pinned
sidebar #176) with the CLI version 0.32.0 embedded.
2026-08-05 13:38:30 +08:00
Haozhe
119a33f7f1
feat(minidb): persistent index generations and lifecycle hardening (#2604)
Some checks are pending
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* perf(minidb): skip idle everysec fsyncs and add lifecycle stats

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore: fix oxlint type-aware lint errors
2026-08-04 20:38:42 +08:00
Haozhe
1328b32037
feat(acp): add experimental agent-core-v2 ACP server (kimi acp-v2) (#2571)
* feat(acp): add agent-core-v2 ACP server

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Guard both model-bound launch paths (plain prompt and skill activation)
with a synchronous in-flight check and reject with invalid_request
(turn.agent_busy), matching the legacy adapter's busy semantics. Local
slash handling (builtins, unknown-command answers) is unaffected.
2026-08-04 10:20:24 +08:00
qer
326e1fb6ce
fix(web): restore chat code block styling after the markstream upgrade (#2459)
* fix(web): restore chat code block styling after the markstream upgrade

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

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

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

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

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

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

The markstream family ships breaking rendering changes in patch releases
(1.0.8 swapped the code-block engine and renamed its DOM classes), so a
floating caret range hands merge control to upstream. Pin markstream-vue,
stream-diffs, stream-markdown and stream-monaco exactly — upgrades become
deliberate actions with a visual check, same posture as @chenglou/pretext
in the same file.
2026-07-31 19:14:02 +08:00
qer
5c0ec2938a
fix(web): upgrade markstream-vue to 1.0.9-beta.1 and enable Monaco code highlighting (#2415)
* fix(web): upgrade markstream-vue to 1.0.9-beta.1 and enable Monaco code highlighting

* fix(nix): update pnpm deps hash for markstream-vue 1.0.9-beta.1
2026-07-30 20:01:57 +08:00
liruifengv
ea81c9a3c5
feat(kap-server): expose the managed-account profile at GET /oauth/userinfo (#2363)
* feat(kap-server): expose the managed-account profile at GET /oauth/userinfo

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

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

* chore: drop the userinfo endpoint changeset

* test(kap-server): pass the required host identity in the userinfo route test
2026-07-30 15:05:55 +08:00
Haozhe
ceaa96942b
feat(kap-server): add global message search with literal and live-session modes (#2321)
* feat(kap-server): add the /api/v1/search global message search endpoint

Cross-session full-text search over user messages, assistant text and
session titles, backed by a minidb index under <home>/search-index with a
single-writer lock election and read-only WAL catch-up for other
processes. Hits carry transcript anchors (turn ordinal and step id) so
clients can jump straight to the matching turn or step.

* feat(kimi-inspect): add a search view with chat-timeline navigation

The left rail gains a Search view over the global search endpoint, with
role and sort filters and cursor pagination. Clicking a hit switches to
the chat view and navigates to its session, agent, turn and step — the
channel pages the turn into the loaded window, scrolls it into view and
flashes the target briefly.

* chore(kimi-code): start the dev server without built web assets

The repo's dev server scripts (dev:server, dev:kap-server,
dev:kap-server:multi, dev:server:restart) now set KIMI_CODE_DEV_SERVER=1.
When it is set and dist-web/index.html is missing, kimi web starts the
API server without the bundled web UI instead of failing at startup,
so backend dev no longer requires a kimi-web build.

* feat(kap-server): add literal substring search and a live session route

- minidb: text indexes accept an injectable tokenizer/queryTokenizer,
  and a hashed 2/3-gram tokenizer (NFKC + lowercase, code-point
  windows) backs substring search; tokenizer names persist in
  db.textindexes.json with backward-compatible defaults
- /api/v1/search gains mode: 'literal' — n-gram candidates confirmed
  against the original text (zero false positives), with an
  'candidate_cap' incomplete flag when the candidate set truncates
- container.session_id queries against a session live in this process
  scan the in-memory transcript store instead of the index (both
  modes); the response's source: live|index field names the serving
  route and rides in the page-token fingerprint
- kimi-inspect: exact-match toggle and source badge in the search
  view, plus an in-chat session search bar with jump-to-hit navigation

* test(kimi-inspect): avoid stringifying BodyInit in search api tests
2026-07-28 21:47:50 +08:00
Haozhe
f79fde2b90
feat(node-sdk): migrate the SDK method surface to agent-core-v2 (#2262)
* feat(node-sdk): add agent-core-v2 backed SDKRpcClientV2 harness

- add SDKRpcClientV2 wiring the v2 engine (DI x Scope) in-process via the
  klient memory transport, with getExperimentalFeatures migrated to
  klient.global.flags.list() and unmigrated methods failing fast
- export createKimiHarnessV2 / SDKRpcClientV2 from the SDK index
- wire the experimental v2 gate into the CLI interactive shell (run-shell)
- extend build-dts to bundle agent-core-v2 and klient declarations

* feat(node-sdk): migrate listWorkspaceSkills to agent-core-v2

- add engineAccessor escape hatch exposing the in-process engine's app-scope
  service accessor for SDK methods the klient facade does not cover yet
- implement listWorkspaceSkills via ISkillDiscovery plus the v2 user/project
  root helpers and BUILTIN_SKILLS (plugin skills and skillDirs still gaps)
- add a v1-v2 parity test pinning identical return values per migrated
  method, with understood gaps listed explicitly in KNOWN_DIFFS

* feat(node-sdk): migrate the SDK method surface to agent-core-v2

- implement the remaining SDKRpcClientBase methods on SDKRpcClientV2,
  routed through the klient facade where covered, the engineAccessor
  escape hatch where the engine has a service, or SDK-side rebuilds on
  v2 primitives where only primitives exist (config shape mapping,
  global mcp.json store, MCP OAuth flows, importContext, session
  warnings, print background policy)
- translate the v2 event stream into the v1 Event union and bridge
  approval/question/user_tool interactions per live session
- rebuild resume replay by folding the v2 wire.jsonl through the v1
  agent restore pipeline, so resumed sessions render history again
- keep deleteSession as not_implemented; the v2 engine has no delete
  capability
- extend the v1-v2 parity suite to every migrated method, pinning
  understood engine differences in KNOWN_DIFFS
- add the dev:cli:v2 root script to launch the TUI on the v2 engine

* fix(node-sdk): await the v2 undo and compaction-cancel agent calls

* test(cli): spread the real oauth module in the telemetry test mock

* feat(node-sdk): forward v2 engine telemetry to the host telemetry client

* feat(cli): gate the v2 TUI route behind a dedicated KIMI_CODE_TUI_V2 switch

* fix(node-sdk): honor skillDirs on the agent-core-v2 SDK route

The v2 SDK client accepted KimiHarnessOptions.skillDirs (the CLI's
--skills-dir) but never seeded it into the engine, so explicit skill
dirs were silently dropped on the v2 TUI route and the Skill tool
could not find skills from them. Seed skillCatalogRuntimeOptions at
bootstrap and let listWorkspaceSkills resolve the explicit dirs as
the user source, matching the engine's session skill catalog.

* refactor(cli): gate the v2 TUI route behind the master experimental flag again

Drop the dedicated KIMI_CODE_TUI_V2 switch: the TUI v2 harness route is
gated by KIMI_CODE_EXPERIMENTAL_FLAG, the same master switch as the
kimi -p v2 route. The gate tests are kept with updated assertions, and
dev:cli:v2 sets the master flag again.
2026-07-28 20:29:45 +08:00
Haozhe
e556088458
feat(tree-sitter-bash): add a pure-TypeScript bash parser and an agent-core-v2 bashParser service (#2016)
* feat(tree-sitter-bash): scaffold pure-TypeScript bash parser package

Add packages/tree-sitter-bash with the SyntaxNode tree model (UTF-16
code-unit offsets, tree-sitter-bash named-node type names), a parse
budget (deadline + node cap) with an aborted ParseResult variant, and
a placeholder parse() to be replaced by the real lexer/parser.

* feat(tree-sitter-bash): add lexer and core recursive-descent parser

Cover the permission-analysis grammar subset: lists, pipelines,
commands, words, quotes, expansions, command/process substitution,
subshells, the full redirect operator set, heredocs and comments,
with tree-sitter-bash named-node type names. Long scan loops check
the parse deadline via budget.progress() so large literals cannot
exhaust the node cap; parse depth is bounded on all recursion
chains.

* feat(tree-sitter-bash): support the full bash grammar

Add compound commands (if/while/until/for/c-style-for/select/case/
function/compound/do_group), test commands with reference-exact
extglob/regex right-hand-side rules, a Pratt expression engine for
arithmetic expansion shared across arith/c-for/test modes, arrays
and subscripts, declaration/unset commands, ansi-c/translated
strings and brace expressions. Reserved words are recognized only
in statement position. Case-aware balanced scanning keeps command
substitutions intact around case items, expression leftovers are
kept as ERROR nodes instead of dropped, and recursion depth is
bounded per chain (substitution 150, parse 500, lexer scan 1024).

* test(tree-sitter-bash): add differential fixtures, corpus, fuzz and perf suites

Turn the ad-hoc wasm comparison work into permanent infrastructure:
a differential helper pinning reference-equivalent and
known-difference samples against the real tree-sitter-bash wasm
(478 match / 79 known-diff fixtures plus the official v0.25.0
corpus), a three-way consistency check between fixtures, the
known-difference registry and the README, deterministic seeded
fuzz with tree-integrity assertions, and performance smoke tests.
Converges 15 further divergence groups found by systematic probing
and documents the rest; the full suite is 853 tests in ~4s.

* feat(agent-core-v2): add App-scope bashParser service

Wrap the pure @moonshot-ai/tree-sitter-bash package as
IBashParserService (L1, no dependencies): parse(source, options)
returns a wire-safe BashParseResult whose nodes drop the cyclic
parent link so trees can cross the RPC boundary. Budget exhaustion
surfaces as { ok: false, reason: 'aborted' }, malformed input as
hasError — never a throw.

* chore: add changeset for the bash parser service

* chore: update pnpmDeps hash after adding tree-sitter-bash package

* fix(agent-core-v2): register bashParser service with ScopeActivation

The registration was written against the removed InstantiationType API
(#/_base/di/extensions); switch to ScopeActivation.OnDemand from
#/_base/di/scope so the package typechecks and the service registers.

* feat(kimi-inspect): add Bash Parser view

Add a fourth icon-rail tab that exercises the App-scope bash parser
service over the debug RPC surface: a source textarea with a parse
budget (timeoutMs / maxNodes), a dropdown of curated examples adapted
from the tree-sitter-bash differential fixtures, and an expandable
syntax tree with per-node type, UTF-16 range and leaf text, plus
hasError / aborted / node-count badges.

* fix(agent-core-v2): snapshot bash syntax trees iteratively

A long left-associative chain (e.g. an arithmetic expression with a
few thousand operands) parses into a tree thousands of levels deep
while still within budget; the recursive DTO conversion then overflowed
the JS call stack and made parse throw RangeError, breaking the
never-throws contract. Convert the tree with an explicit stack, the
same approach as the parser's own materialize.

* feat(kimi-inspect): add a deep-arithmetic example to the Bash Parser view

A thousand-operand left-associative chain fills the textarea with a
thousand-level binary_expression tree — the shape that once overflowed
the DTO conversion. Deeper chains still parse in-process but cannot
cross the JSON RPC transport (V8 call-stack limit in serialization),
so the example stays within the wire limit.

* chore: update pnpmDeps hash for the rebased lockfile

The rebase onto main merged pnpm-lock.yaml, invalidating the recorded
fetchPnpmDeps hash; use the hash CI computed for the merged lockfile.
2026-07-28 14:29:08 +08:00
qer
425cfdf53f
chore(web): upgrade markstream-vue to 1.0.7 (#2294)
* chore(web): upgrade markstream-vue to 1.0.7

Fix garbled code-block line numbers reported on 0.29.2: the async
code-block loading fallback rendered unstyled (proportional font,
code overlapping the line-number gutter) and with an over-estimated
reserved height that clipped leading lines. markstream-vue 1.0.6 adds
self-contained fallback styles and 1.0.7 fixes the height estimate.

* chore(nix): update pnpmDeps hash for markstream-vue 1.0.7

* chore(web): scope lockfile update to the markstream-vue graph

Regenerate pnpm-lock.yaml with a plain install so only markstream-vue
and its transitive deps move (markdown-it-ts, stream-markdown-parser,
etc.); unrelated toolchains (e.g. lightningcss in kimi-inspect) stay
pinned as before. Addresses review feedback.

* chore(nix): update pnpmDeps hash after lockfile scoping

* chore(changeset): shorten release note to one user-facing sentence
2026-07-28 13:25:38 +08:00
7Sageer
77618e38c3
feat(kap-server): wire cloud telemetry for engine events (#2230)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(kap-server): wire cloud telemetry for engine events

The v2 engine registers a full telemetry event catalog, but kap-server
never attached an appender, so events from web-hosted sessions were
dropped to the null appender. Add an opt-in `telemetry` start option
that attaches a CloudAppender (app_name kimi-code-cli, ui_mode web,
matching the v1 `kimi web` host conventions), still gated by the
config `telemetry` toggle, with periodic flush and a bounded flush on
close. The option defaults off so tests never post to the real
endpoint; the CLI's `kimi web` host enables it.

* fix(kap-server): honor telemetry disable environment

* fix(agent-core-v2): isolate session telemetry context

* fix(kap-server): seed telemetry client version

* fix(cli): share telemetry shutdown deadline

* fix(telemetry): make shutdown ownership durable

* fix(kap-server): keep telemetry shutdown best-effort
2026-07-27 17:18:16 +08:00
7Sageer
ce0e3ceb04
feat: support custom agent files (#1735)
Some checks are pending
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / typecheck (push) Waiting to run
* feat: support custom agent files

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

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

* chore: shorten custom agents changeset

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

* docs: drop v2 engine notes from custom agents docs

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

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

* fix: tighten custom agent behavior

* fix: address custom agent review findings

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix: enforce session tool policy across agents

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* test: fix invalid custom agent fixture

* fix(cli): reject multiple agent selectors

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

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

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

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

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

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

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

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

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

* chore: slim changeset wording to user-facing language

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

* chore: shorten changeset entries to one-line summaries

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

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

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

* chore: clear the comments

---------

Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
2026-07-21 15:01:43 +08:00
Haozhe
5ae60fa673
feat(transcript): add unified transcript layer, drop the /api/v2 RPC surface (#1888)
* feat(transcript): add unified transcript layer and v1 surface

- add packages/transcript: agent-granular L1 store, idempotent L2 ops,
  off/turn/block/delta L3 granularity, L4 view registry, and turn-cursor
  pagination; sole owner of all transcript wire types
- kap-server: engine-event-driven TranscriptService with history backfill,
  GET /sessions/{id}/transcript, and transcript.ops WS deltas with
  per-connection granularity control
- kimi-inspect: render ChatView from the transcript surface (REST pages +
  delta-only WS) instead of context memory
- sync flake.nix workspace lists and document packages/transcript and
  packages/server-e2e in AGENTS.md

* fix(transcript): project live prompts and anchor backfilled items

- agent-core-v2: carry the extracted prompt text on turn.started so the
  transcript projector can render the user input at turn open (the context
  append with the same text is not a bus event and lands later)
- kap-server: keep the prompt through turn.ended; anchor backfilled
  markers/taskrefs to their following snapshot turn so replaying history
  after live turns arrived keeps the historical order
- transcript: add an optional beforeTurn placement anchor to
  marker.upsert / taskref.upsert; anchored inserts land before the first
  turn at or past the anchor instead of appending blindly

Addresses review feedback on #1888.

* fix(transcript): adopt backfilled stream frames and group goal turns

- kap-server: on mid-stream attach, adopt the backfill-seeded stream frame
  (id + offset) instead of opening an empty one whose upsert clobbered the
  seeded text and whose offset-0 appends could not land
- transcript: group a turn-opening system_trigger (goal continuation) into
  its own turn so cold rebuilds keep the turn boundary and stay
  ordinal-aligned with the engine's live turn numbering

Addresses review feedback on #1888.

* fix(transcript): drop stale live stores on close and heal after turns

- drop a session's live transcript store when the session closes or archives
  (lifecycle events plus a re-check on the cached-entry path) so reads fall
  back to the cold rebuild instead of serving a stale store
- re-read an ended turn from the persisted wire records (debounced per
  agent) and merge it back live-first: headers keep live state/timestamps
  while origin/prompt recover from disk, and truncated text/thinking frames
  from a mid-turn attach are restored only when the persisted text is
  longer — a fresh live frame or a lagging flush is never reverted

Addresses review feedback on #1888.

* fix(transcript): adopt seeded tool frames and route subagent questions

- kap-server: adopt the backfill-seeded tool frame when a tool.result
  arrives for a call that started before the projector attached, so the
  output lands instead of being dropped (the producer-store lookups now
  ride one projector options object)
- agent-core-v2: record the owning agent on question interactions
  (ISessionQuestionService.request gains an agentId option, passed by
  AskUserQuestionTool from its agent scope) so a subagent's questions
  route to its own transcript and WS events instead of 'main'

Addresses review feedback on #1888.

* fix(transcript): adopt parent frames, namespace live markers, redact resets

- kap-server: fall back to store adoption when subagent.spawned links a
  parent tool call that started before the projector attached, so the
  agentRefs link is not lost on mid-bind attaches
- kap-server: id live markers in their own namespace (live-mN) — the cold
  rebuild numbers its markers m1... too, and a colliding id made the
  store's upsert replace the historical marker instead of appending
- transcript/kap-server: redact transcript.reset snapshots to the
  subscriber's grade (below 'block' the step/frame detail is stripped), so
  a 'turn'-grade subscription no longer receives full content on resets

Addresses review feedback on #1888.

* fix(transcript): fold blocked turns into failed, drop inline v2 comment

- kap-server: map turn.ended reason 'blocked' to the 'failed' transcript
  state, matching the engine's TurnEndReason wire contract and the v1
  mapTurnReason folding (it was presented as a user cancellation)
- agent-core-v2: move the question agentId rationale into the ask-user
  file header — package rules keep comments in the top-of-file block only

Addresses review feedback on #1888.

* fix(transcript): keep roster descriptors and resolved-event agents

- kap-server: keep the metadata-seeded roster descriptor (parentAgentId /
  label) when an on-demand history backfill lands its roster entry, instead
  of downgrading it to { agentId, type }
- kap-server: remember each interaction's owning agent and stamp it on the
  resolved question/approval v1 events — they were hard-coded to 'main', so
  an agent-filtered subscriber saw a subagent's question open but never
  close

Addresses review feedback on #1888.

* fix(transcript): defer pending seeding, skip ghost roster entries

- kap-server: announce interactions pending at bind time only after the
  initial backfill (new TranscriptBinding.seedPendingInteractions), and
  adopt the seeded tool frame on the request/resolve paths, so a
  pre-existing approval lands next to its backfilled tool call and keeps
  the approvalId back-link
- kap-server: skip the roster entry when a probed agent id has neither a
  roster presence nor persisted content (agent_id=nope no longer conjures
  a ghost subagent)
- agent-core-v2: fold the turnEvents import rationale into the loop file
  header (package rule: comments live in the top-of-file block only)

Addresses review feedback on #1888.

* fix(transcript): reject hostile agent ids, honor the agent allowlist

- transcript/kap-server: validate agent ids as plain names (no separators,
  no dot segments) at the REST query layer and again before the id is
  joined into the wire-records path — an authenticated client could
  otherwise read a wire.jsonl outside the agents directory
- kap-server: compose the legacy v1 agent allowlist with transcript
  grades on every fan-out path (initial resets, per-ops fan-out,
  roster-driven resets), so a filtered connection no longer receives
  other agents' transcript frames

Addresses review feedback on #1888.

* fix(transcript): settle foreground shell tasks on shell.completed

- agent-core-v2: emit a transient shell.completed event when a foreground
  `!` command settles (detached runs keep reporting through the task
  lifecycle) — the generic task.terminated never fires for foreground
  tasks, so their transcript cards were stuck at 'running'
- kap-server: map shell.completed to the terminal transcript task state
  (completed/failed) and classify the event as volatile on both v1
  durability gates, like its shell.* siblings

Addresses review feedback on #1888.

* fix(transcript): replay early resolves, reset on a widened filter

- kap-server: register bind-time pending interactions without frames so a
  resolve arriving before the post-backfill seed still routes, then replay
  it at seed time — request and resolve land together with the approvalId
  back-link, instead of the interaction vanishing entirely
- kap-server: treat an agent newly admitted by a broadened legacy agent
  filter as owed a transcript.reset even when its grade transition is a
  no-op (delta → delta) — its ops were suppressed so far, so it has no
  baseline otherwise

Addresses review feedback on #1888.

* fix(transcript): keep materialized transcripts on agent disposal

- kap-server: only the projector dies with the agent scope — the
  materialized transcript and roster entry now survive disposal (the
  roster mirrors session metadata, which keeps completed agents).
  Dropping them lost already-served history for good: the backfill cache
  dedupes per agent, so the next read rebuilt an empty shell instead of
  replaying the persisted records

Addresses review feedback on #1888.

* fix(transcript): anchor refreshes by oldest turn, group slash turns

- kimi-inspect: re-cover the previously loaded window after a refresh by
  paging until the previous OLDEST turn is loaded again (extracted as
  recoverLoadedWindow) — a count-based stop silently dropped the window's
  head once new turns shifted the server window
- transcript: group user-slash skill/plugin activations into their own
  turn (marker included), mirroring the engine's isRealUserPrompt — their
  assistant output no longer folds into the previous turn on cold rebuilds;
  other triggers stay marker-only

Addresses review feedback on #1888.

* fix(transcript): compare all tool fields, overlay in-flight backfills

- transcript: include toolCallId/name/view/input in the tool frame
  equality check — an upsert correcting only those was dropped as a no-op,
  leaving stale tool metadata on clients
- kap-server: overlay the loop's active turn as 'running' after a backfill
  (cold grouping marks every rebuilt turn completed, so a live turn showed
  as finished until it ended); snapshot data supplies origin/prompt, and a
  projector-owned running header is never downgraded

Addresses review feedback on #1888.

* fix(transcript): gate ops before the seed, re-assert running headers

- kap-server: gate the transcript ops fan-out (and roster-driven resets) on
  a per-connection seeded flag set only after the baseline reset has
  landed — a subscriber joining mid-stream no longer receives deltas
  against an empty baseline
- kap-server: always re-assert the loop's active turn as 'running' after
  the snapshot ops in a backfill (skipping the overlay when a live running
  header existed let the snapshot's cold 'completed' header downgrade it);
  live header fields win over the snapshot's

Addresses review feedback on #1888.

* docs(agent-core-v2): fold turnEvents notes into the file header

Move the turn.started prompt rationale from field/function TSDoc into the
module header — package rules keep comments in the top-of-file block only.

Addresses review feedback on #1888.

* fix(transcript): emit shell failure output, dispose per-agent listeners

- agent-core-v2: emit the synthesized failure text as a final shell.output
  chunk before shell.completed (it was never streamed, so failed
  foreground commands showed empty output in transcript tasks until a
  full rebuild)
- kap-server: track each agent's bus subscription per agent and dispose it
  in onDidDispose — the listener captures the projector, so a disposed
  agent no longer keeps projecting late events into the store

Addresses review feedback on #1888.

* fix(transcript): route shell events by task id, tidy question docs

- agent-core-v2: keep the commandId → foreground-task-id mapping and carry
  taskId on shell.output / shell.completed, so consumers attaching
  mid-command (having missed shell.started) can still route output and the
  terminal state
- kap-server: fall back to the event's taskId in the shell output/completed
  projectors and seed the shell task before the first chunk, so output is
  preserved and the terminal upsert cannot clobber it with an empty tail
- agent-core-v2: fold the question agentId note into the question.ts file
  header (package rule: comments live in the top-of-file block only)

Addresses review feedback on #1888.

* fix(transcript): tighten agent id validation, match kinds in heals

- transcript: constrain agent ids to a filename-safe shape
  ([A-Za-z0-9._-], <=128 chars) — NUL-containing or overlong ids made the
  wire-records read throw unhandled errors (500) instead of failing
  validation
- kap-server: require the live frame's kind to match before the post-turn
  heal's length shortcut — a kind-mismatched frame (the projector guessed
  the stream kind wrong mid-turn) is now replaced by the persisted one
  instead of being skipped

Addresses review feedback on #1888.

* fix(transcript): heal missed tool results, seed pendings per agent

- kap-server: re-emit tool frames in the post-turn heal when the live step
  lacks the frame or the live frame lacks the outcome the persisted one
  carries (a tool.result dropped in the attach race is otherwise
  unrecoverable); live-only extras (display / agentRefs / approvalId) are
  preserved, and frames with a live outcome stay untouched
- kap-server: scope seedPendingInteractions by agent — the initial seed
  after backfillMain covers main-owned pendings, and each subagent's
  pendings seed after its own on-demand backfill, so placement and the
  approvalId back-link find the persisted tool frames

Addresses review feedback on #1888.

* fix(protocol): register shell.completed on the v1 event surface

- packages/protocol: add ShellCompletedEvent (plus optional taskId on
  shell.output / shell.completed and prompt on turn.started) to the event
  interfaces, zod schemas, the agent event union, and the volatile list —
  schema-validating consumers previously rejected the forwarded
  shell.completed frames outright
- kap-server: mirror the same fields in the v1 events-zod module

Addresses review feedback on #1888.

* test(node-sdk): cover shell.completed in the exhaustive event switch

The SDK's session-event type test asserts exhaustiveness with assertNever;
register the new event there (CI typecheck caught it).

Addresses review feedback on #1888.

* fix(transcript): source cold-session rosters from session metadata

- kap-server: add TranscriptService.readColdRoster (persisted state.json →
  descriptors, mapped like the live seeding) and use it for the cold
  transcript path — the requested agent id is only appended when it has
  content (or is main), so an empty probe (agent_id=nope) no longer
  fabricates a ghost roster entry, matching the live path

Addresses review feedback on #1888.

* fix(transcript): emit taskrefs when seeding missed shell commands

- kap-server: the mid-command-attach seeding in onShellOutput now emits
  the matching taskref.upsert (exactly like onShellStarted), and
  onShellCompleted emits one when the whole command was missed — the task
  no longer exists only in the global map with no timeline item to render

Addresses review feedback on #1888.

* fix(transcript): defer unseeded live pendings, keep cold tools running

- kap-server: pendings created before their owning agent's seed has run
  now defer into the same unseeded queue as bind-time ones (tracked per
  agent) — announcing them during the backfill window misplaced them into
  a synthetic step with no later repair
- transcript: cold grouping initializes tool frames as 'running' and lets
  the tool-message branch transition them to done/error — an approval-
  gated or still-executing tool no longer shows as completed on rebuilds

Addresses review feedback on #1888.

* fix(transcript): seed live-created agents, merge backfills live-first

- kap-server: agents created after binding are marked seeded immediately —
  their projector covers every event from creation on, so their pendings
  announce without waiting for an explicit history read (which previously
  left live subagent approvals/questions stuck in the unseeded queue)
- kap-server: the initial backfill merges turns live-first via
  healTurnOps (snapshotToOps gains a turn-mapper parameter) — live frame
  fields landed during the disk read (display/approvalId, longer text)
  are no longer replaced by the staler persisted version

Addresses review feedback on #1888.

* fix(transcript): count pages in turn segments, not head units

- transcript: the leading non-turn unit no longer consumes a turn slot —
  pages are counted in turn segments and the head unit rides only with the
  page reaching the first turn. A timeline with a head marker and exactly
  pageSize turns used to drop the marker from the newest page and
  hallucinate an older marker-only page (has_more: true with no older
  turns)

Addresses review feedback on #1888.

* fix(transcript): send baseline resets after cursor replay

- kap-server: broadcaster.subscribe gains deferTranscriptReset (recording
  prev grades/filter per target) plus flushTranscriptSeed; the v1
  connection defers the transcript baseline on cursor-carrying
  (re)subscribes and flushes it after replay — a reconnecting client no
  longer sees the reset's current seq ahead of the replayed lower-seq
  backlog

Addresses review feedback on #1888.

* fix(transcript): gate the ops fan-out only when a reset is coming

- kap-server: willSendTranscriptReset decides upfront whether any reset
  will be sent (grade upgrade or widened legacy filter); a same-grades
  resubscribe no longer un-seeds the target, so ops emitted mid-resubscribe
  keep flowing instead of being silently dropped by the fan-out gate

Addresses review feedback on #1888.

* fix(transcript): seed subscribers even when no reset is owed

- kap-server: a no-reset subscription (e.g. a client subscribing to a
  fresh session with an empty roster) now still marks the target seeded
  after subscribeTranscript completes — roster resets and ops would
  otherwise stay gated forever once agents appear

Addresses review feedback on #1888.

* fix(transcript): guard mismatched appends, expose prompt via klient

- transcript: appendAtOffset now treats an overlapping chunk whose head
  does not match the local tail as a gap (diverged stream) instead of
  silently rewriting from the offset and dropping local content
- klient: add the optional prompt field to the turn.started event schema
  so SDK listeners receive it instead of zod stripping it

Addresses review feedback on #1888.

* fix(transcript): open turns for subagent run prompts in cold grouping

- transcript: add the subagent system trigger to the turn-opening set —
  a subagent's run prompt (persisted as system_trigger/'subagent') always
  launches a new engine turn, so resumed subagent histories no longer fold
  the response into the previous turn or lose the prompt

Addresses review feedback on #1888.

* fix(transcript): guard bus subscriptions independently of projectors

- kap-server: subscribeAgent now guards on a dedicated subscribedAgents
  set instead of projector existence — a projector seeded before its
  agent's handle exists (e.g. during an on-demand backfill) no longer
  blocks the bus subscription, so the agent's live events keep flowing

Addresses review feedback on #1888.

* fix(kimi-inspect): reconcile the transcript on every socket open

- apps/kimi-inspect: TranscriptWs now reports onReconnected on the FIRST
  successful open too, not only on re-established ones — ops emitted
  between the REST page load and the subscription (a delayed or failed
  first connection) were previously lost onto a stale store; the consumer's
  refresh guard drops the no-op call while the initial load is in flight

Addresses review feedback on #1888.

* fix(transcript): derive the active step, dedupe tool error rendering

- kap-server: the projector gains a stepOrdinal lookup backed by the
  engine's activity view (resolved lazily through the agent lifecycle), so
  deltas after a late attach at step >= 2 land in the real active step
  instead of a synthesized t<N>.1
- apps/kimi-inspect: render a tool frame's error only when it differs from
  its output — onToolResult sets both to the same string for failed tools,
  which drew the failure twice in red

Addresses review feedback on #1888.

* fix(kimi-inspect): reconcile the transcript on the subscribe ack

- apps/kimi-inspect: TranscriptWs now fires onReconnected when the
  subscribe ack for its client_hello arrives instead of at socket open —
  the server attaches the transcript stream only after processing
  client_hello, so a refresh fired at open could finish before the
  subscription was active and still miss the ops in between

Addresses review feedback on #1888.

* fix(kimi-inspect): coalesce concurrent transcript refreshes instead of dropping

A subscribe ack landing while the initial REST load was still in flight
hit the `if (refreshing) return` guard, so ops emitted between the REST
page snapshot and the WS subscribe were neither in the page nor
delivered over the socket. Replace the drop guard with a coalesced
runner: at most one refresh in flight, and triggers during a run are
collapsed into exactly one follow-up run after it settles.

* fix(kap-server): force the transcript baseline after cursor-based replay

A cursor re-subscribe at unchanged grades deferred its baseline and then
compared against the previous grades on flush, so no reset was sent —
while volatile ops fanned out during the deferral had been dropped,
leaving the client with a permanent gap. flushTranscriptSeed now always
seeds a full baseline (previous grades no longer tracked in the deferred
record), and a regression test covers the same-grade cursor resubscribe.

Also drop the inline comments added to shellCommandService.ts — the
agent-core-v2 convention keeps commentary in the top-of-file block; the
context moved there.

* fix(kap-server): harden transcript seeding against stale and wildcard subs

Two subscribeTranscript gaps found in review:

- Re-read the target's subscription after the history awaits: subscribe
  work runs asynchronously, so an overlapping downgrade/unsubscribe used
  to be answered with resets computed from the stale spec. The reset
  loop now uses the latest grades/filter from state.targets and bails
  when the target is gone or no longer graded.
- Backfill roster agents admitted via the wildcard grade, not just
  explicitly named ones: a historical subagent seeded into the roster
  from session metadata had no materialized AgentTranscript, so
  wildcard subscribers silently never received its baseline reset.

Adds regression tests for the wildcard backfill, the mid-seed
downgrade, and the mid-seed unsubscribe (all three fail without the
fix); makeCore now accepts persisted agent metadata for roster seeding.

* fix(transcript): let meta.merge clear mode badges on mode exit

`agent.status.updated` with `planMode: false` / `swarmMode: false` was
dropped by the transcript projector because `meta.merge` could only set
mode badges, never clear them — clients kept rendering an exited mode
until the next full reset. The merge wire shape now accepts `null` per
mode key (set = object, clear = null, absent = keep): the reducer
deletes the key and normalizes an empty `modes` away, the zod schema
validates the nullable form, and the projector emits the clearing op
for exit events.

* fix(agent-core-v2): keep system-turn steering text out of turn prompts

`turn.started.prompt` was populated from the turn input for every
origin, so system-triggered turns (goal continuation, subagent run,
cron) exposed their internal steering text to live transcript
consumers; the cold rebuild mirrored the same leak when grouping
persisted history. The loop now populates the prompt only for
displayable user origins (user input, or a user-slash skill/plugin
activation) via the new isDisplayablePromptOrigin gate, and the cold
grouping opens hidden-origin turns promptless. Turns still open
normally — only the prompt text is withheld.

* fix(kap-server): reattach the transcript fan-out after a session reload

When the engine session closed or archived, TranscriptService dropped
the live store together with its ops listener set, but the
broadcaster's SessionState kept its transcriptStream — so
ensureTranscriptStream returned early for a later subscribe on the
resumed session, delivering a fresh reset but never the live
transcript.ops. The stream is now pinned to its TranscriptStore
instance and the fan-out re-registers whenever a rebuilt store shows
up. Adds a regression test that drops the service entry mid-stream and
asserts ops keep flowing after resubscribe (fails without the fix).

* fix(transcript): map legacy background_task origins in cold rebuilds

Legacy/v1 sessions persist background-task notifications with
origin.kind === 'background_task' (the live mapper already handles that
spelling), but the cold grouping only mapped 'task' — after a restart
those turns fell through to { kind: 'other' } and lost their taskId, so
the transcript could no longer associate the notification turn with its
background task. Both spellings now share the task-origin branch.

* fix(kap-server): project no-taskId shell failures into the transcript

A foreground `!` command that failed before onForegroundTaskStart ran
(Bash validation/spawn/registerTask errors) published shell.output /
shell.completed with taskId undefined, and the projector's guard
dropped them — the live transcript lost the stderr and the terminal
state of a command that did run. Shell events now resolve their task as
the id learned at shell.started, else the event's own taskId, else a
synthetic per-command id (shell-<commandId>), so early failures land
like any other shell task.

* refactor: drop the /api/v2 RPC surface and the klient http transport

- kap-server: remove the /api/v2 REST routes and /api/v2/ws socket (registerRpcRoutes renamed to serviceDispatcherRoutes; transport/ws/{eventMap,registerWs,wsClient,wsConnection,wsProtocol} deleted). /api/v1/debug/* is now the only RPC surface — a reflection dispatcher over the entire scoped DI registry with no whitelist — and /api/v1/ws the only WS endpoint
- klient: drop the http transport (transports/http/*, transports/ws/wsSocket.ts) and the kap-server devDependency; transports reduce to the ipc|memory subpath entries, and the dual/v2 e2e suites go with them
- kimi-inspect: target /api/v1/debug only with no fallback, replace the Service-event push channel (wsChannel/wsSocket) with on-demand fetch plus 15 s polling, and show a blocking "Debug surface unavailable" screen on connection failure
- transcript: add global attachment/interaction/todo entities (model, ops, wire schema) and project them from engine events in kap-server's coreEventMap

* fix(kimi-inspect): drop the unused TranscriptTodo import
2026-07-20 15:33:05 +08:00
qer
f441193e1d
chore: remove kimi-desktop app and desktop release pipeline (#1849)
* chore: remove kimi-desktop app and desktop release pipeline

The desktop client has moved to a dedicated internal repository. Drop the
Electron shell app, the desktop-build reusable workflow and its release
job, and all workspace references (dev script, typecheck filter,
onlyBuiltDependencies, flake.nix entries, lockfile). The kimi-web desktop
detection code is intentionally kept intact.

* chore: fix flake.nix after kimi-desktop removal

Complete the workspaceNames cleanup missed in the previous commit and
refresh the pnpmDeps fixed-output hash for the regenerated lockfile.
2026-07-17 20:32:47 +08:00
Haozhe
9b496946dc
feat(kimi-inspect): add kap-server web inspector with dev-only /api/v1/debug RPC surface (#1806)
* feat(kimi-inspect): add web inspector for kap-server /api/v2 surface

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

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

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

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

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

The private dev app never ships, so it should never appear in a changeset.
Add it to the changeset config ignore list (next to vis*) and note the rule
in the gen-changesets skill.
2026-07-17 15:56:53 +08:00
qer
d1ca65e1de
feat(vscode): migrate extension to Node SDK (#1769)
* feat(vscode): migrate extension to Node SDK

* fix(vscode): address CI failures

* fix(vis): handle token count records

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

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

* docs(vscode): record Node SDK migration design

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

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

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

* fix(vscode): align webview thinking effort handling with the TUI
2026-07-16 17:27:21 +08:00
Haozhe
4cffd732c2
feat(klient): contract-driven facade with http/ipc/memory transports (#1768)
* feat(klient): contract-driven facade with http/ipc/memory transports

- add zod-validated contract sections (global/session/agent) under
  src/contract and a facade exposing global.*, session(id).*, agent(id).*
- select transport once at creation via subpath entries
  (@moonshot-ai/klient/http|ipc|memory); drop legacy channel/client/
  httpChannel/wsChannel/wsKlient/proxy implementations
- absorb packages/server-e2e into packages/klient test/e2e suites
  (dual-backend, legacy v1, v2 wire) and remove the server-e2e workspace
- expose model registry and catalog services on kap-server v2 RPC surface

* fix(klient): derive session status from agentActivityView

The engine retired its sessionActivity service in #1751 (session busy is
now derived from agent activity views), but the facade still called the
deleted wire channel and imported the deleted engine module, breaking
typecheck and every klient suite at import time.

- drop the sessionActivity contract/registry entries and mirror the
  agentActivityView service instead (agent scope)
- compose session status() client-side from the pending interaction lists
  and each agent's agentActivityView, keeping the retired service's
  precedence and typing SessionStatus locally in the facade
- replace the deleted-channel call in the v2 smoke suite with a
  sessionInteractionService probe
- fix the legacy image-file suite to wait with the harness's
  waitForSessionBusy
2026-07-16 16:43:09 +08:00
7Sageer
70211a727f
feat: attach trace_id to telemetry events (#1724)
* feat: attach KFC trace_id to telemetry events

Capture the x-trace-id response header in both kimi providers (kosong
and agent-core-v2 llmProtocol) via the openai SDK withResponse(), and
thread it through the request result chain into telemetry so events can
be joined with server-side request logs by trace id instead of
session_id + time window.

- kosong / llmProtocol: StreamedMessage/GenerateResult.traceId,
  GenerateOptions.onTraceId early capture (available mid-stream),
  APIStatusError.traceId on the error path
- v1 (agent-core): per-turn traceIdByTurn on TurnFlow; attach trace_id
  to turn_ended/turn_interrupted/api_error/cancel/tool_call(+dedup,
  repeat)/permission_approval_result/question_*/compaction_*
- v2 (agent-core-v2): AgentTelemetryContext carries turn_id/trace_id
  updated by llmRequester; registry gains trace_id on 13 events plus
  turn_id on turn_* and turn_id/step_no on api_error

* fix: attribute trace_id to the failed request on error paths

Review follow-ups for trace_id attribution on failure paths:

- v1: chatWithRetry feeds a failed attempt's APIStatusError trace id into
  the turn's traceIdByTurn before the loop dispatches turn.interrupted,
  so turn_ended/turn_interrupted on error turns attribute to the failed
  request instead of the previous successful step.
- v2: llmRequester keeps the onTraceId-captured trace in per-request
  state and trackApiError prefers it over error extraction, so failures
  after response headers arrived (empty response, mid-stream decode
  errors) no longer clear the ambient trace_id.
- v2: compaction_failed falls back to the ambient trace_id once a
  summarizer request actually hit the wire, covering mid-stream failures
  whose error carries no trace.
- kosong: parseTraceId filters empty x-trace-id headers and both kimi
  providers read the header through it.
- v2 test harness forwards onTraceId to GenerateFn so tests can simulate
  the early header capture.

* fix: attribute v1 api_error to the in-flight request on post-headers failures

- v1: a failure after response headers arrived (mid-stream decode error,
  empty response) carries no trace on the error itself, so api_error lost
  the trace the client had already captured via onTraceId. Add a per-step
  in-flight capture (written by onTraceId, cleared at step begin/end) and
  fall back to it when the error carries no trace, aligning with v2's
  per-request requestTraceId. Failures before any response headers
  (network errors, local aborts) still report no trace rather than
  leaking a previous request's.
- v2: document that ambient trace_id distribution assumes serialized LLM
  requests per agent, naming the two supported paths that break it
  (after-step compaction below the block ratio; inject-path turns during
  a manual compaction).

* fix: isolate telemetry trace attribution

* fix: isolate telemetry trace by request

* fix: declare @moonshot-ai/protocol dependency of agent-core-v2

The trace-by-request refactor imports types from @moonshot-ai/protocol
in toolContract and toolExecutorService, but the package was not
declared, failing clean CI installs (TS2307). Also drop three type
imports that are unused after the merge with main.
2026-07-16 09:14:12 +08:00
_Kerman
4f99114342
refactor(kap-server): drop the @moonshot-ai/protocol dependency (#1755) 2026-07-15 22:40:05 +08:00
_Kerman
27236bd75f
refactor(agent-core-v2): drop the @moonshot-ai/protocol dependency (#1745) 2026-07-15 16:03:19 +08:00
Haozhe
1c85f94472
feat(agent-core-v2): gate image formats and add media recovery resends (#1626)
* feat(kap-server): drop WS heartbeat; never terminate idle connections

- remove ping interval / pong timeout / socket.terminate() from both the
  v1 (WsConnectionV1) and v2 (WsConnection) WebSocket connections
- v2 protocol: drop pong from the client message schema, remove PingMessage
  and ReadyMessage.heartbeatMs; the ready frame is now bare { type: 'ready' }
- v1 protocol: remove buildPing/PingFrame and ServerHelloPayload.heartbeat_ms;
  server_hello no longer advertises a heartbeat
- drop pingIntervalMs/pongTimeoutMs options from registerWs/registerWsV1

* feat(agent-core-v2): gate image formats and add media-stripped resend

- add image-format-policy as the single source of truth for the
  provider-accepted image MIME set (PNG/JPEG/GIF/WebP), with MIME
  normalization, data-URL parsing, byte sniffing, and refusal notices
- enforce the format gate at every ingestion point: ReadMediaFile refuses
  unsupported formats with per-OS conversion guidance, MCP tool results and
  prompt step requests drop them for a text notice, and kap-server prompt
  routes gate inline, uploaded, and remote-URL images on the sniffed bytes
- resend once with every media part replaced by a text marker when the
  provider rejects an image's format, and keep later steps of the same turn
  on the media-stripped projection (v1 parity)
- commit the WebP decoder wasm as a base64 module for the bundled CLI, with
  a regenerate script

* feat(agent-core-v2): add media-degraded 413 resend and WebP re-encoding

- resend once with the media-degraded projection after an HTTP 413
  body-size rejection: all but the two most recent media parts are replaced
  by text markers, and later steps of the turn stay degraded (stripped
  still wins over degraded)
- generalize stripAllMediaParts into degradeOlderMediaParts; the
  media-stripped resend is now the keepRecent=0 case
- re-encode non-animated WebP through the wasm decoder and the PNG/JPEG
  ladder instead of passing oversized WebP through (animated WebP still
  passes through whole)
- stop lossless PNG rescaling at a 1000px floor and switch to the JPEG
  ladder below it, so small byte budgets stay readable
- add the @jsquash/webp dependency

* feat(agent-core-v2): add flag-gated fault injection for recovery testing

- add the faultInjection domain: a one-shot arm/take latch that raises a
  deterministic provider failure (HTTP 413 body-size or image-format 400)
  before the provider is contacted, so the media-degraded / media-stripped
  recovery resends can be exercised end-to-end against a real provider
- gate arming behind the new fault-injection experimental flag
  (KIMI_CODE_EXPERIMENTAL_FAULT_INJECTION), off by default
- expose IAgentPromptService and IFaultInjectionService over the kap-server
  /api/v2 RPC channel
- add a klient example that drives the REST ingestion gate and both
  recovery resends against a live server
2026-07-13 22:11:46 +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
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
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
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: 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
liruifengv
170ae44205
style(web): polish the session sidebar (#1519)
* feat(web): use sidebar fold/unfold icons for sidebar toggle

* feat(web): move settings entry to a sidebar footer row

* feat(web): fully collapse sidebar with animated width transition

* feat(web): redesign sidebar colors, spacing and macos desktop chrome

* feat(desktop): center traffic lights on the 48px header row

* fix(web): restore webkit thin scrollbars and unify sidebar icon sizes

* feat(web): add Kbd keycap component and justify sidebar search shortcut

* style(web): rework sidebar palette and pin a resident sidebar toggle

* fix(desktop): sync window appearance with web UI theme so dimmed traffic lights stay visible

* feat(web): adopt Kimi design icons in the sidebar via a local icon collection

* style(web): mute workspace group title color in the sidebar

* style(web): refine sidebar typography, unify shortcut keycaps, float workspace row actions

* style(web): cap sidebar draggable width at 480px

* style(web): derive sidebar row height from type and padding, float the kebab

* chore: add changeset for sidebar UI polish

* fix(nix): update pnpmDeps hash

* style(web): put the sidebar collapse button inside the header on non-mac

* fix(nix): update pnpmDeps hash
2026-07-09 19:39:20 +08:00
Kai
1bf2c9afee
feat: keep image-heavy sessions within provider request-size limits (#1508)
* feat(kosong): classify HTTP 413 request-body-too-large as a dedicated error type

* feat(agent-core): lower default image downscale cap to 2000px and make it configurable

* feat(agent-core): strip media to text markers and retry when the compaction request is too large

* feat(agent-core): cap model-initiated image reads with a configurable byte budget

* feat(agent-core): resend with degraded media when the provider rejects the request body as too large

* test(agent-core): add explicit timeouts to encode-heavy image budget tests

* feat: add WebP decoding support with wasm integration

- Introduced a new WebP decoding module using @jsquash/webp's wasm decoder.
- Implemented functions to decode WebP images and check for animated WebP formats.
- Updated image compression tests to include scenarios for WebP handling, including encoding and decoding.
- Enhanced error handling for API request size limits to accommodate various error messages.
- Updated pnpm lockfile to include new dependencies for WebP encoding and decoding.

* chore(changeset): consolidate this PR's entries into one

* fix(nix): update pnpmDeps hash for merged lockfile

* feat(agent-core): refuse HEIC/HEIF reads with platform-matched conversion guidance
2026-07-09 18:05:14 +08:00
Luyu Cheng
ad30a1c632
style(web): polish web UI typography and controls (#1502)
* style(web): polish sidebar and tool typography

- Use UI font and medium weight for sidebar and composer controls

- Add reusable shortcut and tool output blocks

- Cap long tool output at 50 lines with a scrollbar

- Update sidebar show-more copy and muted styling

* style(web): refine workspace picker sizing

* style(web): align composer mode menus

* style(web): tune list and question typography

* style(web): reuse shortcut keys in approvals

* style(web): size workspace picker from content

* feat(web): localize chat status labels

* style(web): refine composer toolbar controls

* style(web): use complete Inter variable font

* style(web): tune sidebar workspace typography

* style(web): polish composer and workspace picker

* style(web): refine markdown and thinking typography

* chore: add web UI polish changeset

* fix(web): pin Inter package for Nix build

* style(web): polish goal tool calls

* style: polish goal mode display

* fix: layer latest message pill below menus

* style: align goal strip content
2026-07-09 14:55:58 +08:00
liruifengv
ee385456d0
refactor(web): migrate icons to unplugin-icons (#1467)
* refactor(web): migrate icons to unplugin-icons

Replace the hand-written gen-icon-data.mjs + @iconify/utils runtime
rendering with unplugin-icons build-time imports. The public API
(<Icon name>, iconSvg, IconName, SIZE_PX, NAME_TO_REMIX) is unchanged;
127+ call sites are untouched.

- add unplugin-icons@^23.0.0 (devDep) + Vite Icons() plugin (compiler: vue3)
- rewrite src/lib/icons.ts: static ~icons/ri/* imports (component + ?raw)
  for 56 distinct Remix icons across 59 IconName entries
- Icon.vue renders <component :is> with unknown-name fallback
- append ICON_GROUPS export for DesignSystemView catalog
- DesignSystemView: v-for catalog, remove legacy-script references
- delete gen-icon-data.mjs, gen-icon-catalog.mjs, icon-data.ts,
  gen:icons script
- remove @iconify/vue and @iconify/utils from dependencies; move
  @iconify-json/ri to devDependencies
- drop Icon.vue from check-style ICON_EXEMPT (no hand-written <svg>)

* refactor(web): drop unused NAME_TO_REMIX icon mapping

NAME_TO_REMIX was a Record<IconName, string> table introduced to map
internal icon names to their ri: ids. After the unplugin-icons migration
it has no production consumers — only icons.test.ts imported it (for two
drift tests) and DesignSystemView mentioned it in descriptive copy. The
ICONS table already conveys the same ri: id via each entry's paired
component + ?raw imports (e.g. RiFolderOpenLine / RawFolderOpenLine).

- remove NAME_TO_REMIX const from src/lib/icons.ts (-63 lines)
- remove NAME_TO_REMIX import + describe block from icons.test.ts
- update DesignSystemView §02 copy: describe the import-pair idiom and
  stop claiming ICON_GROUPS is sourced from NAME_TO_REMIX

* chore: add changeset for web icon migration

* chore(nix): bump pnpmDeps hash for unplugin-icons

Adding unplugin-icons changed pnpm-lock.yaml, so the fixed-output
pnpmDeps derivation hash is stale. Update to the hash reported by the
Nix Build CI run.
2026-07-07 16:44:56 +08:00
liruifengv
353c0885e5
chore: remove unused kimi-migration-legacy package (#1415)
This package only ever contained a package.json with no sources, dependencies, or scripts, and nothing in the repo imports it (the CLI uses @moonshot-ai/migration-legacy instead). Remove the directory and drop its entries from flake.nix and the changeset config, then refresh the lockfile.
2026-07-06 15:09:27 +08:00
qer
6a469b3e07
refactor(web): replace hand-written icons with Remix Icon (#1293)
* refactor(web): replace hand-written icons with Remix Icon

Generate a tree-shaken Remix Icon subset at build time via @iconify/utils + @iconify-json/ri, keeping the <Icon>/iconSvg() API.

Add a chat-new icon for the new-chat buttons and reveal the workspace 'new chat in group' button on hover. Unify the message copy and undo buttons (matching hover style and tooltip, drop the undo hover label, align sizes). Switch the mobile switcher kebab to the horizontal dots icon and tweak sidebar search colors. Regenerate the design-system icon catalog.

* fix(web): address PR review feedback

Restore accessible names (aria-label) on the message copy and undo buttons. Keep the workspace add button reachable for keyboard users by revealing it on header focus-within. Update the nix pnpmDeps hash for the newly added icon dependencies.

* fix(web): address follow-up review feedback

Keep the workspace add/more buttons focusable without hover by revealing them via opacity instead of display:none, so keyboard and non-hover users can reach the control.

Drop explicit .ts extensions in icon imports to satisfy oxlint, and read the design-system icon catalog directly from the generated icon data.
2026-07-02 14:20:17 +08:00