* feat: scope thinking effort to the current session
Selecting a model or thinking mode in the TUI or web UI no longer
persists the concrete effort to the config; only the boolean thinking
toggle is saved. The web UI now restores and submits each session's own
daemon-reported level instead of a browser-wide per-model pick, whose
localStorage store is removed.
* feat: persist bounded thinking efforts and migrate persisted max once
Only low/medium/high/xhigh are written to the config on a pick; max and
unrecognized levels stay session-only with just the boolean toggle saved.
A one-shot migration rewrites a previously persisted thinking.effort max
to high (recorded in migrations.json, never re-run). The web UI brings
back the per-model localStorage pick as the seed for new sessions, while
a session's own daemon-reported level keeps winning for existing ones.
* feat: gate effort persistence on the model top declared level
The session-only tier is now the last entry of the model's own
support_efforts (ordered by strength) instead of a fixed name list, so
custom provider-declared levels get the same treatment; anything below
the top persists as before, and unknown model metadata keeps the
concrete effort.
* docs: align changeset wording with the top-tier persistence rule
* refactor: rename the config migration marker file to migrations-effort.json
* feat: drop the legacy global thinking pick fallback in the web UI
A raw single-level string left in localStorage by older versions was
migrated into a '*' fallback entry applied to every model; stale values
(typically max) could silently steer any session. Non-map content is now
discarded instead of inherited.
* feat: drop the web per-model thinking pick store entirely
Thinking in the web UI is now sourced only from the daemon: a session's
own reported level wins when the model declares it, and everything else
falls back to the model's catalog default. The stale localStorage key is
cleaned up on startup.
* feat: skip the effort write when the picker confirms its initial value
Re-confirming the effort shown when the model/effort picker opened is not
an explicit choice: the TUI persists only the model (no effort key, or no
write at all when nothing changed), and the web skips the global config
write the same way it already did for model switches.
* revert: keep the web global thinking write on re-confirm for now
Only the TUI skips the effort write when the picker confirms its initial
value; the web behavior stays unchanged until the interaction is
reconsidered.
* fix: carry the draft thinking pick into a newly created session
A level picked on the empty composer lived only in rawState.thinking, so
selectSession's watcher overwrote it with the catalog default and the
first prompt/skill submitted that instead of the pick. createDraftSession
now captures the draft level and seeds the new session's own entry.
* docs: simplify the changeset wording
* fix: close the remaining top-tier persistence and draft-capture gaps
The provider-add default-model flow now resolves support_efforts through
effectiveModelForHost (overrides + protocol-profile inference), so a
top-tier pick on catalog models without declared efforts no longer
persists. The draft thinking level is captured before the session
creation awaits, so a concurrent session switch can no longer seed the
new session with another session's effort.
* fix: wait for the session status fold before resolving prompt thinking
In the cold window right after a reload or session switch, the session's
own level has not landed from /status yet; resolving straight to the
catalog default would carry the wrong level to the daemon, which writes
it into the session profile. Send, steer, side-chat and skill-activation
paths now await the fold when the session entry is missing.
* 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(cli): run kimi web and the TUI /web command in the foreground by default
kimi web and /web now start the server attached to the terminal (Ctrl+C
stops it) instead of backgrounding a daemon; --background opts back into
the daemon behavior. kimi server run is unchanged. A foreground kimi web
reuses an already-running server instead of failing to bind, the /web
handoff prints the same ready banner as kimi web (plus the session deep
link), and the banner Stop hint adapts to the hosting mode.
* fix(cli): resolve the server token after the server is up in /web
The token lookup had moved ahead of ensureDaemon()/startServerForeground()
during the foreground-default refactor, so a first-time start (no
server.token on disk yet) opened the browser without the #token= fragment
and left the user at the auth gate. Resolve the token after the daemon is
healthy, and inside the foreground ready hook.
* docs(changeset): flag the web foreground default as breaking in the changelog
The bump stays minor, but the entry now calls out the behavior break and
the --background mitigation for scripts that expect kimi web to return
immediately.
* feat(cli): flag a version mismatch when reusing a server from an older CLI
After an upgrade, an older still-running server is reused as-is. The
lock's host_version is now surfaced: kimi web / server run print a
version-mismatch line next to the reuse notice, /web shows the same as a
status warning, and the docs plus changeset tell users a single
kimi server kill switches them onto the new version.
* fix(tui): correct YOLO and Auto permission mode descriptions
* fix: unify YOLO and Auto permission mode descriptions across CLI, ACP, web, and docs
* docs: correct YOLO and Auto mode descriptions in the interaction guide
* fix: correct YOLO mode notices in session replay and vscode extension
* feat(vscode): rename /afk command to /auto, keeping afk as hidden alias
Also correct the stale 'afk' mode reference in the built-in MCP config
skill guidance of both agent engines.
* fix(vscode): forward engine approval requests instead of blanket-approving them
The extension-level approval handler auto-approved every request when
legacy yolo/afk was on, silently swallowing the sensitive-file,
plan-review, and ask-rule prompts the engine yolo mode still sends.
Forward every request to the user and let the engine permission mode do
the auto-approving, matching TUI and web behavior.
* 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