* chore: remove kimi-desktop app and desktop release pipeline
The desktop client has moved to a dedicated internal repository. Drop the
Electron shell app, the desktop-build reusable workflow and its release
job, and all workspace references (dev script, typecheck filter,
onlyBuiltDependencies, flake.nix entries, lockfile). The kimi-web desktop
detection code is intentionally kept intact.
* chore: fix flake.nix after kimi-desktop removal
Complete the workspaceNames cleanup missed in the previous commit and
refresh the pnpmDeps fixed-output hash for the regenerated lockfile.
* fix(web): remember the thinking level per model
Persist kimi-web.thinking as a JSON map of model id to level instead of a
single global value, and resolve the active level against the model's
catalog (stored pick when still declared, else the model default) at
loadModels, setModel, and on active-model changes via a watcher.
Fixes the empty, unresponsive thinking picker shown for a model that does
not declare a previously stored level (e.g. a max-only model with a stale
global 'low').
* fix(web): resolve a submitted prompt's thinking from its own model
submitPromptInternal and the steer path read the single active-session
rawState.thinking, so a queue drain for a background session submitted the
level of whichever session the user had switched to since enqueueing —
the same cross-model leak on the submit path. Thinking now joins model and
the per-session modes in being resolved from the prompt's own session
model (its stored pick when declared, else the catalog default), falling
back to the active value only when the model has left the catalog.
* fix(web): keep model switches from persisting derived thinking defaults
setModel routed the resolved level through applyThinkingLevel, which
writes per-model storage unconditionally — a switch to a model with no
saved pick stored the catalog default as if it were an explicit choice,
pinning the user to it across later default changes, and the rollback
path did the same write for a switch that never happened. Model switches
now update the in-memory level only; storage writes stay with
setThinking, the explicit picker path.
* fix(web): resolve thinking per target session on the BTW and skill paths
sendSideChatPromptOn combined the captured parent's model with the
active-session level, so a session switch during the startBtw await sent
the BTW first turn at the wrong model's effort — resolve it from the
parent's own model, falling back to the active value off-catalog, same
as the other submit paths.
activateSkill carries no thinking either, so the daemon ran skills at
the session profile effort, which can predate the per-model restore the
picker now shows. Persist the resolved level to the session profile
first, mirroring the new-session skill path; that path itself now
resolves against the new session's model instead of the raw active
value.
* fix(web): keep per-model thinking picks in memory as the runtime truth
The resolver re-read localStorage on every submission, letting storage —
not the displayed state — decide what the daemon receives: with storage
unavailable (policy/quota) an explicit pick reached the UI while every
submit path fell back to the catalog default, and a pick made in another
tab silently changed what this tab submits mid-session.
Per-model picks now live in an in-memory map hydrated from localStorage
at startup; explicit picks update it first and persist best-effort
(read-modify-write merge, so concurrent tabs' entries still survive).
localStorage is only hydration plus persistence — another tab's pick can
no longer alter this tab's runtime level.
* fix(web): carry the legacy global thinking pick forward as a fallback
Pre-map installs stored a single global level as a raw string; the map
parser dropped it, silently resetting the user's explicit preference to
the catalog default on upgrade. The legacy value is now carried as a
fallback for models without their own entry — validated against each
model's catalog at resolution, so effort models keep the user's pick
while a max-only model still falls through to its default and can never
be trapped by it.
* fix(web): keep the legacy thinking fallback across the first map rewrite
The first explicit pick after an upgrade rewrote the raw legacy value
into a map containing only that one model, so the next reload saw a
nonempty map and dropped the legacy fallback for every other model.
The migrated value now lives inside the map under a '*' key that no
real model id can collide with: per-model entries override it, and
rewrites persist it alongside them instead of deleting it.
* fix(web): persist only the changed thinking pick on write
Overlaying the whole in-memory map on write could revert a newer pick
made in another tab for a model this tab still held a stale copy of.
Write the changed entry alone (delta-style, like saveUnread), carrying
only the migrated legacy '*' fallback along so it survives the first
rewrite into map format.
* fix(web): abort skill activation when the thinking profile persist fails
persistSessionProfile surfaces failures itself and resolves, so awaiting
it never blocked a following activation: a failed /profile write still
launched the skill at the session's stale effort. It now resolves a
success flag; both activation paths (existing session and new-session
draft) gate on it and skip activating when the persist fails, without
reporting a second, synthetic error.
* refactor(web): persist the new-session skill profile's thinking once
startSessionAndActivateSkill persisted the resolved thinking and then
activateSkill persisted it again unconditionally — a redundant profile
update and status refresh whose transient failure would false-veto an
activation whose prerequisite profile was already applied. Thinking is
now written by activateSkill alone (the single, gated writer); the
draft patch carries only model, plan/swarm and permission.
* fix(web): throw an Error instance for the profile-persist sentinel
oxlint --type-aware (only-throw-error) rejects throwing a Symbol; the
identity-based sentinel works the same as a shared Error instance.
* fix(web): resolve an empty session model through the default before skills
session.model can be '' transiently (daemon profile echo), so
activateSkill fell back to the raw active-view level; in the new-session
flow a concurrent switch could persist another model's effort onto the
target session. Normalize '' through the configured default_model first,
same as the prompt/BTW/steer paths.
* fix(kimi-web): stop the prompt queue from ghost-sending stale attachments
Sending while a turn was running queues prompts locally. A failed flush
left entries stuck, and every later session open silently re-submitted
them with their old file attachments. Gate the drain on locally
witnessed turns, re-drive stuck entries FIFO from real events with a
failure budget, restore merged entries on steer failure, persist the
queue per session so a refresh loses nothing, and converge queues
across tabs via storage events.
* fix(kimi-web): merge cross-tab queue updates by entry id (review P1/P2)
Whole-record adoption could silently discard a prompt another tab
enqueued concurrently. Queue entries now carry a stable id and enqueue
timestamp; adoption union-merges snapshots by id, a shared TTL'd removal
set stops flushed/discarded entries from resurrecting (and being
flushed twice), an in-flight marker covers the submit window, and
manual reorders re-stamp timestamps so they survive merges. The flush
failure budget is also tracked per entry instead of per session, so
removing or reordering the head no longer hands its strikes to the
next entry.
* fix(kimi-web): single-flusher queue entries, merge order convergence, forget guard (review round 2)
Turn-end events reach every open tab and the server accepts concurrent
submissions as distinct prompts, so two tabs holding the same adopted
entry could both submit it. Entries now record their owner tab and only
the owner flushes (ownerless legacy entries flush anywhere); an idle
send adopts entries left behind by closed tabs so a stranded queue can
still drain. Cross-tab merges now keep the newer enqueuedAt copy per id
so a manual reorder converges instead of ping-ponging writes, and the
flush failure callback no longer resurrects a queue whose session was
forgotten while the submit was pending.
* refactor(kimi-web): drop the cross-tab queue persistence, keep the minimal fix
Cross-tab queue sync over localStorage is a distributed-systems problem
(claim/lease, conflict merge, ownership) that keeps generating review
findings far beyond this PR's scope. Remove the persistence/hydration/
adoption machinery wholesale; keep the bug fix proper: gated queue
drain, event-driven FIFO retry with a per-entry failure budget, steer
queue restore, and the forgotten-session flush guard. Durable queued
prompts will be designed together with the server-side prompt queue.
* chore: align the changeset wording with the reduced scope
* fix(kimi-web): never duplicate on ambiguous submit failures; advance after drop
Two review findings: (1) restoring merged queue entries after ANY steer
failure could re-submit prompts the daemon had already accepted when the
failure was a lost response — submits now report ok/rejected/uncertain
and restores + flush re-queues happen only on definitive daemon
rejections, while ambiguous failures drop the entry (the failure toast
still tells the user); (2) dropping an exhausted queue head no longer
strands the entries behind it — the new head is submitted immediately,
carrying its own failure budget.
Models occasionally stream whitespace-only thinking (e.g. a single
space). It starts a thinking draft that renders as a bare bullet line,
both while streaming and when replaying session history. Skip
whitespace-only thinking deltas before they create a draft, and skip
whitespace-only think text at the component funnel so stored
whitespace think parts never render on replay. Stored thinking is
still replayed verbatim to the model.
* feat: refresh model list for API-key providers at the managed endpoint
* docs: simplify changeset wording
* docs: add kimi-code/k3 to the default config example
* fix: clear stale default model when a refresh drops its alias
* fix: clear refresh defaults via replace; set(undefined) cannot delete
* docs: drop the auto model refresh paragraph from providers page
* docs: simplify changeset wording
* feat(tui): add /copy slash command to copy the last assistant message
The command copies the latest assistant reply (text parts only, skipping thinking, tool-call-only turns, error and internal messages) to the clipboard. Clipboard writes now also emit an OSC 52 sequence (tmux-aware) so copying keeps working over SSH and in containers where no native clipboard tool exists.
* fix(tui): gate /copy to idle and report OSC 52-only copies as unverified
During streaming the in-flight assistant text is not in getContext() history yet, so /copy would silently copy an older message; make the command idle-only like /export-md. copyTextToClipboard now returns how the text was delivered so /copy can say when only an unverified OSC 52 escape carried it.
* fix(tui): source /copy text from the visible transcript
After /compact the model context keeps only user messages plus a user-role summary, so scanning it found no assistant message even though the last reply is still on screen. Read the last assistant transcript entry instead — it matches what the user sees and survives compaction and resume.
* fix(tui): mark real model text in the transcript so /copy skips synthetic cards
Hook-result and goal-completion cards are also 'assistant' transcript entries appended after the real reply, so /copy could copy a hook card instead of the answer. Tag the single entry-creation site for genuine model text (both live and replay flow through it) and have /copy only accept tagged entries.
* 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.
* fix(workspace): dedupe workspaces across Windows path spelling variants
The same directory reached the workspace registry as distinct strings on
Windows (drive-letter casing, typed vs on-disk casing, slash style), and
every identity check compared exact strings, so one folder could appear
as multiple workspaces with sessions split across hash-keyed buckets.
- add workspaceRootKey (slash-normalize + case-fold Windows-shaped
paths) in agent-core, agent-core-v2, and the web app, and compare
roots by identity key everywhere instead of exact strings
- registry createOrTouch folds alias spellings onto the existing entry
instead of minting a new workspace id; session buckets reuse the
registered id via a resolver in the v1 session store
- list endpoints expand alias buckets (resolveAliasIds /
resolveAliasWorkDirs, including session-index-only spellings) so
previously split workspaces list all sessions and counts under one
merged group; session_index entries use the registry-resolved id
* fix(workspace): fold the runtime touch path and drive-root identity keys
Two gaps in the Windows path-spelling folding, both reachable in the
v1 session-create flow:
- touchWorkspaceRegistry minted the alias spelling's id outright; the
freshly persisted alias entry then became the resolver's preferred id
on the next create, splitting sessions into a duplicate bucket again.
It now folds onto the identity-matching existing entry, mirroring the
registry service.
- workspaceRootKey stripped trailing separators before testing the
Windows shape, so a drive root (C:\) collapsed to C: and escaped the
case-fold. The shape test now runs before the strip in all three
copies (agent-core, agent-core-v2, web).
* fix(workspace): unfold symmetric operations that escaped the identity key
Two asymmetric spots left the folded comparison one-sided:
- the web app matched hidden roots by folded key but cleared them on
re-add by exact string, so hiding C:\Foo and re-adding c:\foo kept
the workspace hidden forever; clearing now folds too
- registry delete (both engines) removed and tombstoned only the exact
id, so a legacy split sibling resurfaced as the directory's
representative on the next list; delete now removes every registered
spelling sharing the root's identity key and tombstones the full
alias set (registered ids plus session-index spelling mints), so the
session-index merge cannot resurrect the directory either
* fix(vscode): stop mid-turn core errors from corrupting the active turn
* fix(vscode): reject prompts during exclusive operations with a terminal error
* chore(vscode): release 0.6.1
* test(vscode): read the extension version from the manifest in version assertions
* test(vscode): declare version on the runtime rig type
* fix(vscode): count configured provider credentials as signed in and make the gear sign-in actually log in
* fix(vscode): keep the gear auth action scoped to the Kimi account session
* fix: scope Anthropic effort fallback profile to non-Kimi providers
Managed Kimi models routed through the Anthropic protocol (protocol =
"anthropic", no catalog-declared think_efforts) inherited the inferred
latest-Opus effort profile, so the UI showed reasoning effort choices the
server never declared. The fallback now applies only when the provider type
is known, non-kimi, and the effective wire protocol is Anthropic; Kimi
providers keep catalog-declared efforts only, and callers without provider
context fall back to name matching.
* fix: align v2 catalog with resolver for providerless Anthropic models
Flat models (inline base_url, no named provider) and providers without a
declared type now fall back to the model's own protocol when deciding the
Anthropic fallback effort profile, so the model catalog stays consistent
with runtime resolution.
* fix: keep flat Anthropic model effort metadata in TUI and ACP catalogs
Flat models without a named provider (inline base_url, protocol:
"anthropic") have no provider entry to look up; fall back to the
model's own protocol as the provider identity so the effort picker and
ACP catalog stay consistent with runtime resolution.
* style: move v2 anthropic fallback rationale into the modelAuth header
The v2 comment convention keeps comments in the top-of-file block only;
drop the inline explanations added beside functions and statements.
* feat(vscode): migrate extension to Node SDK
* fix(vscode): address CI failures
* fix(vis): handle token count records
* fix(vscode): keep chat toolbar and header readable at narrow widths
* fix(vscode): map yolo to core yolo permission and honor the global yolo setting
* docs(vscode): record Node SDK migration design
* docs(vscode): split breaking changes out of the 0.6.0 changelog
* fix(vscode): keep a resumed session's thinking effort instead of reapplying the default
* fix(vscode): announce session status when a view attaches so the display matches it
* fix(vscode): align webview thinking effort handling with the TUI
* fix(web): align context usage display with 1024-based units and ring-only meter
- simplify the composer context meter to the ring only; the full
used/max/pct numbers live in the tooltip
- format token counts with 1024-based k/M units via a shared formatTokens
helper (256k context reads "256k", not "262k"), applied to the composer
tooltip, status panel, mobile settings sheet, model picker, goal strip,
and turn rendering
- ceil the usage percent so sub-0.5% usage still shows a sliver instead
of an empty meter
* fix(tui): render context usage with 1024-based units and ceil percent
- formatTokenCount is now 1024-based ("256k", not "262.1k"); the footer,
/status and /usage panels, subagent cards, and goal stats all share it,
replacing five local 1000-based copies
- the footer and panel percents use an integer ceil (new usagePercent
helper) so any non-zero usage shows at least 1% instead of "0.0%"
* fix(web): clamp the status panel context percent to [0,100]
ctxUsed can momentarily exceed ctxMax (estimates), which could flash a
"101%" readout — the composer and mobile sheet already clamp the same
ConversationStatus data, so apply the same clamp around the ceiled
percentage here.
* chore: merge the context usage changesets into one
* chore: reword the context usage changeset in English
- kap-server: remove event.model_catalog.changed from the v1 WS union,
broadcaster forwarding, and the zod event registry
- web: refresh all providers (POST /providers:refresh) before loading
models when the model picker opens, replacing the event-driven refresh
- keep domain publishers, the protocol schema, and the web receiver for
compatibility with older daemons
* feat: warn about context-cache loss when switching model or thinking effort
* chore: polish the switch warning copy
* feat: wrap the switch warning instead of truncating it
* chore: bold /new in the switch warning
* Revert "chore: bold /new in the switch warning"
This reverts commit a438e87cda543202a6d4726c96cdabd73b8dcf6f.
* chore: align the changeset with the warning copy
* fix: record crash telemetry for unhandled promise rejections
The crash handler only listened on uncaughtExceptionMonitor, which never
fires for a rejection that has a listener — and the TUI always registers
one, converting rejections into a silent exit(1) with no telemetry at
all. Observe unhandledRejection directly so those crashes still leave a
trace. Since registering a real listener suppresses Node's default
crash-on-rejection, rethrow when we are the only listener (print /
server modes), deduped so the monitor does not double-report.
* fix: fall back to text paste when the clipboard image handler rejects
The Ctrl+V image-paste dispatch chained off the async handler without a
rejection branch, so any failure inside it became an unhandled
rejection — which the CLI's crash path turns into a silent exit(1).
Treat a rejection like "no image available" and paste as text.
* fix: dedupe all rethrown rejection reasons at the crash monitor
Non-Error rejection reasons (plain objects, strings, null) were recorded
by the rejection handler but not added to the dedupe set, so the
uncaughtExceptionMonitor pass after the rethrow reported a second crash
for the same failure; null/undefined reasons also crashed the monitor's
own error-type extraction. Use a Set so primitives dedupe by value, add
every rethrown reason, and classify monitor crashes null-safely.
The TUI crash path (uncaughtException / unhandledRejection) logged the
error into an asynchronously-drained sink and then called process.exit()
on the same tick, so the one line explaining the crash was never written
to disk. Flush the diagnostic logs synchronously before exiting.
Surface the coded provider error the daemon already sends: a semantic
title per error code, the provider's raw message, and expandable
diagnostics (error code, HTTP status, request ID, SDK error name) with
copy support, instead of a bare text-only toast.
* fix(web): dedupe background subagent rows in the agents dock
* fix(web): seed agent identity on late task registration and prefer REST output in task fold
* fix(web): backfill terminal output to folded background subagent rows
* fix(web): sync subagent phase when the REST fold makes a row terminal
* feat(web): allow attaching any file type in chat
- Composer, paste, and drop no longer filter out non-media files;
arbitrary files upload as generic icon chips and are submitted as
file content parts
- kap-server materializes file parts into the session's attachments
dir and replaces them with a path reference, so the model opens the
file with the Read tool on demand instead of receiving inline bytes
- Images rejected by the provider format gate (SVG, AVIF, ...) are now
persisted and referenced by path instead of being dropped with a
notice; uploaded file names are sanitized before hitting disk
* fix(server): stop CSP from blocking web bootstrap script and fonts
- Move the anti-FOUC bootstrap from an inline <script> in index.html
to /boot.js: CSP 'self' never covers inline scripts, while a classic
same-origin script keeps the same render-blocking timing
- Allow data: in font-src — KaTeX and the Inter / JetBrains Mono
Variable fonts ship @font-face data URIs in their distributed CSS
- Set explicit form-action, base-uri, and frame-ancestors, which do
not fall back to default-src
- Add a regression test asserting the served index.html carries no
inline scripts or inline event handlers
* fix(web): normalize empty attachment MIME so extensionless files submit
Files with an empty File.type (Makefile, LICENSE, other extensionless
or unknown types) stored mediaType: '' on the chip, and the submit
fallback used ?? which does not catch empty strings — the wire schema
requires a non-empty media_type, so the prompt was rejected. Normalize
to application/octet-stream at attachment creation, adopt the
server-recorded MIME after upload completes, and make both submit
mappings use || so reloaded chips with '' are covered too.
* feat(web): render all user-turn attachments as chips
* feat(web): attach files by dropping them anywhere in the window
* refactor(web): share one attachment chip between composer and chat bubble
* fix(web): neutral attachment chips, paperclip attach icon, and clickable file chips
* fix(web): drop the extension badge from attachment chips
* fix(web): use the tabler paperclip for the attach button
* fix(web): whitelist attachment previews, reject active document types
Clicking a file chip navigated a new tab to a blob: URL of the uploaded
bytes whenever the type looked browser-renderable. blob: inherits the
web origin, so a text/html or image/svg+xml attachment would execute
same-origin script with the daemon credential (localStorage) and a live
window.opener.
Preview is now restricted to inert types (pdf, non-SVG images, video,
audio, non-HTML text), the blob is re-wrapped with the whitelisted MIME
instead of trusting the recorded content-type, and window.opener is
severed. Non-whitelisted types no longer silently download: the chip
reports 'unsupported' and the pane shows a transient hint.
* fix(web): recover file attachment chips from the server notice
The kap-server prompt route replaces file parts with an "Attached file
…" text notice before enqueueing, so after any snapshot resync the
attachment chip degraded into raw notice text leaking the absolute
server path — unlike image/video uploads, which already recover their
chip from the <video|image path> tag. Parse the notice the same way:
the materialized basename carries the file id, so the chip becomes
clickable again (and editable back into the composer); inline-base64
notices (content-hash named, no file id) still collapse into a
non-clickable chip instead of raw text.
The notice wording is now a client/server contract — flagged on
buildAttachedFileNotice.
* fix(web): preserve UUID file ids when rebuilding attachment chips
* fix(web): skip unresendable file chips when loading attachments for edit
---------
Co-authored-by: qer <wbxl2000@outlook.com>
* fix(web): confirm dialogs respond to Enter and await async actions
The confirm dialog's initial focus was resolved from the Button
component's $el, which is a text node in dev builds (the component has
a template-root comment, so it renders as a fragment). Focus fell back
to the header close button, so Enter cancelled instead of confirming.
Resolve the initial focus with a CSS selector on the confirm button
instead.
ConfirmOptions now accepts an async action: the dialog stays open with
a loading state (cancel/Esc/overlay suppressed) until the work settles.
The archive-session, remove-workspace, and delete-provider confirms
move from the menu components into App.vue so the dialog can await the
actual client call.
* fix(web): block superseding a confirm dialog while its action runs
A second confirm() during an in-flight action would replace the busy
dialog and inherit the global busy state, opening inert until the first
action settled. Resolve the new request unconfirmed instead.
* fix(kimi-web): improve mobile safe-area handling
* fix(kimi-web): restore dock-height fallback where ChatDock is absent
* fix(kimi-web): pin the app shell to the visual viewport height
* fix(kimi-web): pin the app shell to the visual viewport
* chore: add changeset for mobile safe-area fixes
* fix(kap-server): carry the live subagent roster in the session snapshot
* fix(kap-server): clear the subagent roster on the next main turn start
* fix(kap-server): exclude background subagents from the snapshot roster
* fix(kap-server): finalize live roster entries when the main turn aborts
* fix(kap-server): drop roster entries when foreground subagents detach
* fix(web): expand the swarm card by default while subagents are running
* docs(kap-server): qualify the roster-clearing durability claim
* fix(telemetry): deliver session_started in v2 headless mode
session_started/session_load_failed fire inside create()/resume(), but the CloudAppender was installed only after resolveNativeSession() returned, so they were dropped to the null appender.
- run-v2-print: install the appender before resolving the session; reconcile a resumed session's real model via setContext afterwards.
- sessionLifecycle: bind the session id in announceCreated before emitting so session_started carries session_id.
- CloudAppender.setContext: allow updating the model context.
* fix(agent-core-v2): bind session id on session_load_failed
The resume failure path emitted session_load_failed without binding the
session id first, so the event went out with session_id null even though
the service knows which session failed to load. Bind it via setContext
before track2, mirroring the announceCreated path for session_started,
and update the two tests that locked the empty context in as expected.
* feat(kimi-code): keep print-mode goal runs alive until the goal settles
- applyPrintBackgroundPolicy waits for goal continuation turns via a goalActive hook before applying the exit/drain/steer mode, bounded by the wait ceiling
- createPrintTurnEndings skips the timeout when the remaining budget is not finite
- update the changeset to cover the goal-run lifecycle
* fix(kimi-code): wake the print goal wait periodically so settled goals exit promptly
* fix(server): report CLI version as server_version
- kap-server: accept opts.version in startServer, reported as
server_version (/meta, OpenAPI, session exports, lock registry,
default User-Agent); defaults to its own package version
- kimi-code: pass the CLI product version when starting the server
- add boot test covering /api/v1/meta, lock file, and User-Agent
* fix(agent-core-v2): make new sessions resumable by older v1 builds
- add wireRecord.seal() to write the metadata envelope at agent creation,
so fresh logs satisfy v1 replay's first-record-must-be-metadata invariant
- seal the wire log before any op dispatch in AgentLifecycleService.create;
no-op when the log already has records (resume / forked copies)
- seed empty agents/custom maps in session metadata so v1 Session.resume()
can index agents['main'] on a v2-created state.json
- add seal unit tests and lifecycle sealing tests
* fix(kap-server): show real message send times in snapshot history
- read per-record times stamped on wire.jsonl via reduceContextTranscript
and cache them alongside the transcript messages
- prefer each record's real dispatch time for created_at; records without a
stamp fall back to session.createdAt + index, clamped so the page stays
strictly increasing (mirrors MessageLegacyService.list)
- add tests for fallback, clamping, and the page-offset index mapping
* fix(agent-core-v2): backfill missing agents/custom maps in existing session metadata
- load() now heals pre-fix v2 state.json documents that never gained the
agents/custom maps, persisting the backfill so one open on a new build
leaves the session resumable by released v1 builds (Session.resume()
indexes agents['main'] unconditionally)
- updatedAt is deliberately untouched so the format heal does not reorder
session listings
* feat(agent-core-v2): make subagent timeout configurable, default 2h
- add [subagent] config section with timeout_ms and KIMI_SUBAGENT_TIMEOUT_MS env override
- resolve Agent and AgentSwarm per-run timeouts through resolveSubagentTimeoutMs
- update tool descriptions and tests to reflect the 2-hour default
* feat(agent-core-v2): align print-mode background policy with v1
- add printBackgroundMode/printMaxTurns to the task config section with resolvePrintBackgroundMode (keepAliveOnExit fallback)
- apply exit/drain/steer policy to kimi -p on the experimental engine, buffering turn.ended events and failing the run when a steered turn fails
- register the subagent config section from the package entry
* fix(agent-core-v2): strict equality in metadata heal, comments in file headers only
- replace == null with === undefined in the session-metadata heal to
satisfy eqeqeq (oxlint --type-aware in CI)
- move the seal() rationale into the wireRecord contract header and the
agents/custom invariant into the sessionMetadata header per the
tree's header-only comment convention
* fix: preserve provider thinking effort values
* fix: resolve Kimi thinking effort fallbacks
* fix: use resolved Kimi effort in v2 requests
* fix: align Kimi effort resolution paths
* fix: synchronize forced Kimi effort state
* fix: synchronize forced Kimi effort in v2 state
* fix: tolerate unresolved models in v2 status
* fix(web): submit thinking level verbatim and drop the hardcoded default
Align kimi-web's thinking-level handling with the TUI:
- Submit the stored level as-is on every prompt path (prompt, steer,
skill activation, BTW side chat) instead of coercing it onto the
target model's declared efforts.
- No stored preference (undefined) instead of a hardcoded 'high'
default: prompts omit the thinking override and the daemon resolves
the config/model default, same as an unset [thinking] in the TUI.
- Model switcher pre-selects the target model's own default level when
switching models; re-selecting the current model keeps the level.
- Display the effective level (stored value, else the model default)
in the composer, mobile sheet, and /status panel.
* chore(web): remove the dead dev:stub script
The stub daemon (dev/stub-daemon.mjs) no longer exists, so the
dev:stub npm script and its docs references were dead weight.
* fix(web): pin the model default thinking level and persist picks globally
- With no stored preference, loadModels() pins the active model's
catalog default_effort as a concrete in-memory value, so what the
UI shows, what prompts submit, and what the session runs always
agree. localStorage stays reserved for levels the user picked.
- setThinking and model switches now also write the daemon-wide
[thinking] config (same mapping as the TUI's thinkingEffortToConfig),
so sessions created by other clients inherit the pick.