* 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>
* 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
* 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>
* feat(agent-core-v2): add generated config section manifest
- add scripts/gen-config-manifest.mts: drains the live
registerConfigSection / registerConfigOverlay contributions and renders
docs/config-manifest.toml in the on-disk config.toml shape (owner,
scope, registered defaults, env bindings, schema fields)
- add a gen:config-manifest package script (--check mode included) and a
freshness test that rebuilds the manifest and compares byte-for-byte
- point the agent-core-dev config skill and the package AGENTS.md at the
generated manifest instead of the stale hand-maintained ownership map
* feat(agent-core-v2): add generated wire-protocol manifest
- add scripts/gen-wire-manifest.mts to generate docs/wire-manifest.d.ts
from defineOp registrations (payload interfaces, persist policy,
toEvent, cross-reducers) plus a WirePayloadMap
- extract shared JSON Schema helpers from gen-config-manifest.mts into
scripts/lib/jsonSchema.mts
- add gen:wire-manifest script and wireManifest.test.ts freshness check
- document the manifest in packages/agent-core-v2/AGENTS.md
* fix(agent-core-v2): keep array-of-tables manifest sections fully commented
A bare `[hooks]` header parses as a plain table, which array sections
reject on load; emit only the commented `[[hooks]]` shape so the
manifest matches the on-disk config.toml shape it documents.
Addresses a Codex review comment on PR #2086.
* refactor(agent-core-v2): decouple kosong from config persistence
- keep the kosong provider/model registries in memory and sync them with
config.toml through a new app/kosongConfig two-way bridge: hydrate on
startup, push config section changes into the registries, and persist
runtime mutations (discovery refresh, OAuth provisioning, default-model
pointer changes) back to disk
- declare the providers/models/thinking section constants, zod schemas,
env bindings, and TOML transforms in a single app/kosongConfig
configSection.ts; kosong keeps hand-written types only
- pin every schema to its kosong type at compile time via
AssertExact<Equal<...>> (_base/utils/typeEquality)
- register a transitional auth>kosongConfig exception in the domain-layer
checker for the OAuth provisioning flows
* chore(agent-core-v2): drop unused IConfigService import from authLegacy
* fix(agent-core-v2): reconcile registry with env-pinned default pointers
A registry-originated default-model/provider write lands only in the
config user layer when an effective overlay pins the section
(KIMI_MODEL_NAME pins defaultModel to the reserved env model): the
effective value does not move and no change event fires, so the registry
diverged from the effective config view — catalog/auth reads reported the
user pick while profile resolution kept the pinned model. After
persisting, the bridge now re-asserts the effective value into the
registry, restoring the pre-refactor behavior where every default-pointer
write was arbitrated by the effective view.
* fix(agent-core-v2): await persistence before resolving kosong registry mutations
ProviderService/ModelService mutations resolved as soon as the in-memory
registry updated, while the kosongConfig bridge persisted the change on an
unawaited private chain — callers (klient kosong.*, set_default route,
OAuth provision, discovery refresh) could observe success before the write
reached config.toml, and a restart right after could lose it.
- fire registry change events through AsyncEmitter and await delivery in
set/delete/replaceAll/setDefaultX, so a mutation resolves only after
listeners' waitUntil work completes; loadAll keeps synchronous timing
- the bridge hooks its persists into waitUntil, hoists the equality guards
into the listeners so config-originated echoes stay fully synchronous,
and serializes persists on the chain as before
- retry a failed persist with bounded backoff (3 attempts); failures are
logged, never rejected to callers, and the in-memory change stands
- default-pointer change events now carry an { id } payload (fireAsync
requires object events)
- add the klient kosong-config stress example covering read-after-write,
concurrent bursts, and restart durability
* refactor(agent-core-v2): extract toolApproval domain from permissionGate
- add agent-scoped `toolApproval` domain owning the approval round-trip:
builds approval requests, drives the session/approval broker, publishes
permission.approval.* events, records session approval rules, and
resolves ask continuations
- slim permissionPolicy down to the static risk-adjudication chain; drop
the dynamic `registerPolicy` mechanism
- move harness constraints out of the policy chain into their owning
domains as toolExecutor hooks ordered before 'permission': plan-mode
guard (plan), swarm batch exclusivity and AgentSwarm approve (swarm),
goal-start review (goal), exit-plan review (plan/exitPlanModeReview),
and btw deny (session/btw)
- delete the now-unused policies (deny-all, plan-mode-guard-deny,
plan-mode-tool-approve, goal-start-review-ask, swarm-mode-agent-swarm-
approve, agent-swarm-exclusive-deny)
- update Permission.md, AGENTS.md, and check-domain-layers for the new
domain layout
* feat(kimi-inspect): add App Services view for app-scope service reflection
- add `services` top-level view (`AppServicesView`) on the NavRail, showing
the full-width app-scope Service panel grid; session/agent scopes stay in
the Chat view's Inspector
- extract shared `ServicePanels` from Inspector and add `methodArgs` helper
to build per-method argument editors from channel metadata
- support variadic service calls in `panels.ts` (`call(svc, method, ...args)`)
- update agent-core-dev skill docs for the toolApproval extraction and the
guard/review-off-chain permission design
* refactor(agent-core-v2): restructure workspace domains
- rename workspaceRegistry to workspace and workspaceLocalConfig to
projectLocalConfig
- extract id-spelling resolution into the workspaceAliases domain
(IWorkspaceAliases)
- extract workspace-centric session queries into workspaceSessions
(IWorkspaceSessions)
- update kap-server routes, klient contracts, and related tests to the
new services
* feat(transcript): add op-batch sequencing and point-to-point catch-up
- wire: transcriptSeqSchema with per-agent batch seq watermark on
transcript.reset/ops and the REST transcript response, the
transcript_since subscription cursor, and the GET transcript/ops
catch-up shape; seq stays optional everywhere so legacy peers fall
back to loss-signal-driven refreshes
- wire: drop interactionFrame, interactions live on the interaction
item in the ops stream
- kap-server: TranscriptService assigns consecutive per-agent batch
seqs and retains them in a bounded in-memory journal; the
transcript_since cursor replays journaled batches instead of a
baseline reset, and the baseline reset is now items-empty because
history always pages in over REST
- kimi-inspect: transcript REST/WS clients track the op-batch
watermark and run seq-gap/reconnect catch-up with full-refresh
fallback; add the Transcript audit panel (AuditTrail timeline,
structural diff, state tree) docked right of the chat
- docs: sync AGENTS.md and agent-core-dev skill notes with the new
transcript contract
* feat(transcript): persist plan revisions and task/interaction facts, add user-messages endpoint
- record each ExitPlanMode submission as a versioned plan blob via a reference-only plan.revision op, projected live and cold as a plan.revision marker plus the plan badge (reviewPath, version)
- persist task.started/task.terminated (with a bounded output tail) and interaction.request/interaction.resolved ops, and add foldFacts so a cold transcript rebuilds tasks, interactions, todos and goal/plan/swarm meta from the wire journal
- add GET /sessions/{session_id}/transcript/user-messages returning all turn-opening prompts grouped per agent
* fix(agent-core-v2): anchor external PreToolUse hooks before the permission gate
The toolApproval extraction forces the gate to construct early (planService injects it to anchor plan-guard), so the 'permission' hook registered ahead of 'externalHooks' and a policy ask waited on the approval broker before PreToolUse could block, hanging the turn. Fetch the gate first in registerListeners and register the PreToolUse hook with before: 'permission', falling back to appending when the gate is stubbed without its hook.
* refactor(agent-core-v2): replace ordered onBeforeExecuteTool hook with veto-event pattern
- introduce BeforeToolExecuteEvent with veto/allow/pass/waitUntil statements
- add BeforeToolExecuteEmitter with two-pass fire (immediate then deferred)
- split readiness work into separate onWillExecuteTool participation event
- migrate all domain listeners: permissionGate, plan, goal, swarm, btw,
externalHooks, toolDedupe, mcp
- remove IAgentPermissionGate force-injection for hook ordering
- update docs, tests, and domain-layer check to match
* refactor(agent-core-v2): unify veto payload on ExecutableToolResult
- veto() and waitUntil factories now carry a plain ExecutableToolResult:
isError reads as a denial, anything else as a short-circuit;
the block/reason/syntheticResult weak union is gone
- add denyToolExecution(reason) helper for the common denial shape, and
narrow the fire/authorize return to BeforeExecuteDecision ({ veto } or
{ executionMetadata })
- narrow the policy 'result' resolution to { kind: 'result'; result }
- settle vetoed calls through a single normalize/merge path in the executor
* fix(agent-core-v2): pull up IAgentPermissionGate in agent activation
The permission gate only subscribes `onBeforeExecuteTool` from its
constructor. The veto-event refactor removed the ordering-driven
force-injections that used to pull it up, so without an explicit
resolution tool execution would run without policy adjudication.
* refactor(agent-core-v2): fold systemReminder domain into contextMemory appendTagged
- add `appendTagged(content, tag, origin)` to `IAgentContextMemoryService`,
storing content pure with a `tag` field on `ContextMessage`
- apply the XML tag at projection time in `contextProjector` via the new
`tag.ts` helpers (`wrapTag` / `applyTagToContent`)
- delete the `systemReminder` domain and migrate all call sites
(contextInjector, goal, plugin, prompt, swarm, btw, sessionInit,
toolSelectAnnouncements) to `appendTagged`
- build toolDedupe reminder strings with `wrapTag`
* refactor(klient): merge providers/models/catalog into global.kosong facade
Converge three separate facade namespaces (global.providers,
global.models, global.catalog) into a single global.kosong facade
that exposes two domain concepts: provider (CRUD) and model
(read-only view). Add streaming generate() method for direct
LLM calls through the facade.
- Define ProviderAuth (api-key | oauth), ProviderInput,
AnonymousProviderInput, GenerateInput, GenerateParams,
GenerateEvent as klient-owned public types
- Extend KlientChannel with stream() for AsyncIterable transport
- Add streaming IPC protocol (stream/stream_data/stream_end/
stream_error/stream_cancel frame types)
- Add StreamingProcedureContract, ScopedStreamCaller, and
per-chunk zod validation in the contract layer
- Implement generate via dispatcher special-case routing to
IModelCatalog.getRequester().request()
- Rename events: providers.changed -> kosong.providers.changed,
models.changed -> kosong.models.changed,
catalog.changed -> kosong.changed
- Remove GlobalProvidersFacade, GlobalModelsFacade,
GlobalCatalogFacade, and ModelRecord from public exports
- Update all tests, examples, and README
BREAKING CHANGE: global.providers, global.models, and
global.catalog replaced by global.kosong; event names changed;
ModelRecord no longer exported.
* feat(transcript,kimi-inspect): add tag field to text frames and improve session creation
transcript:
- add optional `tag` field to TextFrame, textFrameSchema, and HistoryMessage
- propagate tag through contextTranscript MutableMessage
kimi-inspect:
- render tagged frames with violet badge and distinct styling in ChatView
- skip cwd prompt for workspace-based session creation in Sidebar
- auto-bind default model on new sessions via resolveDefaultModel
* refactor(transcript): rename wire/ directory to contract/
The transcript package's REST/WS schemas and event types lived in
src/wire/, which collided with the engine's persisted wire.jsonl
record vocabulary. Rename it to src/contract/ so "wire" unambiguously
refers to wire.jsonl records (foldWireRecordFacts, HistoryWireRecord
stay unchanged).
- rename src/wire/{schema,events}.ts to src/contract/
- update index exports and test imports accordingly
- reword comments: "wire shape" -> "contract shape", "on the wire" ->
"in ops" / "in transit" / "on the WS channel" / "transcript API"
- events.ts: "transcript frame" -> "transcript event" for WS envelope
messages, avoiding confusion with TranscriptFrame
- kap-server tests: TranscriptWire/TurnWire/FrameWire/OpsCatchupWire/
UserMessagesWire -> *Contract
- update AGENTS.md references
* refactor(transcript): rename wire/ directory to contract/
The transcript package's REST/WS schemas and event types lived in
src/wire/, which collided with the engine's persisted wire.jsonl
record vocabulary. Rename it to src/contract/ so "wire" unambiguously
refers to wire.jsonl records (foldWireRecordFacts, HistoryWireRecord
stay unchanged).
- rename src/wire/{schema,events}.ts to src/contract/
- update index exports and test imports accordingly
- reword comments: "wire shape" -> "contract shape", "on the wire" ->
"in ops" / "in transit" / "on the WS channel" / "transcript API"
- kap-server tests: TranscriptWire/TurnWire/FrameWire/OpsCatchupWire/
UserMessagesWire -> *Contract
- update AGENTS.md references
* Revert "refactor(agent-core-v2): fold systemReminder domain into contextMemory appendTagged"
This reverts commit 55afaa3d96f729c4f73a71f1fbd23d3f6087453b.
Restore the systemReminder domain: reminders go back to being baked
into message text at write time, and ContextMessage loses the `tag`
field (projection-time wrapping is removed with it).
* feat(transcript): add wire-equivalent detail, dedupe session events
- transcript: add step usage/timing/retry, turn durationMs/error/usage,
tool inputText/progress, task resultSummary/error, meta.agent status,
a global prompts entity, and the 'hook' marker
- kap-server: project the new fields in coreEventMap and suppress
transcript-projected session events on connections subscribed to the
transcript protocol (live fan-out and cursor replay)
- kimi-inspect: mechanical type sync for the new snapshot prompts field
* fix(klient): resolve lint errors in ipc channel stream and e2e matrix test
* feat(config): add env overrides for loop control and background task limits
Add three operational environment overrides, resolved as env > config.toml
> default in both engines (agent-core and agent-core-v2), matching the
existing KIMI_IMAGE_MAX_EDGE_PX / KIMI_SUBAGENT_TIMEOUT_MS pattern:
- KIMI_LOOP_MAX_STEPS_PER_TURN overrides loop_control.max_steps_per_turn
- KIMI_LOOP_MAX_RETRIES_PER_STEP overrides loop_control.max_retries_per_step
- KIMI_CODE_BACKGROUND_MAX_RUNNING_TASKS overrides background.max_running_tasks
Invalid values are ignored and fall back to the config value. The v2 engine
resolves them through config section env bindings (effective-only, never
persisted); the v1 engine resolves them at the consumption point.
* fix(config): strip env-bound fields before persisting config writes
Environment overrides resolved into the effective config could be echoed
back through IConfigService.set/replace (e.g. GET then POST
/api/v1/config) and persisted into config.toml, outliving the env var.
This affected the pre-existing image / subagent / keep-alive bindings as
well as the new loop control and max-running-tasks bindings.
Extend ConfigStripEnv with a getEnv parameter and add a shared
stripEnvBoundFields helper: while a field's env var is set, writes
restore the field's raw on-disk value (or drop it) instead of persisting
an echoed env value; when unset, normal writes persist. Register it for
the loopControl, task/background, image, and subagent sections.
Also fix ConfigService.stripEnv looking up rawSnake by the camelCase
domain key; on-disk sections are keyed snake_case.
* fix(config): honor binding parsers when stripping env fields on persist
An invalid env value (e.g. KIMI_LOOP_MAX_STEPS_PER_TURN=abc) is ignored
on the read path but still marked the field env-owned on the write path,
so a config write for that field was silently dropped.
stripEnvBoundFields now derives the guard from the section's envBindings
and skips fields whose env value fails the binding's parse, so invalid
env values are ignored on both paths — and the duplicated field/env
descriptor list is gone.
Also drop function-level comments added beside helpers; agent-core-v2
keeps comments solely in the top-of-file block, so the strip semantics
now live in the config.ts / configService.ts headers.
* fix(config): re-apply env overlays from the env-free base on every read
Two follow-ups from review:
- ConfigService.get()/getAll() re-applied section env bindings on the
already-overlaid effective cache (and get() mutated it in place), so a
valid override degraded to invalid or unset kept serving the stale
value until the next reload — and an echoed stale value could then be
persisted by a config write. Reads now recompute from a cached
env-free validated base, so degraded or removed env values fall back
to the file immediately.
- stripEnvBoundFields restored env-owned fields from the raw snake
sub-object, missing values persisted under legacy keys (e.g.
max_steps_per_run). Section stripEnv now receives the env-free,
fromToml-normalized raw base, so legacy aliases are honored.
* test(kap-server): retry temp-home cleanup in auth tests
auth.test.ts removed its temp home with a plain recursive rm, which races
late async writers in the server shutdown path and flakes with ENOTEMPTY
(seen on main CI and locally on main). Harden the cleanup with the same
maxRetries/retryDelay options sessions.test.ts already uses.
* chore(changeset): consolidate the env-override changesets into two entries
One feature entry (loop/background env overrides, both engines plus the
CLI) and one fix entry (env values persisting on writes and sticking
after degrade/unset, agent-core-v2 plus the CLI).
* fix(config): clear fully-stripped sections and refresh overlay domains on get()
Two follow-ups from review:
- stripEnvBoundFields returned an empty object when every written field
was env-owned, so a section with a non-empty default (e.g. subagent)
stored raw = {} and the default stopped applying until the next
reload. A fully stripped result now clears the raw section instead.
- get(domain) only recomputed env overlays for sections with env
bindings, so domains written solely by a ConfigEffectiveOverlay
(models / defaultModel from KIMI_MODEL_NAME) kept serving stale cached
values after the env changed. get() now derives every non-memory
domain from the fresh env-free base, matching getAll().
* test(config): drive the get() overlay freshness test with an inline overlay
The previous version imported the model env overlay to activate it, which
the CI runners failed to resolve from this test file (both tsgo and
vitest, while the same specifier resolves elsewhere — not reproducible
locally). An inline ConfigEffectiveOverlay double exercises the same
ConfigService contract with a tighter seam and no module dependency.
* fix(config): preserve unknown fields on full strip and clone nested env targets
Two follow-ups from review:
- stripEnvBoundFields cleared the raw section whenever the stripped
result was empty, so an env echo write could drop unknown
forward-compatible fields from the TOML table. An emptied section now
keeps its raw table while the env-free base still holds other fields,
and is cleared only when nothing remains (defaults keep applying).
- applyEnvBindings reused nested child objects in place, so a nested
env binding on a persistable key would mutate the env-free validated
base and serve stale values after the env var is removed. Children
are now cloned before descending.
* fix(config): keep the env-free base when a strip leaves nothing to persist
Returning {} for a fully stripped write stored the empty object into the
raw layer while the TOML table survived via rawSnake, so the bases
diverged; a second echo write then saw an empty base, cleared the
section, and deleted the table — dropping unknown forward-compatible
fields. When nothing persistable remains, the write is now a no-op for
the section (the env-free base is kept as-is), and the section is
cleared only when the base is empty.
* fix(config): revalidate stripped results so restored raw values never persist
A strip may restore on-disk values from the unvalidated raw base (e.g.
an env-masked invalid field), smuggling them past the merge-time
validation into the stored config: the section would then fail the next
buildValidated pass, dropping accompanying valid edits from the runtime
while the invalid value stayed persisted. set() now revalidates the
stripped result — discarding the parse output so unknown fields survive
— and rejects the write instead, matching replace().
* fix: emit turn_id on turn_started/ended/interrupted telemetry
The turn lifecycle telemetry events (turn_started, turn_ended,
turn_interrupted) never carried the turn id, while tool_call and
tool_call_dedup_detected did. Any analysis correlating a turn's start,
end, or interruption back to its tool calls had nothing to join on.
Add turn_id (already in scope) to all three track() calls, matching the
existing key/value convention used by tool_call_dedup_detected.
Update the strict turn_started/turn_interrupted assertion to cover it.
* fix(agent-core-v2): emit turn_id on turn_started/ended/interrupted telemetry
Port the v1 fix to agent-core-v2: turn lifecycle telemetry events
(turn_started, turn_ended, turn_interrupted) carried no turn id while
tool_call did, leaving nothing to correlate a turn's start, end, or
interruption back to its tool calls.
Add turn_id to the three event interfaces, the telemetry registry
property docs, and the three track2() calls in AgentLoopService,
matching the existing ToolCallEvent key convention. Extend the
turn telemetry assertions in loop.test.ts to cover it.
* fix: emit turn_id on tool_call telemetry
* docs(agent-core-v2): clarify turn_id is a per-agent index in the telemetry registry
* feat(telemetry): add agent_id to turn and tool events
turn_id is a per-agent counter, so it collides across the main agent and subagents within a session. Emit each agent's scope id as agent_id on turn_*, tool_call, tool_call_dedup_detected, api_error and subagent_created (plus parent_agent_id) so events become attributable via (session_id, agent_id, turn_id).
* docs(changeset): cover turn_id emission alongside agent_id
* feat(telemetry): add agent_id to agent-level settings events
* feat(telemetry): link v2 events across agents, turns, and tool calls
- subagent_created: parent_tool_call_id, so a child run joins to the tool call that launched it
- permission_policy_decision / permission_approval_result: agent_id, turn_id, tool_call_id
- plan_submitted / plan_resolved / plan_enter_resolved, context_projection_repaired: agent_id
- compaction_finished / compaction_failed: agent_id + optional turn_id
- cron_scheduled / cron_deleted: optional agent_id of the scheduling agent
- api_error: turn_id + request_kind, so compaction request failures are distinguishable from turn requests
- tool_call_repeat: agent_id + optional turn_id; tool_call_dedup_detected stops fabricating turn_id: 0 outside a turn
- background_task_created/completed: task_id on both, unified kind vocabulary ('process' replaces the legacy 'bash' alias on created)
- agent lifecycle: auto-assigned agent-N ids now skip ids persisted by previous runs, so a resumed session cannot reissue agent-0 and collide with earlier telemetry
* fix(agent-core-v2): preserve telemetry turn attribution
* chore: split telemetry changeset into two logical changes
* refactor(agent-core-v2): bind agent telemetry context at scope
* test(agent-core-v2): fix telemetry assertions for scope-bound agent_id
- undoHistory/goal: include the injected agent_id in exact property assertions
- rpc-events: assert on the shared records array instead of a track2 spy,
which the scoped telemetry view bypasses
* refactor(agent-core-v2): bind agent identity ambiently in telemetry
- TelemetryService.withContext now returns a lightweight forwarding view:
transport state (appenders, enabled flag) stays with the App-scoped root,
so views created before an appender attaches no longer silently drop
events, and enablement changes apply to every live view.
- Agent-scope events register with defineAgentTelemetryEvent and compose the
centrally declared AgentTelemetryEventContext (agent_id) into their wire
schema; business payloads and call sites stay free of agent_id, enforced
at compile time and by the registry convention test.
- image_compress/image_crop stay plain events: the kap-server prompt routes
emit them through a session-scoped view without agent identity.
- v1 subagent_created gains parent_tool_call_id for parity with v2.
- Add a lifecycle test asserting each agent scope's telemetry view binds its
own agent id.
* Delete .changeset/subagent-id-reuse.md
Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
---------
Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
* feat(kap-server): enable multi-server shared home by default
- always register kap-server instances under server/instances and drop the
legacy single-instance lock (acquireLock/getLiveLock/ServerLockedError)
- remove the multi_server experimental flag and its
KIMI_CODE_EXPERIMENTAL_MULTI_SERVER env var from agent-core-v2
- discover running servers via the instance registry in server
ps/kill/rotate-token, kimi web daemon reuse, and the desktop app
- remove the pending minidb changesets
* feat(cli): add per-instance targeting to server kill and ps
- `kimi server kill [serverId]` stops only the matching instance; without
an id it still stops the longest-running one, and an unknown id errors
with the live server ids listed
- `kimi server ps` lists connections grouped per server id (`--json`
nests them under a per-server object); an unreachable instance degrades
to a per-server note instead of failing the whole listing
- update the zh/en command reference and the multi-server changeset
* feat(cli): replace kimi server with the kimi web command tree
- `kimi web` now runs the local server in the foreground and opens the
browser; the background daemon (ensureDaemon / spawn / idle-exit) is
removed, so repeated runs simply start another instance on the next
free port
- drop the OS-service lifecycle (install/uninstall/start/stop/restart/
status) together with kap-server's svc layer
- `kimi web kill [serverId]`, `kimi web ps`, and `kimi web rotate-token`
manage instances from the registry
- the TUI /web command now connects to an already-running instance
instead of spawning a background daemon
- update the zh/en command reference, dev scripts, and tests
* feat(cli): route kimi server invocations to a deprecation notice
Any `kimi server …` call — bare or with any legacy subcommand/flags —
now prints a deprecation notice pointing at `kimi web` and exits 1,
instead of failing with an opaque "unknown command". The shim is
scheduled for removal in the next major version.
* feat(cli): add the `all` keyword to kimi web kill
`kimi web kill all` stops every live instance in the registry (ULIDs
can never collide with the keyword). Each instance still gets the API
shutdown + SIGTERM/SIGKILL treatment; a failure on one instance does
not stop the sweep and is reported at the end.
* docs(changeset): drop the web-foreground-default changeset
The kimi web command tree replaces the foreground-default behavior this
entry describes: --background, daemon reuse, and the version-mismatch hint
no longer exist, so the pending entry would contradict the actual release
notes.
* docs(changeset): tighten the multi-server entry wording
* feat(cli): let /web pick a running server or start a new one
The /web picker now lists the live instances from the registry with
their versions (flagging a CLI mismatch) instead of only connecting to
the longest-running one, and offers starting a new server: that one
runs in the foreground attached to the terminal after the TUI exits,
via the restored exit-takeover wiring. formatReadyBanner is exported
and adapts its Stop hint to Ctrl+C for the attached case.
* feat(cli): skip the /web picker and start a new server when none is running
* 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.
* 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
* 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.
* 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>
* chore(kimi-code): upgrade pi-tui to 0.78.1 and adapt native helpers
Bump @earendil-works/pi-tui from ^0.74.0 to ^0.78.1. pi-tui 0.75.5 replaced its koffi-based Windows VT input with a bundled native helper, and 0.76.0 added a darwin native helper for Terminal.app Shift+Enter.
- SEA build: teach native-deps to collect pi-tui's per-target .node files, drop the koffi registry, and add a native-file-only collect mode so only package.json + the target .node ship (28 -> 2 files).
- Redirect pi-tui's absolute-path native require() into the native-asset cache through the Module._load hook, and extend the native smoke test to actually load the helper.
- npm package: ship pi-tui's native/ directory so macOS Terminal.app Shift+Enter and Windows Shift+Tab keep working for npm installs.
* chore: add changeset for pi-tui upgrade
* chore: vendor @earendil-works/pi-tui 0.80.2
Fork the upstream pi-tui source into packages/pi-tui for local modification. Pristine snapshot of @earendil-works/pi-tui@0.80.2; the apps/kimi-code dependency on ^0.78.1 from npm is left unchanged.
* feat(kimi-code): integrate vendored @moonshot-ai/pi-tui
Replace the npm @earendil-works/pi-tui dependency with the vendored @moonshot-ai/pi-tui workspace package so the fork can be modified locally.
- Point apps/kimi-code imports and native-deps at @moonshot-ai/pi-tui.
- Make pi-tui source-first (exports -> src, publishConfig.exports -> dist, mirroring node-sdk) and strict-clean: bracket access for process.env / named capture groups, an override modifier, and non-null assertions for noUncheckedIndexedAccess.
- Bump the root tsconfig target to ES2024 and enable allowImportingTsExtensions (needed for pi-tui's /v regex and .ts imports, which node --test requires).
- Add packages/pi-tui to flake.nix workspaces and exclude the vendored source from oxlint.
* fix(pi-tui): export package.json for native asset resolution
The SEA native-asset collector resolves the package root via
require.resolve('@moonshot-ai/pi-tui/package.json'). The vendored
package's exports field only exposed ".", which blocked the
"./package.json" subpath and broke build:native:sea with
ERR_PACKAGE_PATH_NOT_EXPORTED.
* chore(kimi-code): sync pi-tui native prebuilds at build time
Copy packages/pi-tui/native prebuilds into apps/kimi-code/native
during build instead of tracking a manual copy in git. Only the
.node prebuilds are copied (not the C sources); the directory is
now a build artifact covered by .gitignore.
* fix(pi-tui): avoid destructive full redraw during streaming
When the first changed line is above the viewport, the differential
renderer fell back to fullRender(true), which clears scrollback and
yanks the user's viewport. On Windows Terminal this jumps to the
absolute top (microsoft/Terminal#20370).
Clamp the diff to the visible viewport when content length is
unchanged (spinner tick / markdown reflow above the viewport), so
streaming no longer triggers a full redraw in those cases. Length
changes still fall back to fullRender to reset the viewport.
* fix(kimi-code): update pi-tui imports in files merged from main
Two files added on main (effort-selector, plugin-command) still
imported the old @earendil-works/pi-tui package name; point them at
the vendored @moonshot-ai/pi-tui.
* chore(nix): update pnpmDeps hash after lockfile regen
* fix(pi-tui): clamp above-viewport diff even when content shrinks
Previously, when the first changed line was above the viewport and
content length changed (e.g. spinner removed at end of streaming), the
renderer fell back to fullRender(true), which clears scrollback and
yanks the viewport to the absolute top on Windows Terminal.
Always clamp the diff to the visible viewport instead, preserving the
user's scroll position. Stale bytes remain in scrollback but are not
visible.
* fix(kimi-code): keep activity placeholder to avoid streaming shrink
When streaming ends, removing the activity spinner shrank the content
by two rows, which (combined with transient→final code highlighting
above the viewport) triggered a destructive full redraw. Keep a one-row
placeholder in the activity pane when idle so the content does not
fully shrink.
* chore: refine streaming scroll changeset wording
* chore: remove obsolete pi-tui native helpers changeset
* chore: add pi-tui changesets and document the pi-tui changelog rule
Add changesets for the fork integration, package manifest export, and
viewport clamp fix so the vendored pi-tui keeps its own changelog.
Update the gen-changesets skill to treat @moonshot-ai/pi-tui as a
special internal package that lists itself instead of the CLI, with a
separate CLI changeset only when the change is user-visible there.
* chore(nix): update pnpmDeps hash after merging main
* fix(changeset): drop private kimi-code-docs from google-genai changeset
* feat(tui): add ctrl+t to expand the todo list
Toggle between the truncated view and the full list; the shortcut only takes effect while the list actually overflows.
* docs(keyboard): document ctrl+t todo expand shortcut
* chore(changeset): mark todo expand shortcut as patch
* docs(agents): clarify minor vs patch in gen-changesets skill
* fix(tui): clear pending exit when toggling the todo list
* chore(skills): add release-precheck skill
Add a project-scope agent skill that audits an open kimi-code release PR ("ci: release packages") before merge: it lists the changesets the release will publish, maps each to its source PR, scans the release window for merged PRs that forgot a changeset, and flags wrong bump levels, wrong packages, and non-compliant wording. Outputs the release PR link and a concise suggestion table.
* chore(skills): fix release-precheck package glob and base filter
* chore(skills): classify shipped apps/vis paths as needing changeset
* chore(skills): simplify release-precheck to principle-based audit
* chore(skills): scope release-precheck to changeset validation only
* chore(skills): surface possibly-missing changesets in release-precheck
* chore(skills): replace release-precheck with pre-changelog
* docs(reports): collapse P3 plan into a single final-solution doc
Drop the per-step TDD/commit scaffolding; keep the substance as one final
approach per area (what it does, files to touch, key types/events/projection,
component responsibilities, verification, risks, sequencing).
* fix(kimi-web): normalize chat block spacing
Group consecutive tool cards structurally so chat block spacing is applied consistently without leaking card borders or shadows.
* feat(web): land P3 — goal / swarm / subagent + terminal + view split
Implements the locked P3 design end-to-end:
- subagent lifecycle projection (spawned→started→suspended→completed/failed) +
inline Agent / AgentGroup cards; swarm progress card (multi-column) derived
from swarmIndex; goal dock strip (expandable) from goal.updated; plan/goal/
swarm activation badges in the composer status line.
- terminal as a view (xterm + WS terminal_* frames with since_seq replay) and a
tab/view-dimension split (usePaneLayout tree + ViewGroup + SplitLayout, VSCode
editor-group style), persisted to localStorage.
Adds swarm-groups / subagent-goal / agent-group-turns unit tests and stub-daemon
seeds. 98 tests pass; vue-tsc + oxlint clean; production build OK.
Accepted by review (see reports/web-p3-acceptance.md); no blocking issues.
* docs(reports): P3 landing acceptance review
Comprehensive acceptance of the P3 landing (f5a7f21c): per-area verdicts, the
terminal 'map' crash explained as a stale-stub test artifact, non-blocking
recommendations, and verification record (98 tests, vue-tsc/oxlint clean, prod
build, in-browser smoke). No serious issues found; no code changed per the
'only fix serious issues' instruction.
* fix(terminal): make node-pty load and spawn in packaged + pnpm-dev builds
Two distinct PTY failures:
- 'Failed to load native module: pty.node' (npx/published daemon): node-pty was
transitively bundled via @moonshot-ai/services (alwaysBundle), inlining its JS
while its native binary can't be bundled and wasn't shipped. Mark node-pty
external in tsdown (neverBundle) and declare it as a runtime dependency of
@moonshot-ai/kimi-code so npm/npx installs it with its prebuilt pty.node.
- 'posix_spawnp failed' (local pnpm dev): node-pty's prebuilds/*/spawn-helper
loses its +x bit through pnpm's store extraction. Add a root postinstall
(scripts/fix-node-pty-perms.mjs) that restores the executable bit; verified it
fixes a reproducible spawn failure.
Also harden defaultShell() to fall back on an empty (not just unset) $SHELL.
Note: the SEA standalone binary still needs node-pty's pty.node + spawn-helper
wired into scripts/native/native-deps.mjs (not addressed here; npx path covers
the reported case).
* fix(web): use a real monospace font + tighter line height in the terminal
xterm's fontFamily takes a literal font string, so 'var(--mono)' never resolved
and the terminal fell back to courier with loose metrics — the wrong-looking
font and spacing. Pass the actual JetBrains Mono stack, await document.fonts
before xterm measures the cell (so the variable font isn't mismeasured), tighten
lineHeight 1.25 → 1.1, and pin letterSpacing 0.
* style(web): drop the staggered line-in animation on expanded tool-call output
Remove the per-line kimi-line-in stagger on `.box.open .bb > div` (modern/kimi
themes) and its keyframes — expanding a tool card no longer animates each output
line in.
* feat(web): move the tool-call summary into the card when expanded
Previously the command/summary always sat on the header. Now it shows on the
header only while collapsed; expanding hides it from the header and renders it
at the top of the card body (above the output) — so it appears exactly once and
the expanded header stays clean. Re-adds the .bb-summary style and a mount test.
* feat(web): show the full, un-truncated summary in the expanded tool card
The expanded body has room to wrap, so it shouldn't keep the header's '…'
clip. Add a `full` flag to toolSummary that skips the length clip and use it for
the .bb-summary; the collapsed header keeps the clipped form (CSS ellipsis still
guards overflow). Extends the mount test to cover full-vs-clipped.
* revert(web): keep the sending moon until the turn ends
Reverts 980ff9d4: dropping the moon the instant the first token streamed wasn't
wanted. Remove the assistantDelta/messageUpdated clear so sendingBySession is
again cleared only on turn end (onSessionIdle), restoring the prior behavior,
and delete the now-moot sending-moon test.
* style(web): bump composer textarea font-size to 14px
The composer input (.ph) under the modern/kimi themes was 13px while the
terminal-theme baseline is 14px. Unify on 14px so the textarea text matches
the rest of the composer.
* fix(web): dedupe the daemon echo of an image steer (no double user bubble)
Steering an image while a turn was running rendered TWO user bubbles and the
steer text looked like it never landed. Two causes:
1. The reducer matched the daemon's user-message echo to our optimistic copy by
exact content equality. Image content serializes differently on each side
(our {source:{kind:'file',fileId}} vs the daemon's resolved URL/base64), so
the echo never matched and appended a duplicate. Match by prompt_id first
(stamped on the optimistic message at submit), falling back to content.
2. Optimistic message ids were msg_opt_<Date.now()>. A queued send + a steer in
the same millisecond collided on one id, so the prompt_id stamp landed on the
wrong message. Use a monotonic counter for a unique id per optimistic message.
steerPrompt now also stamps the real prompt_id onto its optimistic echo, like
submitPromptInternal already did.
* fix(web): don't flash the chat pane when opening an empty session
Selecting a never-opened session set sessionLoading=true until its snapshot
arrived, so the chat pane (loading spinner) rendered for a beat before the
empty-composer. A session the daemon reports as empty (messageCount 0) has
nothing to load — keep sessionLoading false for it so the empty-composer shows
immediately. Non-empty sessions still show the loading state.
* fix(web): auto-scroll to the latest content after a mid-stream refresh
Refreshing while a turn was streaming left two things parked above the live
output:
- The thinking block's inner 5-line window stayed at its TOP. Its scroll watcher
only re-pins when already at the bottom, but a refresh delivers the whole
thinking text at once with scrollTop 0. Pin a streaming block to its latest
line on mount.
- The transcript could stop short of the bottom: the first scroll runs before
markdown highlighting/images lay out and grow the content. Re-pin on the next
couple of frames (only while still following) so a refresh ends at the latest
content.
* fix(web): stop subagent turns from fragmenting the parent transcript
A subagent runs under the parent session id and streams its own turn / step /
delta / tool frames over the SAME session channel, each tagged with the
subagent's agentId. The web projector folded them into the parent transcript,
which produced the reported bug: empty 'skeleton' assistant bubbles (a subagent
turn.step.started opened a parent assistant message the main agent never filled)
and fragmented snippets (subagent deltas appended to the parent).
Skip transcript-building frames whose agentId is a non-main subagent, mirroring
the server's InFlightTurnTracker (which already tracks only main-agent
activity). Subagent progress is unaffected — it flows through the
subagent.* -> task -> AgentCard path, which is intentionally not gated.
* feat(web): remove the floating todo/background-task overlay
The wide-screen float-stack pinned a todo card + running-tasks card to the
top-right of the chat. Drop the overlay entirely (and the now-unused
TasksCard.vue) — todos and background tasks live in their own ~/todo and ~/tasks
tabs, so the overlay was a redundant, transcript-covering duplicate.
* feat(web): show all background tasks in the tasks tab, scroll on overflow
The tasks tab capped the list at 5 rows and showed '… +N more', hiding the rest
even with plenty of room. Render every task and let the list scroll internally
once it overflows the pane, so nothing is silently dropped.
* feat(web): running spinner + unread blue dot left of the session title
The gutter slot left of each session title (which kept the title aligned under
the workspace name) now carries a status indicator instead of being an empty
spacer:
- a small SVG spinner (Kimi-blue arc) while the session is running, replacing
the old absolutely-positioned pulse dot;
- an unread blue dot when a BACKGROUND session finished a turn the user hasn't
opened yet. Tracked via unreadBySession (set on idle for a non-active session,
cleared when the session is selected).
* feat(web): unify archive/remove wording + keep the confirm within the title
- Clarify the two list-removal actions: a session is 'Archive' (归档), a
workspace is 'Remove workspace' (移除工作区) — the workspace menu used the bare
'Delete', which read as the same action as the session archive.
- Keep the session row's archive-confirm strip aligned under the title: the
leading gutter slot now persists in the confirm state, so the confirm row
starts at the title's left boundary instead of spilling to the row edge.
* feat(web): new-conversation button + workspace picker on the empty composer
- Add a compose button in the sidebar header (top-left) that starts a new
conversation in the active workspace. It wires up the previously-dead 'create'
emit (handleCreateSession → openWorkspaceDraft).
- On the empty composer, add a workspace picker below the hint so a new
conversation can be started in any workspace without leaving the screen
(switching enters that workspace's draft via openWorkspaceDraft).
* feat(web): add a Fork entry to the session row menu
Forking already worked via the /fork command and the daemon's :fork route, but
had no discoverable affordance. Add a 'Fork session' item to each session row's
kebab menu; forkSession() now takes an optional session id so any row (not just
the active one) can be forked.
* feat(web): recall sent messages with ArrowUp/ArrowDown in the composer
Shell-style history: ArrowUp on the first line of the composer walks back
through previously sent messages; ArrowDown on the last line walks forward and
finally restores the live draft. Editing the text leaves history-browsing, and
the edge-line guards keep multi-line cursor movement intact. Submitting (or
steering) a message appends it to the history (consecutive duplicates skipped).
* feat(web): capture console.log/info/debug + reusable log export
The client trace only captured console.error/warn. Capture every console level
(log/info/debug too) when tracing is enabled, so the exported troubleshooting
log reflects the full front-end console. Extract the JSONL download into a
reusable downloadTraceLog() (the debug panel now calls it; a settings 'Export
log' action can reuse it).
* feat(web): extract settings into a dedicated Settings page
Settings used to live in the sidebar account popover (a cramped fixed dropdown
that mixed appearance, language, account and the daemon endpoint). Move them
into a dedicated SettingsDialog modal opened from the header gear:
- Appearance (theme / colour scheme / accent), Language
- Account (provider, add workspace, reopen onboarding, sign in/out)
- Advanced (daemon endpoint, Export log — reuses downloadTraceLog)
The sidebar popover and its anchoring/positioning code are removed; the gear now
just emits openSettings. A Notifications section is added next (T14).
* feat(web): browser notification when a turn completes (with a settings toggle)
When a session finishes a turn and the user isn't already watching it (page
hidden, or a different session is active), fire a browser system notification
titled with the session, clicking it focuses the window and opens the session.
Opt-in via a new Notifications toggle in the Settings page; enabling it requests
OS permission and the preference is persisted (stays off if the user blocks it).
* feat(web): modes selector (plan/goal/swarm) + fix swarm double-render
- The plan pill at the composer's bottom-left becomes a 'Modes' popover that
groups Plan (a working client toggle) with Goal and Swarm. Each shows its
activated state (plan on / goal active / swarm n/m), and goal/swarm focus
their card in the chat when active. The menu is position:fixed so the composer
input row can't paint over it.
- Fix the swarm 'two blocks' bug: a multi-member swarm rendered BOTH inline as an
AgentGroup AND as its SwarmCard. messagesToTurns now skips the inline block for
swarm members (same membership test as buildSwarmGroups), so the swarm shows
once — its special card in the chat flow.
Note: starting a goal/swarm from the web needs a daemon REST endpoint (the goal
RPC isn't exposed over REST and the daemon doesn't interpret slash commands in
prompts); display + activation state are wired here.
* feat(web): add a chat context header (workspace/session, git, open, copy, PR)
A thin bar above the chat shows the workspace / session breadcrumb, the git
branch with ahead/behind + changed-file count, an 'open in editor' action
(daemon fs:open on the workspace root), and a 'copy all conversation' action
(reuses ChatPane.copyConversation). It also has a GitHub PR slot that renders
when PR data is available — the daemon doesn't expose PR status yet, so it's
wired but currently passed null. Hidden on mobile and for the empty composer.
* feat(web): default path + fuzzy recursive search in the add-workspace browser
- Open the folder browser at the path kimi-web is working in (the active
workspace root, falling back to $HOME) instead of always at $HOME.
- The filter becomes an fzf-style search: typing runs a bounded, debounced
RECURSIVE subsequence-fuzzy walk under the current folder (capped depth/dirs/
results, cancellable) and lists matching directories by relative path. The
result list keeps a fixed height, so the dialog never resizes while searching.
- Collapse the paste-an-absolute-path field behind a secondary 'enter a path'
toggle (auto-expanded when the daemon can't browse).
* chore(web): remove the non-functional /undo slash command
/undo had no daemon endpoint — it only pushed an 'undo not implemented' warning,
so it was a dead menu entry. Remove it from the slash list, the command router,
and the client. The full slash-command review with deletion suggestions for the
remaining commands is in reports/web-goal2-fixes.md (T17).
* docs(reports): results report for the second web TODO sweep (19 items)
* test(web): provide browser storage in vitest under node 24
* docs(reports): add web goal2 acceptance notes
* feat: show shortPath over branch in sidebar workspace header
* feat(kimi-code-web): use rounded chat bubble icon for new session button
* feat(kimi-code-web): add workspace creation in empty composer and tidy settings dialog
* feat: add manual swarm and goal activation to web ui
- Extend protocol schemas with swarm_mode, goal_objective, goal_control
- Add stub diff-dispatch in PromptService for new runtime controls
- Wire swarm/goal state through useKimiWebClient and daemon events
- Add Swarm toggle and Goal create/pause/resume/cancel in Composer modes menu
- Update StatusPanel and MobileSettingsSheet with swarm indicator/toggle
- Add bilingual i18n strings and update fixtures/tests
* feat: remove copy-conversation button from view-tabs
- Drop showCopyConversation / copyConversationCopied props from TabBar and ViewGroup
- Remove the share-conversation button markup and styles from TabBar
- Clean up related i18n strings in en/zh sidebar locales
- Keep the existing ChatHeader copy-all button and internal copy state unchanged
* feat: reorder chat-header layout and simplify git status styling
- Move Copy all button next to the workspace/session title on the left
- Move git branch/status and Open-in-editor to the right
- Shorten editor button label via new openInEditorShort i18n key
- Render ahead/behind/changes as plain colored text without pills
- Update en/zh header locale files
* style: make chat-header action buttons borderless icon + text
- Remove border, background, border-radius, and padding from .ch-act
- Keep label collapse on narrow widths, drop obsolete padding override
* feat: move copy-all to kebab menu and add session actions in chat-header
* feat: redesign chat-header open button with open-in menu
* feat: align chat-header diff stats with git ++/-- red/green style
* style(web): thinner, fainter scrollbars across all components
* fix(web): re-pin chat to bottom when a turn finishes streaming
* fix(web): keep the working moon spinning after a refresh mid-stream
* fix(web): subagent card margins in bubble layout + expandable task/result detail
* feat(web): rebuild subagent cards from the transcript so they survive a refresh
* fix(web): stop code blocks getting stuck on the loading skeleton
markstream's CodeBlock shows a skeleton while !stream && loading, and its
loading prop defaults to true. We never set it, so every settled code block
waited on shiki to highlight before showing anything; a screenful of code
(long session / fast burst) overwhelms shiki and the skeletons get stuck,
leaving the whole page blank. Pin loading:false so blocks render their
plain-text fallback immediately and upgrade to highlighted when ready.
* fix(web): tick running task timers + make task rows expandable to view output
* fix(web): dedupe image-steer echo via a loose (text+image-count) match
The daemon's messageCreated echo can land before submitPrompt stamps the
prompt_id onto the optimistic copy, and an image serializes differently
(file ref vs resolved URL), so neither the prompt_id nor exact-content match
fired and the echo rendered as a SECOND user bubble. Add a loose fallback
matching on text + image-count so the echo reconciles regardless of order.
* fix(web): let ↑/↓ walk all the way through input history once browsing
Recalling a multi-line entry left the caret on its last line, and the
'ArrowUp only on the first line' gate then refused to recall further, so
history only ever went one step back. Once browsing (historyIndex set), walk
history directly regardless of caret line; typing still exits browsing.
* feat(web): minimize button on question/approval cards + stack option label/desc
- Add a minimize toggle so a blocking question/approval can collapse to a thin
header bar instead of covering the chat; number-key shortcuts are gated while
collapsed so an unseen option can't be picked.
- Stack each option's label above its description (was squeezed side-by-side
into many thin lines when the description was long).
Note: there is no question/approval timeout in the codebase (ask-user waits
indefinitely), so the '10 minute' request is a no-op.
* feat(web): archive-confirm text matches title size; workspace remove always hides
- Bump the 'archive session?' confirm label to the session-title size (14px)
so it lines up with the title instead of reading as a smaller note.
- 'Remove workspace' now always hides the sidebar entry, even when it still has
sessions: record the root in a persisted hidden set so mergedWorkspaces stops
re-deriving it from session cwds. History/sessions are untouched; re-adding
the same path un-hides it.
* feat(web): persist unsent composer drafts per session in localStorage
The composer text is saved under a per-session key as you type and restored
when you switch back to that session or reload the page; sending/steering
clears it. New-session drafts use a '__new__' key.
* feat(web): implement undo + edit-and-resend the last user message
- Wire the daemon POST /sessions/{id}:undo endpoint: client.undo(count) reverts
the last turn(s) and re-syncs the snapshot. Restore the /undo slash command.
- Add an 'edit & resend' button on the latest user message: it undoes the last
exchange and refills the composer with that message's text for editing.
* fix(web): reflect the agent's plan mode in the composer toggle
The agent reports plan mode via agent.status.updated (e.g. it auto-entered plan
mode for a 'make a plan' prompt), but the projector only forwarded swarmMode, so
the composer's plan toggle never lit up. Carry planMode on sessionUsageUpdated,
sync it into state, and also read it from GET /status — mirroring swarmMode.
* fix(web): hide an empty {} argument from the tool-call title (kept in details)
An empty tool argument was rendered as a noisy '{}' in the collapsed tool-card
header. toolSummary now returns '' for empty args in header (non-full) mode while
the expanded body still shows it.
* feat(web): show file/media preview as a split pane (peer of chat/files)
On desktop, opening a preview from a chat link/media now splits the layout and
shows it as a 'preview' view at the chat/files level (a transient tab in that
group, closeable via the group's close button) instead of a separate right-side
panel — matching the split buttons. Mobile keeps the full-screen side panel; the
preview view isn't persisted across reloads.
* docs(reports): results report for the third web TODO sweep (16 items)
* chore(kimi-web): temporarily hide open-in-app header menu
* docs: design doc for temporarily disabling swarm and goal modes in web composer
* feat(web): gray out swarm and goal modes with not-supported label
* docs: design doc for composer queue bubble + expanded panel
* feat(kimi-web): move undo button out of bubble with new icon and confirm step
* fix: inherit split layout attributes
* fix(kimi-web): smooth moon spinner speed
* feat(web): wire swarm and goal controls to agent-core
- enable swarm toggle and goal input/pause/resume/cancel controls in Composer\n- add goal error codes and agent-core-to-protocol route mappings\n- wire enterSwarm/exitSwarm and create/pause/resume/cancelGoal RPCs in PromptService\n- bootstrap swarmMode from agent-core state and track it in the shadow\n- update tests for swarm/goal dispatch and session status serialization
* fix(tui): only show provider refresh status for added models
Skip removed / metadata-only provider updates when reporting model list changes.\n\n add: test to enforce the behavior.
* feat(tasks): add background task command and output polling to web
- include command field on protocol/server tasks\n- support withOutput/outputBytes on task get endpoint\n- poll running task output and fetch final output in web client\n- show bash command and terminal output in TasksPane with copy buttons
* feat(web,server): wire open-in app menu to daemon endpoint
- add /fs:open-in endpoint and command builders for vscode, cursor, finder, iterm, terminal\n- expose installed open_in_apps via meta response\n- filter OpenInMenu by available apps and remove antigravity target\n- support optional line number when opening files in apps\n- add unit tests for open-in launch commands
* feat(kimi-web): expose git diff line stats in chat header
- add additions/deletions to fs:git_status protocol and daemon response\n- compute aggregate diff stats with git diff --numstat HEAD\n- render +N/-N counter and detached HEAD label in chat header\n- update tests and stub daemon fixtures
* fix(kimi-web): hide terminal tab temporarily
* feat(kimi-web): add UI font size setting
* fix(kimi-web): repin chat after tail layout settles
* feat(kimi-web): show live subagent progress
* fix(kimi-web): align plaintext colors in dark mode
* feat(kimi-web): stream running bash output
* fix(kimi-web): avoid shiki overload on large messages
* feat(kimi-web): preselect recommended questions
* feat(kimi-web): add conversation outline nav
* fix(kimi-web): tighten mobile layouts
* feat(kimi-web): animate undo removal
* feat(kimi-web): reorganize bottom dock
* fix(web): lengthen session row running spinner arc
* feat(kimi-web): turn goal mode into a toolbar toggle
Turn the Modes menu's 'Goal' row from a dedicated input form into a
switch that arms the main composer. When goal mode is on, the composer
placeholder prompts for an objective and the next submitted prompt
is sent as a goal via updateSession({ goalObjective }).
The armed state is surfaced in the composer toolbar the same way as
Plan and Swarm: the Modes pill shows a 'Goal' tag and the menu switch
is highlighted. An active (agent-driven) goal continues to expose
Pause / Resume controls in the menu.
Also includes minor bottom-dock spacing alignment changes to keep
goal chips, workbar, and composer visually consistent.
* feat(config): add config API endpoint with redaction support
add GET/POST /api/v1/config routes\nadd ConfigService and config protocol schemas\nredact api_key in config response\nadd web client bindings and config event handling\nadd e2e tests for config routes and ws-broadcast
* fix: keep web tool calls collapsed by default
* feat(kimi-web): split dock work panel into bash, subagent, and todos tabs
- Replace the single Background tasks tab with separate Bash and
Subagent tabs in the bottom dock work panel.
- Add i18n labels for the new dock tabs in both en and zh.
- Filter task lists by kind and reuse TasksPane for each tab.
- Align left edges of Bash/Subagent/Todos tab bodies and match
the dock panel width/background to the Goal card style.
- Hide TasksPane header title when rendered inside the dock to
avoid duplicate headings.
- Make the dock tab header show only the current tab name instead
of clickable buttons.
- Hide Goal card title summary on expand while keeping layout
alignment for status/progress/chevron.
- Update unit tests for the three-chip dock behavior.
- Add changesets for the dock split and alignment fixes.
* feat(kimi-web): beta proportional conversation outline with viewport indicator and hover tooltip
* fix(kimi-web): avoid streaming markdown placeholders
* fix(kimi-web): hide legacy conversation outline when beta TOC is off
* chore(kimi-web): rename beta settings section to Experimental / 实验性
* fix(web): scale UI font size consistently
Apply the web font size preference across readable text using the shared UI font scale, keep fixed icon glyph sizes pinned, and raise the default font size to 15px.
* fix(web): remove task tabs from tab bar
* feat(web): refresh OAuth model metadata for always-thinking models
* fix(kimi-web): use 'Sub Agent' and 'Mode' in English labels
* fix(kimi-web): keep swarm subagents across background-task refreshes
REST /tasks lists only the main agent's background-task store and never
returns foreground swarm subagents (kind 'subagent'), which arrive purely
through the WS event stream. Both the 1s output poll and the session-load
task fetch rebuilt tasksBySession from that REST list, so a plain replace
dropped the subagents on every refresh and the next event re-added them —
flickering the swarm/subagent cards, their live "currently doing" line,
and the dock "running" count about once per second.
Add keepLiveSubagents() to carry WS-owned subagent tasks across the REST
refresh (REST stays authoritative for the background tasks it does return)
and use it at both rebuild sites.
* fix(kimi-web): hide completed swarm cards from conversation bottom stack
- Filter out swarm groups whose members are all completed/failed.
- Preserve markstream-vue .table-node styles without overriding its layout.
- Add unit tests for swarm stack visibility.
* feat(session): add session-level abort and expose current_prompt_id in snapshot
- add POST /sessions/{sid}:abort to cancel running turns without prompt_id\n- expose current_prompt_id in in-flight turn snapshot\n- wire session-level abort fallback in kimi-web stop button\n- add IPromptService.abortBySession and getCurrentPromptId\n- update protocol, server, services, and web tests
* fix(server): allow aborting queued prompts and add server-e2e send/cancel coverage
add server-e2e scenario (12-send-and-cancel) and vitest cases for send prompt / cancel prompt flows, including repeated ESC idempotency\nsupport aborting queued prompts in PromptService.abort and add abortSession helper to DaemonClient/HttpClient\nhandle SSE transport case in MCP server mapping and fix related typecheck issues\nadd changeset for @moonshot-ai/services and @moonshot-ai/kimi-code
* fix(kimi-web): prevent file preview scroll jump when opening a file at a line
* fix(kimi-web): show just now for sessions created less than a minute ago
* fix(kimi-web): keep sidebar logo intact and hide product name on narrow sidebars
* fix(kimi-web): preserve markdown code gutter
* feat(web): carry message createdAt into ChatTurn
* feat(web): add formatMessageTime utility
* feat(i18n): localize yesterday label for message timestamps
* feat(web): render timestamp below user query bubble
* fix(web): move user query timestamp outside the bubble
* fix(web): place timestamp on same line as undo action
* fix(web): swap timestamp and undo positions, reveal undo text on hover
* feat(web): make timestamp a button that toggles full date time
* feat(web): update sidebar branding and enlarge session tags; clean up changesets
- Replace "Kimi Code Web" + "BETA" with "Kimi Code" + version pill
- Enlarge session pending tags to match the title font size
- Ignore internal private packages in changeset config
- Remove stale changesets that only affected ignored packages
- Document web release flow in README
* style(web): equalize timestamp and undo button heights and alignment
* feat(web): make workspace names bolder to distinguish from session titles
* feat(kimi-web): rename themes to Explore/Native and remove accent selector
* style(web): nudge undo icon up by 1px
* style(web): align both meta actions to the right
* style(web): remove gap between undo and timestamp buttons
* style(web): nudge undo icon up by another 0.5px
* feat(web): tune workspace name font-weight to 500
* fix(kimi-web): center tag/question text and limit shell-cmd height in approval card
* style(kimi-web): remove icons from session pending tags
* feat(kimi-web): limit recent workspaces in empty-composer picker
* feat(kimi-web): rename sidebar new-workspace button to new-chat and adjust empty conversation title
* style(kimi-web): refine sidebar typography, spacing and font settings
* style(kimi-web): unify Composer typography with sidebar
- Use --ui-font-size for queue text and --ui-font-size-xs for queue labels/bubbles.
- Remove mono font-family from composer queue/bubble elements.
- Normalize perm/mode/model pill text color to --text.
- Set placeholder color to --muted.
* style(kimi-web): keep model pill color dimmed
Revert the model selector pill text color from --text back to --dim
so it stays visually secondary, matching the original design intent.
* style(kimi-web): adjust ChatHeader git status spacing and badge layout
* style(kimi-web): polish composer dock chip styles
* feat(kimi-web): refresh session list relative times on a 30s clock
* fix(kimi-web): decode base64 file content in the preview pane
* feat(kimi-web): show per-session answer/approve tags in the sidebar
* feat(kimi-web): pop the KAP debug panel out into a separate window
* feat(kimi-web): open subagent detail in the side panel; inline agent-live
* style(kimi-web): align DiffView focus outline with KMBlue
* feat(kimi-web): surface goal protocol errors; ignore global config-changed events
* feat(kimi-web): support video attachments in user messages end-to-end
* feat(kimi-web): add /swarm and /goal slash commands
* feat(kimi-web): add /btw side chat backed by child sessions
* style(kimi-web): pin user message bubble font size to 15px
* style(kimi-web): use Lucide PR icon and text-only git status in header
* fix(kimi-web): update undo tooltip copy and reduce hover delay
* fix(kimi-web): auto-scroll to bottom on send, session switch, and tab switch
- Scroll side-chat panel to bottom after sending and while streaming
- Reset scroll baseline on session switch to avoid stale lastScrollTop
- Reset scroll baseline when returning from files tab to chat
- Reset scroll baseline after user sends a message
- Include @moonshot-ai/kimi-code so CLI rebuilds bundle the updated web app
* fix(kimi-web): drop duplicate config-changed case shadowing the real handler
A stopgap no-op `case 'event.config.changed'` (added before the config
feature landed) ended up earlier in the switch than the real configChanged
mapper after merging origin/feat/web, silently swallowing config events.
Remove the no-op so the proper handler runs.
* style(kimi-web): unify PR badge with git status pills and drop changes count
* style(kimi-web): remove unused changes computed in ChatHeader
* fix: keep packaged web build in sync
Build kimi-web before copying packaged web assets and surface the build version plus short commit in the settings dialog.
* fix(web): support slash command input tails
* fix(web): scope composer dock to chat tab
* fix(web): route btw through side-channel agents
* feat(web): open changed files from git status
* feat(web): copy final assistant summary
* feat(web): add tabbed model picker
* fix(web): keep composer input height fixed
* fix(web): preview composer attachments
* feat(web): open workspace links in files tab
* fix(web): stabilize subagent progress
* docs(web): design tab split workflow
* feat(web): connect daemon config settings
* fix: resolve CI failures on feat/web
- add missing 'event.config.changed' case in exhaustive switch test\n- fix oxlint errors (unused import, string spread, unsafe stringification)\n- update protocol test fixtures for additions/deletions, open_in_apps, swarm_mode\n- fix services mcp transport switch exhaustiveness for sse\n- update nix pnpm deps hash
* fix(server): suppress debug logs by default
- route BridgeClientAPI and PromptService debug logs through ILogService instead of console.error\n- lower SessionClientsService debug logs from info to debug\n- add changeset for @moonshot-ai/services, @moonshot-ai/server and @moonshot-ai/kimi-code
* fix(kimi-web): remove model picker top blue bar and widen dialog
- Remove the inset blue box-shadow from the model picker header.
- Increase the default dialog width from 620px to 760px.
* fix(kimi-web): replace model picker checkmark with icon
- Swap the textual checkmark for a proper SVG check icon in ModelPicker,
matching the icon used in the composer model dropdown.
* fix(kimi-web): hide the Open in app menu
- Remove OpenInMenu usage from ChatHeader and the prop/event plumbing
through ConversationPane and App.
- Remove the now-obsolete test case in files-tab-no-git.test.ts.
* feat(kimi-web): scope composer dock to chat tab and polish BTW side chat
- Move the composer dock into the chat tab only so it no longer appears in
split file, task, preview, or BTW panes.
- Render the BTW side chat as a split side pane scoped to the active session,
and keep its messages out of the main conversation transcript.
- Remove the side-chat panel header, relabel the tab to Side chat / 侧边聊天,
and use the shared moon spinner while waiting for the first token.
- Suppress the generic Started a step progress text for side-channel agents.
* feat(server): expose live session status via HTTP and WebSocket
- add status field to session status response schema and event.session.status_changed\n- compute session lifecycle status in SessionService from approvals, questions, prompts, and turns\n- broadcast event.session.status_changed globally to all WebSocket connections\n- re-export new event types from agent-core\n- add e2e and unit tests for status computation and broadcasting
* fix(kimi-web): label sidebar session removal as Archive
* feat(kimi-web): make tab/split chrome accessible
TabBar is now a real ARIA tablist: role=tab buttons with aria-selected,
roving tabindex, Left/Right/Home/End keyboard nav, focus-visible styling,
and aria-controls wired to each ViewGroup's tabpanel (role=tabpanel +
aria-labelledby).
ViewGroup split-right/split-down/close buttons get localized aria-labels
(no longer English title-only) and a 28x28 hit area.
* fix(kimi-web): move auth banner into layout flow
The onboarding/auth banner was position:fixed over the top of the
conversation column, covering the desktop ChatHeader and the mobile
top bar. Wrap the app grid in a flex-column shell and render the banner
as the shell's first in-flow child so it reserves its own height above
both the sidebar/header and the mobile top bar instead of overlapping
navigation.
* fix(kimi-web): enlarge and label header/sidebar icon buttons
Unify icon-button hit areas and keyboard affordances:
- ChatHeader kebab: 28x28 target, aria-label + aria-expanded/haspopup,
focus-visible ring.
- Sidebar workspace kebab (.gh-more): 24x24 target, aria-label, stays
visible on keyboard focus (it was hover-only), focus ring.
- Sidebar per-workspace add (.gh-add): aria-label + larger tap target.
- Focus rings on the settings button and new-chat/new-workspace buttons.
* feat(kimi-web): give overlay dialogs modal focus management
Add useDialogFocus(): records the opener, moves focus into the dialog on
open, and restores focus to the opener on close. Wire it plus
aria-modal="true" / tabindex="-1" into ModelPicker, LoginDialog and
ProviderManager (Escape-to-close was already present). ModelPicker keeps
focusing its search box on open. Covered by a model-picker focus test.
SettingsDialog is intentionally left for a follow-up to avoid colliding
with in-flight settings work.
* feat(kimi-web): consistent rules for the right-side detail layer
The transient detail panels (thinking, compaction summary, subagent
detail, mobile file/media preview) now share one set of rules:
- the aside is a labelled role=complementary region (aria-hidden when
collapsed),
- Escape closes whichever panel is open — handled in App on the capture
phase so it takes precedence over the conversation's "Esc interrupts a
run" handler instead of firing both,
- every close button has an aria-label (not just a title) and a
focus-visible ring; thinking/subagent close targets bumped to 28x28,
- FilePreview toolbar buttons get focus-visible rings too.
* fix(kimi-web): calm the diff line colors
Added/removed diff lines washed the entire row in green/red (12% tint +
fully coloured text), which competed with reading the code. Drop the
background to a faint 7% tint plus a left accent bar, color only the +/-
sign, and let the code text keep the normal ink color so the content —
not the color wash — is what stands out.
* docs(web): record tab/split convergence + UI audit follow-through
Implementation note for the tab/split work: the final three-layer view
model (persistent chat/files tabs, transient preview/btw tabs, right-side
detail layer), the transient-view routing rules, a per-task status table,
which audit suggestions were absorbed vs intentionally skipped, and why
the SettingsDialog focus item is deferred (concurrent in-flight rewrite).
* feat(kimi-web): add side-tab navigation to SettingsDialog
* feat(session): persist session archive state and add include_archive list filter
- Replace deleteSession with archiveSession RPC and REST endpoint\n- Persist archived flag in session state and filter archived sessions by default\n- Add optional include_archive query parameter to list archived sessions\n- Expose archived flag on session responses through protocol and web types\n- Rename web session delete events/handlers to archive
* feat(kimi-web): give SettingsDialog modal focus management
Completes the dialog-focus baseline (task 8): wire useDialogFocus +
aria-modal/tabindex into SettingsDialog now that the side-tab rewrite has
landed. Focus moves into the dialog on open and returns to the opener on
close; covered by a settings-dialog focus test.
* refactor(workspace): centralize registry into a single workspaces.json
- replace per-bucket workspace.json files with one workspaces.json registry\n- serialize registry reads/writes through an opQueue to avoid races\n- delete now removes only the registry entry, leaving the session bucket intact\n- update workspace e2e tests and scenario for the new storage model
* feat(workspace): add workspace lifecycle WS events
- publish event.workspace.created/updated/deleted from the registry service\n- broadcast them to every connection via the __global__ watermark\n- add protocol types/schemas and frontend mapping for real-time workspace sync\n- cover with protocol, broadcast, and exhaustive-switch tests
* feat(fs): add fs:mkdir action for creating directories
- add fsMkdir request/response schemas and FS_ALREADY_EXISTS (40919) error code
- implement IFsService.mkdir guarded by resolveSafePath, returning the created directory entry
- register the mkdir action in the fs route dispatcher with EEXIST/ENOENT mapping
- add protocol schema tests and server e2e coverage
* style(kimi-web): set fixed heights for all modal dialogs
* feat(kimi-web): add collapsible sidebar
* refactor(web): temporarily hide new workspace button in sidebar
* feat(kimi-web): add starred models support to model picker and composer dropdown
- Persist starred model ids in localStorage via useKimiWebClient.
- Pin starred models to the top of the All tab in ModelPicker.
- Show a Starred section in the Composer quick-switch dropdown, including models from other providers.
- Render a star glyph on each starred model row in both pickers.
- Add --star CSS variable with a brighter yellow across themes.
- Add tests for starred model ordering and Composer dropdown rendering.
* test(kimi-web): cover chat dock composer alignment
* feat(server): expose GitHub pull request in git status and web header
Add a pullRequest field to the session fs:git_status response, looked up via gh pr view with a 5s timeout, GH_NO_UPDATE_NOTIFIER/GH_PROMPT_DISABLED, and a 60s per-cwd cache.\nNormalize gh state to open/merged/closed and fail soft to null so git status never breaks.\nWire the web chat header PR badge to the active session.
* feat(kimi-web): surface 5-state session status with a separate busy flag
The session view-model collapsed every lifecycle state to running|idle,
so awaiting-input and aborted sessions were indistinguishable and the
spinner span while a session was actually waiting on the user.
Session now carries the real `status` (idle/running/awaitingApproval/
awaitingQuestion/aborted) plus a separate `busy` flag (running + a real
task in flight). SessionRow spins only when busy, shows awaiting tags
from status as a fallback for background sessions, and a distinct aborted
tag; SessionsDialog and MobileSwitcherSheet distinguish the states too.
(The producers in useKimiWebClient already landed via an earlier commit;
this adds the Session type, the UI, and labels so the tree type-checks.)
* fix(kimi-web): persist unread dots across a page reload
unreadBySession was pure in-memory state seeded empty on every load, with
no localStorage persistence and no server-side read cursor — so a browser
refresh dropped every sidebar unread dot. Persist the `true` entries to
localStorage (compact: only unread sessions are stored) and seed the map
from storage on init; opening a session clears the flag and the stored
entry. Covered by a reload test in the session-cache suite.
Note: also carries pre-existing empty-session-flash test edits that were
already part of this file's working state.
* style(kimi-web): redraw collapse/expand sidebar icons
Use an indent-style glyph: three lines with a directional chevron, mirrored between the collapse (left) and expand (right) states.
* feat: add server-hosted web UI and document its packages
- wire kimi-web into root and CI typecheck (vue-tsc) and refresh the Nix pnpm hash
- consolidate per-feature changesets into a single server-hosted-web-ui entry
- make agentEventProjector.shortJson resilient to stringify failures and adjust tests
- add AGENTS.md for kimi-web and server; add READMEs for server and services
- expand root AGENTS.md project map and clarify the flake.nix workspace-sync rule
* test(kimi-web): cover empty-session flash + draft-send paths
Tests and changeset for the empty-session-flash fix (the sessionsKnownEmpty
/ sessionLoading logic itself already landed in an earlier commit):
selecting a locally-created session shows the empty composer with no
loading flash, an existing session reported as empty still loads its
snapshot (messageCount is not trusted), and sending straight from the
draft composer does not flash the empty state.
* chore: ignore generated docs and reports
* style(apps/kimi-web): remove app shell top border
* fix(build): build kimi-web assets before native SEA build
The native SEA build embeds the Kimi web SPA from apps/kimi-code/dist-web (see scripts/native/02-sea-blob.mjs) and fails when that directory is missing. Build kimi-web and stage its assets via copy-web-assets.mjs before running build:native:sea.
- flake.nix: add the web build + asset copy to buildPhase so `nix build` works.
- _native-build.yml: add the same prep step before the SEA build so CI release / manual native bundles keep working.
Fixes "Kimi web build output was not found at .../dist-web" in the nix build and the CI native build stage.
* feat(web): hide context indicator on empty session composer
* feat(kimi-web): unify detail panel layout
* fix(web): improve dark toc tooltip contrast
* fix(web): keep markdown code blocks mounted
* fix(web): tighten dark color contrast
* fix(web): close dock cards on outside click
* feat(web): show session content summaries
* fix(web): report web client telemetry
* refactor(services): merge @moonshot-ai/services into agent-core
- move services/src/** into agent-core/src/services/** and delete the standalone @moonshot-ai/services package
- re-export service contracts/implementations from agent-core src/index.ts
- update all server, test, and kimi-web imports to @moonshot-ai/agent-core
- enable experimentalDecorators in tsconfig and adjust dev/build configs
- sync workspace registry (changeset config, flake.nix, pnpm-lock)
* refactor(server): remove Swagger UI and --swagger flag
- drop @fastify/swagger-ui and the dev-only --swagger option
- keep /openapi.json via @fastify/swagger (bundled in the SEA)
- simplify the native SEA build (no swagger-ui external or asset copy)
- update tests, docs, and lockfile
* feat(server): add on-demand daemon for kimi web with idle shutdown
- make kimi web non-blocking by spawning or reusing a single detached daemon per device (via ~/.kimi-code/server/lock), auto-picking a free port on conflict
- daemon self-exits after a 1-minute grace once the last web WebSocket client disconnects (new onConnectionCountChange hook + createIdleShutdownHandler)
- add getLiveLock helper for daemon discovery
- hide kimi server install/uninstall/start/stop/restart/status (service-ization) for now; implementation preserved for later re-exposure
* fix(web): improve mobile dialog layouts
* fix(ci): resolve lint, typecheck, and nix build failures
- add startBtw to IPromptService test mocks (agent-core, server)
- remove useless spread in PromptService session cleanup
- add assertion to concurrent-connection WS handshake test
- bind idle onConnectionCountChange callback in server run
- type getSessionSnapshot mock via vi.mocked in kimi-web test
- update pnpmDeps hash in flake.nix
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(web): distinguish native markdown links
* fix(vis-web): enable experimentalDecorators for typecheck
vis-web type-checks agent-core source (via source exports), whose services use legacy parameter decorators for DI. Without experimentalDecorators, tsc reports TS1206 "Decorators are not valid here" and the CI typecheck job fails.
* Revert "feat(web): show session content summaries"
This reverts commit 8de58eacbc.
* fix(web): align active toc-bubble highlight with bubble edges
* fix(web): hide conversation toc when chat pane is too narrow
* feat(server): add `kimi server ps` to list active clients
- add GET /api/v1/connections endpoint backed by IConnectionRegistry
- record connection metadata (connectedAt, remoteAddress, userAgent) on WsConnection
- add connection wire schema in @moonshot-ai/protocol
- add kimi server ps CLI command with table and --json output
* feat(kimi-web): add queue chip to chat dock workbar
* fix(agent-core): hide console window when spawning git/gh on Windows
On Windows, spawning a console-subsystem executable (gh.exe / git.exe)
from the background Kimi server creates a visible console window that
flashes on screen. Set windowsHide: true (a no-op on POSIX) to suppress it.
* feat(server): add startup and ws connection telemetry for kimi web
- bootstrap telemetry in `kimi web`/`kimi server run` via initializeServerTelemetry (ui_mode=web, honors telemetry=false)
- wire the real client into KimiCore so agent-core events carry the enriched context
- emit server_started after the server listens; flush telemetry on shutdown
- emit ws_connected/ws_disconnected from WSGateway via WSGatewayOptions.telemetry
- re-export loadRuntimeConfigSafe/resolveConfigPath from the SDK for host config reads
* feat(web): dynamic page title based on session or workspace
* fix(web): show recently active sessions at the top of the web session list
* feat(web): show running indicator in dynamic page title
* feat(kimi-web): show elapsed time for completed assistant turns
* fix(kimi-web): resolve TDZ error on App mount
* refactor(kimi-web): align turn duration and support multi-tab timing
* feat: display per-turn wall-clock duration in web chat
* feat(web): use animated spinner in page title while running
* feat(server): add kill command and background server run
- add `kimi server kill` to stop the running daemon (graceful API + forced PID kill)
- add `POST /api/v1/shutdown` so the server can terminate itself
- make `kimi server run` start in the background and print the ready banner
- route `kimi web` through the same path as `server run` so it prints the banner too
* fix(server): remove duplicate startBtw key in prompt e2e test
Resolves eslint no-dupe-keys and TS1117 errors that broke the lint and typecheck CI jobs.
* chore: bundle Inter font locally
* fix(nix): update pnpmDeps hash for new font dependency
The local Inter font dependency changed pnpm-lock.yaml, so refresh the fixed-output derivation hash to match what the nix builder computes. Resolves the nix build .#kimi-code CI failure.
* fix(server): restore web client telemetry and stabilize skills cleanup
Restore forwarding of x-kimi-client-* headers into session creation telemetry, which was dropped during the services-to-agent-core merge and left the new-session telemetry test with empty records.\n\nRetry the skills e2e sandbox cleanup to ride out ENOTEMPTY races when the core process flushes files into the sandboxed home after close().
* chore: remove trailing blank lines
* chore(changeset): remove consumed changeset files
These 20 changeset files were applied during the version bump and are no longer needed.
* chore: clean up web release changesets
* docs: clean up kimi web readme
* chore: scope package lint to cli release
* chore: remove temporary design docs and preview files from PR
Remove files that were not intended for submission:
- docs HTML research/design archives
- docs/superpowers specs added during design phase
- apps/kimi-web icon preview pages and one-off test script
* chore(kimi-web): remove dev stub daemon
The real server package is now available; the throwaway stub daemon
is no longer needed for development.
* test(server-e2e): accept aborted image prompt scenario
* chore: update flake.nix workspace paths and add new changesets
- Added new packages: daemon, server-e2e, and kimi-migration-legacy to the workspacePaths in flake.nix.
- Introduced new changeset for "@moonshot-ai/kimi-code-sdk" to add host-side config helpers.
- Removed outdated changesets related to server-hosted web UI and server web APIs.
* feat(server): daemonize by default and fall back to port +1
- kimi server run now spawns a background daemon by default; --foreground keeps the terminal attached
- default server port moves from 7878 to 58627 across CLI, web, e2e, and docs
- listenWithPortRetry retries on port + 1 when a third party holds the port (capped at 100)
- lock gains updatePort so status/kill/ps find the daemon on its real bound port
* fix(cli): resolve oxlint unbound-method errors in server run
---------
Signed-off-by: qer <wbxl2000@outlook.com>
Co-authored-by: qer <wbxl2000@outlook.com>
Co-authored-by: Claude <noreply@anthropic.com>
* Refactor theme
* custom theme support
* docs: add custom themes guide
Document the custom theme file location, the color token reference, selecting a theme via /theme and tui.toml, and fallback behavior. Link it from the customization sidebar and the tui.toml theme field.
* feat(skill): add built-in custom-theme skill
Guides the model (or a manual /custom-theme run) to author a theme JSON in ~/.kimi-code/themes/: docs token reference, deliberate color choices, hex validation, and how to apply via /theme or /reload-tui. Note in the write-tui skill to keep the token set in sync across colors.ts, the schema, the docs, and this skill. Enrich the custom-theme changeset to cover all three usage paths.
* chore: remove theme research report
* fix(tui): resolve lint errors after main merge
Remove unused chalk/currentTheme/ResolvedTheme imports left by the theme refactor; break the theme <-> pi-tui-theme import cycle by dropping the markdown/editor theme getters from the Theme class (consumers call createMarkdownTheme directly); fix unused vars/params, a floating promise, and a redundant union type.
* fix(tui): address custom theme review feedback
- await applyTheme before refreshing terminal theme tracking, so
switching to "auto" installs the watcher against the new state
- invalidate the transcript on automatic (terminal-driven) theme
changes so already-rendered entries repaint
- rebuild UsagePanel bodies on invalidate (previously a no-op); /usage,
/status, /mcp and /plugins now repaint on a theme switch
- repaint the compaction header on invalidate
- validate a custom theme before applying it from the /theme picker
- hide reserved dark/light/auto names from the custom theme list
- escape the theme name when writing tui.toml
- stop the custom theme loader writing warnings to the raw terminal
- remove a stray hello.ts
* refactor(tui): polish custom theme feature
- footer and todo-panel read the currentTheme singleton directly at
render time instead of caching a palette copy; drop their setColors
methods and the manual setColors calls on every theme change
- support "base": "dark" | "light" in custom theme files so a partial
light theme inherits the light palette for unspecified tokens
- reconcile the docs and the custom-theme skill with the silent
invalid-color fallback (no terminal warning)
* refactor(tui): live-repaint the agent swarm progress panel
Read the currentTheme palette through a getter instead of caching it at
construction time, so the swarm progress panel recolors on a theme switch
like the rest of the transcript. Drops the now-unused `colors` option.
* chore: remove plan.md
* docs: update custom theme guide
* docs: document custom theme skill command
* fix(skill): make custom theme user-triggered only
Add the published release date to every version heading on the
user-facing changelog pages (English half-width, Chinese full-width),
covering 0.2.0 through 0.9.0. Dates come from each version's published
release tag. Also update the sync-changelog skill so future syncs carry
the date convention forward.
* feat(cli): unify TUI dialog interaction and visuals
Align every list dialog and selector to a single spec: shared selection
pointer and current-item marker, single-border header with a (type to
search) title suffix, consistent search line, and a uniform keyboard-hint
vocabulary. Replace ad-hoc exit wording (close/back/exit/dismiss) with
cancel, hardcoded pointers with the shared constant, and ▲▼ with ↑↓.
Also add fuzzy search to /provider, restyle the /model provider tabs, and
require a token with multi-field navigation in the custom-registry import.
Introduce the write-tui skill (carrying the DESIGN.md spec) and refresh
the apps/kimi-code development guide to match the current TUI architecture.
* feat(cli): drop arrow-key navigation in the plugins selector
The plugins overview and its marketplace/MCP sub-views used Left/Right to
enter and exit details. Remove that hierarchy navigation: Enter opens a
detail, Esc returns, and the arrow keys no longer jump between levels.
Update the sub-view hints from `←/Esc cancel` to `Esc cancel`.
* feat(cli): install marketplace plugins on Enter only
The marketplace install action also fired on Space, which collides with
the Space-toggle convention used elsewhere. Bind install to Enter only and
update the hint to `Enter install/update`.
* feat(cli): reword the plugin inline change hint
The inline badge shown on a changed plugin row read `pending /new`, which
was cryptic. Reword it to `require run /new to apply`. Also switch the
marketplace-install message-flow tests to Enter, matching the Enter-only
install binding.
* fix(cli): stop the editor flash when toggling a plugin
Each plugin picker's onSelect unconditionally restored the editor before
the handler re-mounted the refreshed picker, so an in-place action like
Space-toggling a plugin flashed: picker → editor → picker. Drop the
pre-restore from the overview/marketplace/MCP onSelect callbacks and let
each handler branch mount its next view; the two branches that close to the
editor (show-list, info) restore it themselves.
* feat(cli): drop /provider search and delete with D
Remove the fuzzy filter from the provider manager: no query state, search
line, or type-to-search title suffix, and Esc closes directly. With no
type-to-search to clash with, bind delete to the D key (matching /plugins)
instead of Del/Ctrl+D. Update the write-tui spec to specify D as the
delete shortcut.
* fix(cli): default thinking-capable models to thinking on
The model selector seeded a single thinking draft from the global thinking
flag, so highlighting a thinking-capable model showed Off whenever the
active model had thinking off (e.g. a non-thinking current model). Compute
the draft per model instead: the active model keeps its live state, any
other thinking-capable model defaults to On, and a ←/→ toggle is remembered
per model.
* chore(flake): simplify nix build and add ci validation
- Replace dynamic pnpm-workspace.yaml parsing with hardcoded workspacePaths
and workspaceNames to reduce format assumptions
- Remove update-pnpm-deps script and kimi-code-pnpm-deps package; use
lib.fakeHash for standard hash mismatch workflow
- Remove nodejs_latest fallback in nodejsFor, hardcode to nodejs_24
- Add nix-build CI workflow that posts hash-mismatch details to PR comments
- Remove unused Nix installation step from release.yml
- Add workspace maintenance note to AGENTS.md