The memory runtime's catalog fork rewrite spread the caller patch over
the source metadata wholesale. The runtime session host forks with a
{ title: undefined, custom: undefined } patch when the caller names no
metadata, so the spread wiped the source's custom metadata before the
goal-stripping logic ran; a partial custom patch likewise replaced the
source's custom object instead of merging into it. Skip undefined-valued
patch keys and merge custom through the same goal-dropping rule the
local runtime applies, keeping the two runtimes' fork semantics
identical.
Rebasing onto main pulled in the global message search feature, which
predates the multi-runtime rework and conflicts with it in three places:
- start.ts wired TranscriptService straight into the search service's
LiveTranscriptSource, but the transcript service is now keyed by full
SessionRef while the search query container carries a bare session id.
Wire a composition-root adapter instead: resolve the bare id against
the runtime host's live scopes (unique match only, mirroring the v1
resolver's projection semantics) and delegate by ref.
- The search index scans on-disk wire.jsonl files and enumerates sessions
through ISessionIndex, tripping the v1 compatibility guard's layout and
index bans. Exempt search/searchService.ts with a documented reason on
both rules: the feature is local-layout-only by design, multi-runtime
search indexing is future work. Stale exemptions now fail the guard.
- Extend the frozen /api/v1 wire baseline with the /api/v1/search route
the branch predates.
Implement the M8b slice, completing the first-stage rework:
- Add ISessionHostFiles, a typed lease capability that carries the
session host-file facts (plan/log/media/agent dirs); local leases
project it, headless leases get the absent value. All business
services (plan, sessionLog, task display, MCP originals, agent
lifecycle, session metadata, cron) read paths from it instead of
ISessionContext, with wire-visible values byte-identical.
- Delete ISessionContext.sessionDir/metaScope, the transitional
pathful context seed, the session.host_files/os.stdio capabilities,
and the unused bootstrap scope builders; cwd/workspaceId stay as
logical metadata fed from the lease.
- Stream the export pipeline end to end (stat-only walks, lazy entry
contents, streamed staging) and derive revisions from the same
content hashes so transfer window validation is unchanged.
- Fix fs-watch duplicate subscriptions when a bare id reroutes to a
different runtime; finalize domain guards with documented allowlist
owners and pathlessness pins; node-sdk reads host files from the
lease for live sessions.
Implement the M8a slice of the cleanup milestone: the old activation
machinery is gone; every session is now activated through the runtime
host.
- Rewrite ISessionLifecycleService as a thin delegation facade:
create resolves/touches the workspace registration and reuses the
registered runtime before host.create; resume/fork prefer the live
lookup and fall back to index + discovery catch-up; close/archive
delegate to the host. The old materialize/fork directory copy, wire
rewrite, session_index append, and cron duplication code is deleted.
- Drop the legacy-owner delegation branches from the runtime session
host and the now-dead own map from the tracked-session bridge;
tracked sessions are the single live source.
- Keep the hooks slot on the lifecycle facade (the long-lived app
service) so SessionStart/SessionEnd streams are unchanged, proven by
the untouched legacy-vs-runtime parity test.
- Keep CreateSessionOptions.workDir as a frozen klient/node-sdk
compatibility input (no longer an everything-deriving overload).
- Extend domain import bans to the runtimeSessionHost domain and sweep
stale own-map-era comments.
Implement the M7 milestone: a transfer coordinator that moves sessions
between runtimes over the logical export/import stream, with no public
surface.
- SessionTransferService: buffers the source export, re-validates the
source revision across the export window (aborting with
session.transfer_source_changed), commits on the target, and only
deletes the source after validating the target when move is
requested; an in-memory journal records progress and failures.
- ISessionManager gains an optional revision() covering every exported
byte; both runtimes implement it with flush-before-read semantics.
- Add a logical cron entry kind: local export collects session-tagged
cron tasks read-only, local import writes them back re-tagged with a
fresh id, memory retains them as namespace blobs for round-trips;
local import rolls back written cron files on mid-commit failure.
- SessionImportInput.forkFrom lets the target runtime apply its own
fork identity rewrite; memory same-runtime and cross-runtime fork
metadata semantics are unified to match the local fork.
- ISessionService.fork now routes cross-runtime forks through the
transfer service.
- Keep cron entries out of the v1 export ZIP via a whitelist in the
export adapter (frozen ZIP contract), with a golden test.
Implement the M6 milestone: all live maps, journals, subscriptions,
activity and cache keys inside kap-server are now keyed by the full
SessionRef, while the /api/v1/ws wire stays byte-identical.
- Resolve bare session ids once at attach time through the v1 resolver,
pin the ref on the subscription, and address every broadcaster,
transcript, fs-watch, and replay operation by ref; ambiguous or
unavailable ids get a compatible ack error and no subscription.
- Re-key the transcript stores, op journals, event journals (now
per-runtime directories), snapshot caches, and the lifecycle tracked
map by sessionRefKey; bare-id lookups answer only on a unique match.
- Route internal session events by an internal runtimeId payload field
(never projected onto the wire) with resolver fallback; keep the
active-state queue append synchronous to preserve frame ordering.
- Add multi-runtime isolation tests (same sessionId on two runtimes
never crosses transcript/prompt/journal streams) and a repeatable
black-box comparison harness that diffs a base-commit server against
this build over REST, WS, and export scenarios.
- Widen node-sdk PlanInfo.path to string|null to match the engine type
and fix the stale resolver-fallback comment in the broadcaster.
Implement the M5c slice: every remaining live/interaction v1 route now
resolves the bare session id through IV1SessionRefResolver and obtains
the live session from IRuntimeSessionHostService (live lookup first,
then open/resume through the owning runtime), while the wire stays
byte-identical.
- Add v1LiveSession helper mapping resolution failures to the frozen
error envelopes; cold :restore now genuinely resumes via the runtime.
- Bridge host-activated sessions into the legacy lifecycle read view
via trackActivated so all existing read-only consumers (broadcaster,
transcript, snapshot, tasks) keep working; host.archive/close
delegate to the legacy owner when the session is legacy-owned.
- Share the app's OS services with local runtimes through the provider
so terminals/fs behave exactly as before (and test overrides apply).
- Rewire transcript store collection to the host's close/archive
events; create adapter now uses host.create.
- Shrink the bare-id exemption list to the WS broadcaster (M6).
Implement the M5b milestone: the runtime activation path now produces
sessions behaviorally identical to the legacy lifecycle activation.
- Local runtime projects the transitional session.host_files capability
and contributes a per-lease ISessionContext replacement seed (real
workspaceId/sessionDir/metaScope/cwd/scope), so plan files, session
logs, media originals, cron buckets, and agent homedir metadata all
land in the existing locations with zero business-code changes;
headless runtimes keep the pathless behavior.
- Add app/runtimeSessionHost: an open/resume/create/fork/archive/close
entry that routes through ISessionService, assembles scopes via the
runtime activation service, and preserves hooks, telemetry, plan-mode
auto-entry, archival events, and failure rollback, keyed by full
SessionRef.
- Fix media-originals and plan-file paths degenerating to relative
paths when sessionDir is empty; relax klient planDataSchema.path to
nullable to match the engine type.
- Add black-box parity tests driving the same operation sequence
through legacy and runtime activation, comparing hook streams,
telemetry, state.json, and the on-disk tree file by file.
Implement the M5a slice of the REST delegation milestone:
- Add IV1SessionRefResolver as the single bare-session-id resolution
entry point for the v1 edge: no match maps to the existing not-found
envelope, exactly one match yields the SessionRef, ambiguous and
runtime-offline cases map to stable existing error envelopes without
leaking candidates or internal causes.
- Delegate the cold/descriptor surfaces through the owning runtime:
session list/get/profile/children, snapshot, transcript cold reads,
task existence checks, and the export ZIP stream (logical export
entries materialized into the same ZIP pipeline), with byte-identical
wire responses, pagination, filters, and ZIP contents.
- Add ensureDiscovered on the workspace runtime manager so sessions in
on-disk buckets stay reachable after restarts, plus local-layout
fallbacks (session-meta/, custom.cwd recovery) matching
FileSessionIndex semantics.
- Guard the resolver as the only bare-id resolution path in kap-server
with an explicit shrinking exemption list for the remaining live
routes.
Implement the M4 milestone: Session Core can now be assembled purely
from an injected session runtime lease, with capability gating for OS
services and tools.
- Add app/runtimeSession: an activation path that seeds a session scope
from the lease (context, capabilities, typed stores, blob-backed
storage, OS handles) and closes via drain + dispose + lease close.
- Add session/sessionCapabilities: build-time serviceFilter on DI scope
collections plus an activation-time policy service; the legacy path
stays allow-all and byte-identical.
- Define ISessionOsCapabilities (filesystem/process/terminal/watch/
environment) and real contribution bindings with requires; annotate
all OS-dependent services and tools, gated behind runtime
capabilities; local runtime projects full node-local handles.
- Parameterize agentLifecycle/toolActivation to drop bootstrap path
lookups; memory runtime fork now re-anchors state.json like the local
fork.
Implement the M3 milestone:
- Add app/workspaceRegistration: a long-lived workspace runtime manager
(idempotent ensureRegistered with inflight coalescing, unregister that
blocks new leases then closes the runtime without deleting data) and
the internal IWorkspaceSessionService facade that delegates every
operation to the already-registered runtime's sessions manager.
- Add a remote workspace runtime test harness fake with offline
semantics (leases fail on disconnect, resume returns
session.runtime_unavailable, no local fallback) and capability gating.
- Route POST /api/v1/sessions through a v1 compatibility adapter that
resolves/touches the workspace registration, reuses the registered
LocalWorkspaceRuntime, persists via runtime.sessions.create, and
activates through the existing lifecycle service so hooks, telemetry,
rollback, and the wire response stay byte-identical.
Implement the M2 milestone: one long-lived runtime per opened workspace,
hosting any number of sessions under the existing sessions/<wd_id> bucket.
- Add the workspace-domain contracts (IWorkspaceRuntime, IWorkspaceProvider,
IWorkspaceRuntimeRegistration, WorkspaceDescriptor) in app/workspace.
- Add app/localWorkspaceRuntime: a full ISessionManager whose persistence
namespaces map onto the existing on-disk layout (state.json, per-agent
wire.jsonl, logs, tasks, plans, blobs, session_index.jsonl) with zero
new on-disk formats, no locator/marker files, and no dual writes.
- Exclusive in-process child leases with flush-then-close semantics,
same-runtime fork reusing the legacy directory copy/wire rewrite,
staged export/import with checksums, and cold reads with revisions
derived from existing files.
- Extend check-domain-layers with a target-side allowlist so the local
adapter cannot be imported outside itself.
Implement the M1 milestone: a long-lived headless host runtime whose
sessions manager hosts many isolated sessions in memory.
- In-memory typed stores (atomic document / append log / blob) mirroring
the node-fs semantics: sticky failures, live tail on rewrite,
ref-counted acquire, detached byte copies.
- StandaloneMemoryHostRuntime with a full ISessionManager: repeated and
concurrent create, exclusive child leases with lease_conflict,
flush-then-close idempotent leases, same-runtime fork, staged
export/import with schema/checksum validation, cold reads, and
artifact owner validation.
- SessionService routes through the registry to the owning runtime.
- Register the standaloneMemoryRuntime domain in check-domain-layers
with the same import bans as the contract domain.
Introduce the M0 milestone of the multi-session host runtime rework:
- Add SessionRef/sessionRefKey identity and the ISessionHostRuntime /
ISessionManager / ISessionRuntimeContext contracts under
app/sessionHostRuntime, with an in-memory registry skeleton and the
internal coded causes from the error model.
- Extend check-domain-layers with import bans so the new domain cannot
depend on workspace domains, node:* specifiers, or persistence/os
backends.
- Freeze the /api/v1 wire baseline: sharded snapshots of every REST
route path-item, component schema, WS channel/operation/frame, and
the protocol version, immune to pretty-format output truncation.
* feat(tui): customizable footer status line via status_line config
The bottom status bar was a fixed layout. Add a [status_line] section
to tui.toml covering the two established models:
- items: codex-style composition. Pick and order the built-in slots
(mode, goal, model, tasks, cwd, git, tips); unset keeps today's
layout, unknown ids are skipped with a warning, and an empty list
blanks line 1.
- command: claude-code-style custom line. The footer runs the command
with a JSON snapshot on stdin (model, cwd, git branch, permission
and plan mode, context usage, session id, version) and renders the
first stdout line. Runs are throttled to one per second and capped
at 300ms; nonzero exit, empty output, or a timeout falls back to the
built-in layout.
Line 2 (context readout) stays built-in in every mode. Resolve#2116.
* feat(tui): apply status_line on /reload-tui and document it
The reload command pushes reloaded tui.toml fields into AppState; the
new statusLine field joins that list so edits go live without a
restart. The config files reference (EN/ZH) documents the new section.
* fix(tui): round-trip active status_line on save and harden the runner
Codex review on #2255 caught a real one: saveTuiConfig rewrites the
whole tui.toml, so changing any other preference dropped an active
[status_line] section. Render it live when set (items and command),
commented-out guide when unset.
Also: spawn the command through ComSpec/cmd.exe on Windows instead of
assuming sh.exe, and take the whole process tree down on timeout
(process-group kill on POSIX, taskkill /T on Windows) so a script that
spawned children cannot leak them.
* fix(tui): address status_line review: runner lifecycle, capture cap, tips slot
- recreate the command runner when a reload swaps status_line.command;
the old runner kept executing the previous script until restart
- schedule a trailing refresh instead of dropping updates that arrive
inside the throttle window, so the last state change always lands
- stop accumulating stdout once the first line is complete (and cap a
missing-newline stream at 64KB); only the first line is ever rendered
- honor the configured position of the tips slot in items instead of
always pinning tips to the far right
- route unknown status_line.items warnings through the TUI status area
on reload instead of raw stderr, which could corrupt the display
Changeset text tightened per maintainer note.
---------
Co-authored-by: Kai <me@kaiyi.cool>
* 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
* 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.
* fix(agent-core): count validation-rejected tool calls toward the repeat breaker
Args-rejected calls returned before prepareToolExecution, so the breaker
never counted them and the model could re-issue the same invalid call
until maxSteps. Register them in finalizeToolResult so reminders fire at
3/5/8 and the turn force-stops at 12.
* fix(agent-core): key parse-failed repeats on raw argument text
Malformed JSON arguments normalize to {} on parse failure, which keyed
every malformed-but-different attempt identically and could force-stop a
turn whose calls were evolving rather than identical. Register skipped
calls on the raw arguments text when parsing failed.
---------
Co-authored-by: fengchenchen <fengchenchen@moonshot.ai>
- add writeStream/putStream to the storage and blob store interfaces so
large values are written incrementally (tmp + fsync + rename) instead of
being buffered in memory
- FileService.save now streams the request body straight to the blob store
and only counts bytes for FileMeta.size
- drop the multipart fileSize limit and the file.too_large error from the
/files upload path (41301 stays in the wire protocol for session export)
* feat(oauth): return structured managed usage rows
Stop formatting plan-usage labels and reset hints into English strings
at the oauth layer. The parser still absorbs backend field drift, but
now emits a stable structured row (name / window{duration,unit} / used
/ limit / resetAt) that kap-server passes through and clients localize
themselves:
- window: normalized from duration/timeUnit (minute/hour/day/week),
whole-hour minute windows fold to hours, unnamed summaries are the
weekly limit
- resetAt: absolute ISO timestamp; relative reset_in/ttl seconds are
converted at parse time
- TUI /usage panel formats labels and reset hints locally
* refactor(oauth): parse managed usage strictly to the current payload shape
Drop the defensive drift tolerance (alternate reset-time spellings,
reset_in/ttl seconds, remaining-derived used, title/scope names,
top-level duration/timeUnit, fuzzy time-unit matching) and parse only
what the platform actually sends: numeric strings, resetTime, nested
detail/window records, TIME_UNIT_* enums.
* fix(node-sdk): update managed usage smoke example and facade tests for structured rows
* fix(kosong): fail fast on quota-exhausted 429 instead of retrying
A 429 caused by an exhausted account quota or insufficient balance
(Moonshot error.type "exceeded_current_quota_error", OpenAI
"insufficient_quota") can never succeed on retry, yet it was classified
as APIProviderRateLimitError and silently retried for the whole budget
(10 attempts, ~3 minutes of backoff) with no UI feedback — the session
appeared frozen on every request.
Introduce APIProviderQuotaExhaustedError, minted in
normalizeAPIStatusError from the structured body error.type/error.code
forwarded by convertOpenAIError, with billing-anchored message patterns
as a fallback for gateways that flatten the body to text. The new class
is excluded from isRetryableGenerateError (fail fast, even when a
retry-after header is present) and from isProviderRateLimitError (no
swarm requeue/suspend). toKimiErrorPayload and translateProviderError
map it to provider.api_error (retryable: false) instead of
provider.rate_limit, and classifyApiError reports it as
quota_exhausted in telemetry. agent-core-v2 mirrors the same fix.
Transient rate-limit 429s keep the existing retry, backoff, and
Retry-After behavior (verified end-to-end against a mock provider:
quota body fails after attempt 1/10; rate-limit body still walks the
full 10-attempt ladder).
Behavior changes to note: quota-failed swarm subagents now fail
instead of suspending indefinitely as "Rate limited...", and quota
errors cross the wire as provider.api_error rather than
provider.rate_limit.
* fix(kosong): classify quota exhaustion in OpenAI Responses stream errors
Responses response.failed / error SSE events carry no HTTP status and
were minted by errorFromOpenAIResponsesEvent as either a rate-limit
error (rate_limit_exceeded / embedded status_code=429) or a base
ChatProviderError — and the base class falls into the retryable
unclassified-failure fallback, so an insufficient_quota event still
burned the whole retry budget on the openai_responses path. Route the
event code and message through the same quota-exhausted check before
the rate-limit branch, in kosong and the agent-core-v2 mirror. Covers
all three entry paths (error events, response.failed, nested gateway
frames) since they share the single converter.
* style(agent-core-v2): drop inline comments per AGENTS.md header-only rule
agent-core-v2 comments live solely in the top-of-file block, never
beside functions or statements; the kosong twins keep the full
rationale.
* refactor(kosong,agent-core-v2): move quota-429 checks to vendor hook
Per review on #1857: the knowledge of how a backend signals quota
exhaustion is vendor-specific and must not run for every
OpenAI-compatible provider from the shared conversion layer.
- Add a convertError hook: ProtocolTrait.convertError in agent-core-v2
(single-value, last-declarer-wins, bound by composeOpenAIChatHooks /
composeAnthropicHooks / traitConvertError) and an equivalent optional
hook parameter on convertOpenAIError / convertAnthropicError. Bases
consult it with the raw failure (SDK error on HTTP paths, raw event on
the Responses in-stream path) after the abort guard, before their own
rules.
- Declare Moonshot's quota signals (exceeded_current_quota_error,
billing wordings) on the Kimi side: kimiOpenAITrait and
kimiAnthropicTrait in v2, the KimiChatProvider and KimiFiles catch
sites in kosong, all through the new classifyKimiQuotaError.
- Drop the options parameter from normalizeAPIStatusError and the
shared quota code/pattern tables: the contract layer keeps only the
vendor-neutral APIProviderQuotaExhaustedError type and its retry /
rate-limit / wire-mapping semantics.
- The OpenAI bases keep recognizing only OpenAI's own documented
insufficient_quota code (HTTP and Responses stream events) as
protocol knowledge of that wire.
Behavior: kimi and openai provider types classify exactly as before;
an unregistered vendor speaking Moonshot billing wordings through a
plain openai transport now stays a retryable rate limit by design.
* fix(kosong,agent-core,agent-core-v2): wire kimi quota hook fully
Follow-up to the second review round on #1857, all four findings:
- Kimi-over-Anthropic (legacy engine): AnthropicOptions gains the same
optional convertError hook as the OpenAI bases, threaded through
AnthropicStreamedMessage and every catch site, and the provider
manager's anthropic route now passes classifyKimiQuotaError for
provider type kimi — a quota-exhausted 429 over this transport
previously still burned the retry budget. classifyKimiQuotaError now
also walks error -> .error -> .error.error for the code/type, since
the Anthropic SDK keeps the full body on .error instead of hoisting.
- v2 telemetry: ApiErrorKind gains 'quota_exhausted' and
classifyApiError checks APIProviderQuotaExhaustedError before the
generic 429 branch, matching the legacy engine's reporting.
- Hook contract: converted ChatProviderErrors now pass through before
the vendor hook is consulted in convertOpenAIError /
convertAnthropicError (both engines), so the hook sees each raw
failure exactly once even when a stream-minted error crosses an
outer catch; tests assert the single consult.
- protocolTrait: the convertError member doc shrinks to the concise
style and the consult contract moves into the file header's
composition rules.
* test(kosong,agent-core,agent-core-v2): lock quota hook assembly paths
Third review round on #1857:
- Fix the v2 anthropic base header and AnthropicHooks doc still claiming
withThinking is the only hook.
- Drop the two remaining non-header JSDoc blocks in protocolTrait.ts per
the AGENTS.md header-only rule; the consult contract already lives in
the file header.
- Update the ProtocolTrait contract test to the seventeen-hook shape
(convertError included) and cover the traitConvertError binding.
- Add real-assembly regression probes: the v2 registry composes a
(kimi, anthropic) provider whose mocked SDK client throws a Moonshot
quota 429 and generate rejects with the non-retryable
APIProviderQuotaExhaustedError (a plain anthropic composition keeps
the same 429 retryable); the legacy ProviderManager routing test
asserts convertError is classifyKimiQuotaError on the kimi-anthropic
route and absent for plain anthropic; the legacy provider threads
options.convertError to its generate catch.
* test(kosong,agent-core-v2): cover KimiFiles quota 429 and drop stale docs
Fourth review round on #1857:
- Drop the AnthropicHooks member JSDoc (its content already lives in the
anthropic.ts and anthropicHooks.ts file headers) and fix the anthropic
contrib header still calling the hook set single-hook.
- Add the missing KimiFiles regression in both engines: a mocked files
client rejecting with a Moonshot quota 429 makes uploadVideo reject
with the non-retryable APIProviderQuotaExhaustedError, locking the
classifyKimiQuotaError argument at the upload catch sites.
* 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.
* 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
* feat(kap-server): add global fs:mkdir endpoint
Add POST /api/v1/fs:mkdir to create a directory on the host filesystem
by absolute path, backing the folder picker's "new folder" action.
Implemented directly on node:fs/promises.mkdir in the transport layer
for now, non-recursive by design, with wire errors mapped to the
existing fs.* codes (40001/40409/40411/40919).
* test(kap-server): update api surface snapshot for fs:mkdir
* test(kap-server): stop export tests from holding server.close() open
The export download tests reused pooled undici keep-alive connections,
so afterEach's server.close() could wait out fastify's 72s default
keepAliveTimeout and die on the 10s hook timeout (flaky on CI). Send
connection: close on the streamed export requests, matching the
fs:content tests.
* 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
* feat(cli): add plugin quota and update notices
- Show "Note: This plugin consumes your quota." after installing
quota-consuming official plugins (currently Kimi Datasource).
- Show a one-time update notice after invoking an outdated plugin (a
plugin MCP tool call or a /<plugin>:<command> turn); the last
notified version is persisted so each new marketplace version
reminds once.
- Skip the third-party trust prompt for loopback sources that mirror
the official plugin CDN path, so the dev plugin marketplace no
longer prompts when installing official plugins.
* feat(cli): report plugin update notices at turn end
Buffer plugin MCP tool usage during the turn and report it together
with plugin command usage when the turn's output has fully ended,
instead of firing the check mid-turn at tool result time. Cancelled
turns no longer trigger the notice.
* fix(cli): refresh plugin MCP map on miss and serialize notice writes
Address review findings on the plugin update notifier:
- The memoized MCP server-to-plugin map is reused across /reload,
/new, and session switches, so plugins installed or enabled later in
the same app run never resolved. Refresh the map once on a lookup
miss, and never pin an empty map when there is no session.
- Concurrent notices (a turn that used two outdated plugins) raced on
the read-modify-write cycle of the notice state file and could drop
each other's entries. Serialize checks through a promise queue so
each notified version is persisted exactly once.
* fix(cli): gate plugin notices on official provenance and survive tool-name truncation
Address review findings:
- The quota note and the update notice keyed on the plugin id alone,
so a local/GitHub fork reusing a billed plugin's manifest id was
treated as the official build. Both now require official provenance
(a zip install from the official CDN plugin path or its loopback dev
mirror) via a shared isOfficialPluginInstall check.
- Resolving plugin MCP tools by splitting on the '__' separator broke
for qualified names core truncates to 64 chars, which can cut the
separator. Match known server names by longest prefix with a name
boundary instead, which survives truncation as long as the server
part itself is intact.
* revert(cli): drop the dev marketplace trust relaxation
The loopback carve-out let any local service bypass the third-party
trust prompt by serving a zip under the official path shape, which
does not prove official provenance (review P1). Revert to the single
rule — only https://code.kimi.com/kimi-code/plugins/official/* is a
trusted official source — and restore the stock dev marketplace
server. Installing official plugins from the dev marketplace shows
the trust prompt again.
* fix(cli): restrict update notices to the official catalog and settle tests
- Skip the update check when the loaded marketplace is not the default
official catalog, so a custom KIMI_CODE_PLUGIN_MARKETPLACE_URL can
no longer produce a notice that claims to come from the Official
Marketplace.
- Return a never-rejecting promise from the notifier entry points so
tests await the serialized queue directly instead of relying on
zero-delay timers for ordering.
* feat(agent-core-v2): add hostIdentity domain for host-overridable system prompt identity
- add app-scope hostIdentity domain (L3) with productName / replyStyleGuide
overrides and hostIdentitySeed for composition roots
- render ${product_name} and ${reply_style_guide} in the base system prompt,
falling back to CLI defaults when the host provides no override
- seed the variables from AgentProfileService via IHostIdentity
- expose hostIdentity on kap-server's ServerStartOptions
* chore: add changeset for host identity system prompt
* fix(agent-core-v2): adapt hostIdentity registration to ScopeActivation
The DI refactor on main removed _base/di/extensions (InstantiationType);
register with ScopeActivation.OnScopeCreated instead, and regenerate the
state manifest for the new SystemPromptContext fields.
---------
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
The secondary-model feature is still experimental (gated by
KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL and ignored by the interactive
TUI), so the config API support merged in #2228 should not produce a
user-facing changelog entry yet.
* feat(kap-server): accept secondary_model in the config API
POST /api/v1/config now accepts secondary_model, persisted to the
[secondary_model] config section via the generic per-domain dispatch.
GET /config also hides the synthesized __secondary__ derived entry
from the models view, matching the GET /models listing.
* Update config-api-secondary-model.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* feat(kap-server): bundle the desktop app log into session exports on request
* refactor(agent-core-v2): align the desktop log export with repo conventions
* refactor(agent-core-v2): rebuild undo as wire-level journal rewind
Replace the compensating context.undo op with a wire-layer rewind
primitive: a log.cut control record with a persisted target, applied
uniformly by the wire during fold. Turn boundaries become first-class
(TurnIndexModel indexing turn.prompt record positions), models declare
a temporal classification (rewindable), and a single
IAgentRewindService owns the undo pipeline (quiesce -> precheck ->
cut -> reconcile) with all entry points converged.
- wire: log.cut record, rewindable model flag, re-fold rebuild;
OpApplyContext.recordIndex for position-aware reducers
- rewind service: aborts the active turn, cancels in-flight
compaction, preserves the pending queue, rebases measured tokens,
reconciles lastPrompt, tracks conversation_undo
- todo list, plan mode, task-notification delivery and the turn index
now rewind together with the undone turns
- transcript reducer applies cut ranges so snapshot/messages surfaces
stay consistent with the model context
- REST/RPC/debug undo entry points converge on the rewind service;
TUI parses the v2 undo-unavailable error shape
- legacy context.undo records keep replaying for old journals
* refactor(agent-core-v2): keep undo domain-owned
* refactor: enhance /undo functionality for consistency and safety, including todo list rollback and improved event handling
* chore: clean up undo changeset artifacts
* refactor: rebuild rewind consistency
* fix: make conversation undo durable and consistent
* fix(agent-core-v2): stabilize undo restoration
* fix: keep TUI undo on legacy error contract
* refactor(agent-core-v2): drop unused full compaction cancel API
Undo now rejects with session.busy while compaction runs instead of
cancelling it, so the awaitable cancel() added for the earlier rewind
semantics has no callers left. Remove it from the interface and
implementation; the RPC cancel path keeps using the task abort
controller directly.
* fix(agent-core-v2): remove injected context on undo
* chore(agent-core-v2): regenerate wire manifest
* docs(agent-core-dev): rename rewind to undo in layer table
* fix(agent-core-v2): undo prompt-owned image reminders
* refactor: remove transcript undo reconciliation
* fix(kap-server): map undo busy errors
* refactor(agent-core-v2): rename undo participant registry and attribute checkpoint depth
- Rename IAgentConversationUndoReconciliationRegistry to
IAgentConversationUndoParticipantRegistry (conversationUndoParticipants).
- Return the limiting model from checkpointDepth and include it in the
SESSION_UNDO_UNAVAILABLE details; report checkpoint_lost instead of
compaction_boundary when no compaction explains the missing depth.
- Add a registry invariant test: every model reacting to context.* ops
must be registered via defineCheckpointedModel or explicitly exempt.
* Delete .changeset/fix-undo-injections.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
Only subscription events now enter the timed coalescing buffer.
server_hello, acks, resync_required, and global session events join the
outbound FIFO and flush it right away, so they no longer wait behind the
flush window and cannot overtake earlier buffered subscription frames.
The broadcaster marks global fan-out with the new BroadcastDelivery
'immediate' lane.
* fix(agent-core): continue goal pursuit when a goal turn hits the per-turn step limit
* chore(agent-core-v2): regenerate state manifest
* fix(agent-core): start goal pursuit when the goal-creating turn hits the step limit
* refactor(agent-core-v2): drop step-cap narration from goal module and test
* refactor(agent-core-v2): register agent tools as DI services with profile-aware activation
- replace module-level registerTool + Eager AgentBuiltinToolsRegistrar with registerAgentTool double registration (Agent-scope DI service + contribution table)
- add toolActivation domain: AgentToolActivationService filters contributions by the bound Profile's tool policy using declared names, resolves instances lazily via accessor.get, and re-activates on agent.status.updated
- rename BuiltinTool to AgentTool service interface; tools become Agent-scope services with decorator-injected dependencies (e.g. AgentTool -> SubagentTool/ISubagentTool)
- AgentLifecycleService.create runs one activation pass after restore and profile binding so tools reflect the Profile before the first turn
- update tool registrations, scripts, and tests; add toolActivationService tests
* refactor(agent-core-v2): centralize builtin tools under agent/tools
- move builtin tools from scattered domain folders (plan/tools,
goal/tools, os/backends/node-local/tools, task/tools, etc.) into a
unified agent/tools/ directory
- split each tool into a kebab-case definition file (bash.ts) and a
registration file (bashTool.ts) pairing with its prompt markdown
- update imports across src, tests, kap-server, and TUI comments
* fix(agent-core-v2): keep agent tools out of scope-creation instantiation
Main's instantiateAll constructs every registered service at scope
creation, but agent tool constructors may legitimately throw when their
host capability is absent (e.g. WebSearchTool without a configured
provider), and profile-aware activation must stay the only resolution
path so the runtime registry holds real instances, never proxies.
- add SyncDescriptor.instantiateWithScope (default true) and let
registerScopedService opt registrations out of the instantiateAll
sweep
- registerAgentTool passes the opt-out, restoring lazy
activation-driven construction on top of the eager-scope semantics
- regenerate docs/state-manifest.d.ts
* refactor(agent-core-v2): replace delayed DI with scope activation
- add explicit OnScopeCreated and OnDemand activation modes
- remove delayed proxy and idle initialization support
- migrate service registrations, tests, and DI guidance
* refactor(agent-core-v2): instantiate all registered services eagerly at scope creation
- add instantiateAll to scope creation: every service registered for a
scope tier is constructed when the scope is created, following the
static dependency graph; a failing constructor fails scope creation
- drop the hand-maintained eager-resolution lists: igniteEagerServices
in AgentLifecycleService, the force-instantiated session services in
SessionLifecycleService, and the kosong config bridge get in bootstrap
- update DI docs and agent-core-dev skill guidance for the new semantics
- adjust affected tests (registry hygiene, timer draining, listener
ordering) and add scope-tree coverage for eager instantiation
* feat(agent-core-v2): add per-scope keyed state container (state domain)
- add _base StateRegistry: typed StateKey/defineState descriptors with
register/get/set, per-key onDidChange and global onDidChangeAny events,
and BugIndicatingError on duplicate or unregistered key access
- bind thin per-scope services at each tier: IStateService (App),
ISessionStateService (Session), IAgentStateService (Agent), so scoped
plain-data state lives in one observable container that dies with the scope
- register the state domain in the layer check script and export the new
services from the package index
- add StateRegistry unit tests and scoped resolution tests
* refactor(agent-core-v2): move session-scope service state into ISessionStateService
- add StateRegistry.snapshot() with JSON-safe serialization for Map/Set/Date/circular values
- migrate plain-data fields of 13 session-scope services (cron, interaction, sessionActivity, agentProfileCatalog, fs, fsWatch, log, metadata, skillCatalog, toolPolicy, workspaceCommand, workspaceContext) to defineState keys registered in sessionState
- register SessionStateService in affected tests and add test/state/stubs.ts helper
* feat(kimi-inspect): rework chat layout with session pane and tabbed right dock
- add SessionPane column next to the sidebar: Services tab (pending
interactions + session Service panels) and State tab polling
ISessionStateService.snapshot() every second, rendered as a live
diff tree
- merge the transcript audit panel and the agent inspector into one
RightPanel with Audit/Agent tabs that keep panel state across switches
- extract InteractionsCard from Inspector into its own component
- collapse multiline strings in StateTree into a compact hover-preview
button with a viewport-clamped fixed popup
- test: cover StateRegistry.snapshot() conversions (Map/Set/circular);
pass a session state service to SessionInteractionService in kap-server
test fakes
- update the AGENTS.md project map for the new layout
* refactor(agent-core-v2): move agent-scope service mutable state into agentState
Register each Agent-scope service's mutable fields into the agent-state
container (IAgentStateService) via defineState keys, and access them
through get/set accessors backed by states.get/set. Promise locks,
disposables, and other mechanism-only fields stay plain instance fields.
- define per-service state keys (e.g. activityView.*, goal.*, toolDedupe.*)
and register them in each service constructor
- replace direct field reads/writes with states-backed accessors across
the touched services
- update the affected unit tests for the new IAgentStateService dependency
* fix(agent-core-v2): collapse class instances in state snapshots
- StateRegistry.snapshot() now stops at custom-prototype boundaries and
emits '(ClassName)' markers, so resource graphs reachable from registered
values (e.g. tool instances holding service references) can no longer
exhaust the heap during export; plain data keeps recursing
- add agent-scope state test covering the full assembled agent scope
- kimi-inspect: extract the session State card into a shared StateCard and
add an agent State tab in RightPanel polling IAgentStateService.snapshot()
- document the per-scope state container pattern in the agent-core-dev skill
* refactor(agent-core-v2): keep resource-holding state out of the state container
- move fields holding live resources (tool entries, managed tasks, turn
jobs, prompt records, MCP registrations, profile callbacks) back to
plain private fields so the agent state service only holds
snapshot-safe plain data
- add gen:state-manifest script that statically collects defineState
keys and their register call sites into docs/state-manifest.d.ts
- add state manifest freshness test and update agent-state docs
* chore: add changeset for session-scope state container
* docs(agent-core-v2): move eager-instantiation notes into the scope.ts header
* fix(kimi-inspect): suppress stale state tree while switching state owner
* fix(tui): steer user messages into the running turn while a goal is active
* fix(tui): reset request state when steering mid-goal input fails
* fix(agent-core): update session prompt metadata on steer
* feat(agent-core): defer registered user tools
Allow hosts to mark registered user tools as deferred so select_tools can discover and load their schemas on demand in both agent-core implementations.
* fix(agent-core): hide unregistered deferred tools
Filter loaded deferred user-tool schemas from provider history and loaded-state checks after unregister while preserving canonical history and disconnected MCP behavior.
* fix(agent-core-v2): drop stale inline schemas
Stop treating previously loaded user-tool schemas as active after the same tool is re-registered for inline disclosure.
Capability resolution now falls back from trait hooks (last declarer
wins) to the protocol base catalog to UNKNOWN_CAPABILITY; the
definition layer is removed from the chain.
- remove ProviderDefinition.capability and the definition branch in
ProtocolAdapterRegistry.explainCapability
- kimi contrib drops its UNKNOWN_CAPABILITY declarations, so base-known
model ids resolve through the base catalog instead of being
suppressed by a vendor-level UNKNOWN
- update composition tests for the new resolution order
* fix(agent-core-v2): honor explicit undefined field deletes in modelsToToml
The models TOML transform merged each entry as { ...oldRaw, ...converted },
so a field the new record carried with an explicit undefined — deleted from
'converted' by setDefined — was put right back from the old on-disk raw.
Field-level clears issued through config.replace (e.g. the provider write
routes dropping base_url, display_name, capabilities) only took effect in
memory and resurrected on the next boot. Merge via setDefined onto the old
raw directly, matching providerEntryToToml.
* feat(kap-server): add provider write endpoints and models.dev/registry import
Add the provider management write surface plus server-proxied catalogs so
API clients (the desktop app first) can manage providers without editing
config.toml by hand:
- POST/PUT/DELETE /providers: manual create, replace-style edit (new_id
rename with pointer migration, tri-state api_key, form-unknown fields
merged through), and delete with real alias removal.
- GET /providers/{id} additionally reveals the stored api_key for edit
prefill on the loopback+bearer transport; the list stays redacted.
- GET /catalog/providers[{id}]: pruned models.dev directory with import
eligibility resolved server-side (10-min cache, stale fallback, built-in
snapshot, shared in-flight fetch).
- POST /providers:import_catalog and :import_registry (collection actions
next to :refresh): catalog/registry imports with TUI-aligned
remove-then-apply refresh semantics, OAuth-managed guards, and the
registry source blob that scheduled refreshes rediscover.
Write-path consistency: field-level clears assign explicit undefined (the
TOML transforms only drop keys that way), import refreshes swap aliases in
two passes so stale on-disk fields cannot ride along, re-imports keep the
stored credential when api_key is omitted, and multi-step writes serialize
through a process-local chain. Global default_provider/default_model are
never modified by provider writes.
* fix(kap-server): cache the built-in catalog fallback and guard alias-key collisions on replace
- Cache the built-in snapshot on upstream failure, so an offline install no
longer pays the full upstream timeout on every catalog call.
- Reject a PUT whose rebuilt alias keys collide with an alias owned by
another provider (foreign-prefix keys are global), checked before any
write so a collision never lands half the edit.
* fix(kap-server): make toCatalogProviderItem's switch explicit for older TS control-flow
* chore(agent-core-v2): drop inline rationale per package comment conventions
* feat(kap-server): seed default_model on provider create/import when none is configured
A fresh setup has no global default model, so the first provider added
through the write endpoints left GET /auth permanently unready and new
sessions without a selected model. Seed default_model from the created
provider's default (or first) model on POST /providers, :import_catalog
and :import_registry when — and only when — no default is configured;
an existing pointer is never moved, not even a dangling one.
* refactor(agent-core-v2): own the models.dev provider import in the engine
kap-server's provider write endpoints imported @moonshot-ai/kosong and
@moonshot-ai/kimi-code-oauth directly for the models.dev browse/import
and custom-registry import. Move that capability behind a new App-scope
IModelsDevImportService so the server only talks to agent-core-v2:
- app/kosongConfig/modelsDev.ts mirrors the third-party api.json schema
and hosts the pure translation (wire inference, endpoint resolution,
model pruning, thinking/gateway overrides) ported from kosong's
catalog.ts; kosong's own type surface stays limited to built-in
vocabulary
- modelsDevUpstream.ts owns the directory fetch, 10-minute cache, and
built-in snapshot fallback; modelsDevImportService.ts owns the import
orchestration (tri-state api_key, two-pass alias swap, OAuth-managed
rejection, serialized writes) with coded modelsDev.* errors
- kap-server routes shrink to wire mapping (zod schemas + numeric error
code mapping) and drop the kosong/oauth dependencies
The /api/v1 surface is byte-identical: the apiSurface snapshot and all
existing modelCatalog tests pass unchanged.
* chore: drop the branch's changesets
* fix(agent-core-v2): route the models.dev always-thinking exemption through kosong
The zero-tolerance vendor-name gate forbids a 'kimi' compare outside the
kosong layer, and the engine-side models.dev port carried one inline.
Kosong (the vendor layer, owner of thinking semantics) now answers the
verdict — wireHasProtocolThinkingDisable — and kosongConfig/modelsDev.ts
asks it instead of comparing wire ids.
---------
Co-authored-by: haozhe.yang <yanghaozhe@moonshot.ai>
* chore: prune non-user-facing changesets
Remove entries covering only agent-core-v2 internals, kap-server
protocol/endpoint changes, and experimental-engine behavior; the
underlying changes still ship with the next user-facing release.
Downgrade the MCP timeout and web service env var additions from
minor to patch, as both extend existing configuration surfaces.
* chore: document user-facing criteria in gen-changesets skill
Add Core Rule 6 to skip changesets for changes users cannot
perceive, note pre-release pruning in the workflow, and classify
configuration additions to existing features as patch.
* feat: support a configurable secondary model for subagents
* refactor: move the subagent model config to a consumer-neutral [secondary_model]
The secondary model becomes a model-domain concept next to default_model so
future consumers beyond subagents can share it: [secondary_model] model /
effort in config.toml, KIMI_SECONDARY_MODEL / KIMI_SECONDARY_EFFORT env
overrides, and the Agent / AgentSwarm per-spawn choice renamed from
"subagent" to "secondary".
* docs: replace "v2 engine only" notes with the concrete effective surfaces
State that [secondary_model], its env overrides, the Agent/AgentSwarm
model parameter, and SYSTEM.md take effect only under kimi web and
experimental kimi -p (the TUI ignores them), and that --agent /
--agent-file are available only under experimental kimi -p. Also drop
the SYSTEM.md claim of parity with --agent/--agent-file, which was
inaccurate: SYSTEM.md is an agent-core-v2 app-domain feature and also
applies under kimi web, while the flags are gated at the CLI.
* fix: review follow-ups for the subagent secondary model
- Drop the "cheaper" claim from the Agent/AgentSwarm model parameter
descriptions and the advertised model list — the secondary model is
not necessarily the cheaper one.
- Downgrade the changeset to patch, note the kimi web / experimental
kimi -p effective surface, and tighten the wording.
- Remove the onWillRestore stub fields from two lifecycle stubs; they
belong to upcoming lifecycle work, not to this change.
* fix(agent-core-v2): prevent ghost agents from invalid model bindings
* fix: narrow the secondary-model error hint to missing-alias failures
The model catalog's not-configured throw now carries details.model, and
wrapSubagentModelError only decorates errors whose details.model matches
the bound model. Malformed [models.*] entries and unrelated config.invalid
failures during agent creation pass through untouched instead of being
misattributed to an invalid secondary-model alias.
* fix: mark subagent resume semantics as breaking
* chore(agent-core-v2): follow header-only comment convention
* fix(agent-core-v2): await agent restore preparation
* feat(agent-core-v2): support agent model preferences
* Update subagent-secondary-model.md
Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
* fix: validate secondary models before agent creation
* Delete .changeset/secondary-model-startup-warning.md
Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
* Add secondary_model config section for subagents
Individual agents can override this via the new `model_preference` field in their agent file.
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* feat(agent-core-v2): support override patches in [secondary_model]
The recipe is now `model` plus the flattened ModelOverride field set.
With any patch field set, a config overlay synthesizes a derived
registry entry (base copy, patch merged into overrides, aliases
dropped) so subagent spawning rides the standard effectiveModelConfig
merge; with none, subagents bind the pointed entry directly.
`default_effort` replaces `effort` (KIMI_SECONDARY_EFFORT rebinds)
and doubles as the explicit subagent thinking; unset, thinking
resolves naturally instead of inheriting the caller. The overlay
strips the derived entry (and any defaultModel pointer to it) from
writes, and the kap-server GET /models route hides it from pickers.
* fix(agent-core-v2): fire section events for overlay-rewritten domains
rebuildEffective only committed the caller-named domains, so a
ConfigEffectiveOverlay or section env binding that rewrote a sibling
domain (setting [secondary_model] synthesizes a derived models entry;
removing the recipe retracts it) left consumers of the models section
stale. Widen the commit candidates with every domain the recompute
actually changed; commit() deepEqual-guards each candidate, so the
widening costs nothing.
* feat(agent-core-v2): gate secondary model behind experimental flag
* Update subagent-secondary-model.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
---------
Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
Signed-off-by: 7Sageer <sag77r@hotmail.com>
OpenAI-compatible endpoints disagree on the wire field for reasoning
content: `reasoning_content` (DeepSeek/Moonshot convention, older vLLM)
vs `reasoning` (OpenAI GPT-OSS guidance, current vLLM — which also only
accepts `reasoning` on the request side, vllm-project/vllm#38488).
Reading only `reasoning_content` silently dropped thinking, and the lost
thinking then never made it back into later requests.
Add per-endpoint dialect detection: inbound responses are scanned in
priority order (reasoning_content, reasoning_details, reasoning; first
string value wins), the carrying key is remembered, and outbound
messages echo thinking under the same key (default reasoning_content).
An explicit `reasoningKey` / `reasoning_key` config still pins the
dialect.
- kosong: shared reasoning-key module; wire the kimi and openai-legacy
providers; the dialect cell is shared across per-step provider clones.
- agent-core-v2: same mechanism in the vendored openai-legacy base; the
kimi trait no longer pins reasoning_content (the default already is),
keeping Moonshot wire behavior byte-identical while adapting to vLLM.
- agent-core: memoize the base provider behind ConfigState.provider so
the detected dialect survives across turns instead of being rebuilt
per access.
* feat(session): add core work aggregate and consume it at WS/REST edges
- agent-core-v2: add the sessionActivity domain (ISessionActivityView,
Session scope) folding each agent's activity view and the interaction
kernel into busy / main_turn_active / pending_interaction /
last_turn_reason, firing cause-tagged change events only on real
tuple changes
- kap-server: resolveSessionFacts reads the core view; the WS
broadcaster drops its per-agent fold/dedup and delegates to the
view, deferring turn_ended emissions until after the turn.ended
frame so busy:false never precedes it; global events now fan out
to every established connection via addGlobalTarget without any
subscription
- kimi-inspect: add src/activity (GlobalEventsWs + SessionActivityHub
+ useSessionActivities) consuming the global push; the Sidebar
renders running / approval / question / failed badges per session
* feat(kap-server): decouple transcript stream from agent_filter
- transcript frames (transcript.reset/ops) are governed by the per-agent
transcript grades alone and bypass the legacy agent allowlist; the filter
still gates session_event delivery
- client_hello is handshake-only in code: hello and subscribe share one
attach path, and hello's inline subscription fields are deprecated
(subscriptions made optional) in both protocol packages
- flush deferred work_changed(busy:false) from a microtask so it always
lands after the matching turn.ended frame
* fix(kimi-inspect): restore session activity hub in the browser
- bind the default fetch in SessionActivityHub: a member call hits the
browser's Illegal invocation (receiver is not the global object), and
the swallowed error left the REST seed never firing, so the Sidebar
badges never populated on refresh
- create the hub in useEffect + useState instead of useMemo: under
StrictMode the first mount's cleanup closes the memo-created hub for
the rest of the page's life
* chore: add changeset for the global session work status push
* feat(kap-server): add transcript plan endpoint for ExitPlanMode calls
- add GET /sessions/{id}/transcript/plan projecting one ExitPlanMode
call's plan info (content, path, options, review outcome) from the
first available fact: the linked approval interaction's request
display, the live tool frame's display, or the tool result output
- add TOOL_CALL_NOT_FOUND (40416) error code
- add transcriptPlanResponseSchema to the transcript contract
- cover live, auto-mode, cold-rebuild, and error paths in tests
- update the API surface snapshot and AGENTS.md
* feat: move transcript subscriptions to subscribe_v2 and extend the plan endpoint
- add subscribe_v2 / unsubscribe_v2 WS control frames as the only
carrier of per-agent transcript grades and the transcript_since
cursor; client_hello / subscribe no longer accept transcript fields
- serialize control-frame handling per connection so interleaved
attaches cannot overwrite fresher subscription state
- make tool_call_id optional on GET /sessions/{id}/transcript/plan:
omitted lists every ExitPlanMode call with recoverable plan content
as a plans array
- add a Plan lookup card to the kimi-inspect agent inspector via
fetchTranscriptPlan
- add TOOL_CALL_NOT_FOUND (40416) to the shared protocol error codes
- update AGENTS.md and add changesets