- Move custom provider management into Settings as a Providers tab
(ProvidersPanel + AddProviderFlow + ProviderForm + providerForm lib),
replacing the standalone ProviderManager overlay; add/update/delete,
models.dev directory import, custom registry import, API key
show/hide, inline error banners
- Onboarding login step gains a custom-provider card landing on
Settings → Providers
- Fork web branding back to the legacy little-blue mark (sidebar,
onboarding BrandLogo, favicon); build-brand-icons.mjs now only
writes desktop assets
- Drop the dev backend switcher pill from the sidebar (switch backends
via KIMI_SERVER_URL instead)
- Rebind web new-chat shortcut to Ctrl+Shift+O (browsers reserve
Cmd/Ctrl+N for new window)
- Source __KIMI_CLIENT_VERSION__ from the kimi-code submodule CLI
version instead of apps/web/package.json
- Warning toasts honor notice severity (info/success/error variants)
- Remove the stale ToolRow.vue copy from the desktop renderer snapshot
- Minor internal tidy-ups in transcript/detail-panel helpers
* feat(chat): preview files outside the workspace via host fs:content
File preview is a local read-only action, so out-of-workspace absolute
paths (chat/Markdown links, tool-call paths, turn-summary files) now read
through the daemon's global fs:content instead of being refused with the
outsideWorkspace error. The allowHostRead opt-in is removed entirely;
workspace-level trust gating will land as a separate feature.
* fix(chat): harden out-of-workspace preview reads
- Lexically normalize absolute preview paths so an in-cwd path with ".."
(src/../a.ts) keeps the session fs:read path (download / reveal / line
count) instead of falling into the host-read branch.
- Preserve the POSIX root when resolving relative links/images inside an
out-of-workspace Markdown preview (/tmp/notes/a.md + ./b.md no longer
resolves to the workspace-relative tmp/notes/b.md).
- Cap readHostFileContent at 10 MiB (FileTooLargeError): fs:content has no
truncation semantics and the body is decoded in full in the renderer, so
an unbounded read could hang or OOM the app. The preview maps it to a
dedicated too-large error state.
* fix(chat): refuse oversized host reads early and load external markdown images
- DaemonHttpClient.getBlob gains a maxBytes option that rejects at the
Content-Length header (cancelling the body stream) instead of after the
whole body is downloaded; readHostFileContent passes its 10 MiB cap
through it, keeping the post-read check as the missing-header fallback.
- resolveImageUrl now reads out-of-workspace absolute image paths via the
host fs:content (base64 data URL), so relative images inside an external
Markdown preview — and model-referenced /tmp images in chat — render
instead of falling back to a same-origin URL that can never load.
* fix(chat): handle Windows absolute paths in image and markdown-link resolution
- resolveImageUrl classifies any local absolute path (POSIX, Windows drive,
UNC) instead of POSIX-only: in-cwd ones relativize via pathRelativeTo for
the session fs:read (in-workspace Windows absolute images render too now),
outside ones read via the host fs:content.
- The Markdown preview's link/image resolvers pass Windows-drive and UNC
absolute targets through unchanged instead of拼接ing them onto the current
file's directory (C:/tmp/b.md no longer becomes C:/notes/C:/tmp/b.md).
* chore: add release notes for v0.0.14
* fix(sidebar): backfill session list after archiving a session
* fix(sidebar): correct cursor re-anchor and discard stale session pages
* fix(sidebar): cap backfill pages and capture archive state pre-POST
* fix(sidebar): retry stale session pages from the re-anchored cursor
* docs: reword archive backfill changeset
* style(sidebar): trim verbose session-backfill comments
* feat(desktop): move the internal-test badge next to the sidebar user name
* fix(desktop): give the user-menu name an explicit shrink floor for the badge
* feat(desktop): add secondary model settings with a linked model-effort picker
* feat(desktop): rename the subagent settings copy to 子智能体
* feat(desktop): drop the tool-name parenthetical from the subagent model hint
* docs: add the OAuth desktop-handoff integration note for the auth service
* feat(sidebar): rework archiving with an inline action and undo toast
- Session rows drop the kebab: hover shows pin + archive; the full menu
stays on right-click. Archiving skips the confirm dialog and raises the
new top-center ActionToast (web-ui) with Undo (restore) and Settings
(deep link to the archived tab / sheet).
- Remove the "last edited" workspace sort mode: archiving an anchor
session collapsed the workspace's recency and reshuffled the sidebar.
Workspaces now always follow the manual (dragged) order.
* docs(changeset): reword the change notes
* fix(account): show upgrade entries for signed-in free managed accounts
A signed-in free managed account (no active membership) can call neither
/models (402) nor /usages + /oauth/userinfo (403/402), so the app used to
mislabel it: the composer offered a sign-in entry, the send gate demanded
login, and Settings -> Account rendered the raw usage 403.
Derive a membership tri-state from the userinfo probe (ok -> member,
402 -> free, anything else -> unknown) and key every surface off it:
- Composer model-pill slot: upgrade entry for free accounts, empty for
other signed-in states, sign-in only when actually signed out.
- Send gate: free accounts get an upgrade prompt instead of the sign-in
dialog.
- Settings -> Account: a PlanUpgradeCard replaces the plan-usage module
for free accounts (the usage card also swaps itself on a 402/403 while
the probe is still in flight).
- UserMenu: free accounts see the upgrade entry and no plan-usage row.
- Empty-session landing: an upgrade banner above the composer, styled
after the design-system Banner(info) recipe.
The upgrade entries share lib/upgrade.ts (per-app 'from' param via
isDesktop) opening the kimi.com code page.
* fix(providers): show an explicit empty state when a provider has no models
A managed provider with zero models rendered a single disabled blank row
whose placeholder hints (gpt-4.1 / 1047576) read like real values. Render
a muted "no models" line instead, and update the add-model placeholders
to kimi-k3 / 1048576.
* fix(login): unify the login dialog copy around "sign in"
The dialog mixed "authorize" and "sign in" wording; use "sign in"
(登录) consistently in both locales, including the polling error hint.
* chore: merge the changesets into a single entry
* fix(account): await the membership probe before the send gate picks a dialog
checkAuth fires the /oauth/userinfo probe fire-and-forget, so a signed-in
free account submitting inside the probe window still snapshotted a null
membership at the send gate and got the sign-in dialog. Extract the
guarded fetch into fetchManagedProfile (same generation/logout guards),
expose an awaitable probeManagedMembership, and let the gate await it
when the account is authenticated but the membership is still unknown.
* feat(desktop): show the signed-in user's profile in settings
* feat(desktop): add the sidebar user menu
* fix(desktop): prevent stale userinfo fetches from overwriting the profile
* fix(desktop): make the user-menu flyouts keyboard accessible
* fix(desktop): address review follow-ups for the sidebar user menu
* fix(desktop): gate the mobile sheet's Escape and use dialog semantics for the usage flyout
* style(desktop): trim the rationale comments in the user menu
* fix(desktop): redact profile fields in debug and renderer logs
* fix(desktop): assign the userinfo fetch generation at checkAuth entry
* chore: bump kimi-code submodule
* chore: link zod for the bumped oauth package
* fix(composer): show a type-to-search hint for a bare @ mention
A bare @ fired an fs:search with an empty query, which the server always
scores zero, so the menu permanently showed "No matches". Skip the
request for empty queries, open the menu immediately, and show a
type-to-search hint; "No matches" is kept for real queries with no hits.
* fix(composer): unblock Enter in empty mention states, drop stale results
Review feedback:
- Enter/Tab/arrows are no longer consumed while the mention menu has no
items (bare-@ hint / no-match state); only Escape is handled there
- in-flight search responses that no longer match the live query are
ignored, and the pending debounce is cleared when the @token is gone
- trim the root-cause comments per repo guidance
* fix(composer): keep the mention menu closed for a bare @
Simpler take on the empty-query state after preview: no popup at all
until the query is non-empty, so the hint state and its i18n strings go
away. The stale-response guard now re-derives the live @token instead of
tracking the query in state.
* feat(desktop): add kimi-code:// deep link to surface the app after OAuth login
The OAuth device-flow completion page can now link to
kimi-code://auth/success to bring the desktop window to the front once
the user finishes authorizing in the browser. No credentials ride the
URL: the daemon's device-flow poll completes the login on its own and
the renderer converges through its existing daemon polling, so the only
deep-link action is showing the main window.
- main/deep-link.ts: scheme constant, a strict URL whitelist (any
webpage can fire the scheme), dev-only setAsDefaultProtocolClient,
and the showMainWindow handler
- main/app.ts: macOS open-url listener registered before ready, queued
until launch routing is up (same pattern as second-instance argv);
Windows/Linux deliver the URL via argv and are covered by the existing
second-instance / cold-start paths
- electron-builder.config.cjs: top-level protocols so packaged builds
register the scheme (macOS Info.plist, Windows registry, Linux
x-scheme-handler in the desktop file)
* fix(desktop): gate second-instance deep links through the URL whitelist
Windows/Linux deliver deep links as plain argv, and the second-instance
handler surfaced the window unconditionally — any webpage could fire
kimi-code://anything to steal focus. Route those argv through
handleDeepLink so only whitelisted URLs focus the window; plain
relaunches (no URL in argv) keep the existing always-show behavior,
including launches queued before the window is ready.
Also redact rejected deep-link URLs with redactUrlForLog before they
reach the persistent log, so a malformed auth link cannot persist codes
or tokens from its query or fragment.
* feat(desktop): unify the host identity and mark OAuth logins as desktop-originated
Resolve the desktop host identity once (main/identity.ts) and pass it to
the embedded server through kap-server's new typed hostIdentity option:
the OAuth device flow, model, and WebSearch requests now all carry the
desktop's User-Agent + X-Msh-* set (kimi_code_desktop) with headers
derived downstream — the manual X-Msh-Platform override, the header-seed
plumbing, and the device-id scraping are gone. The engine version keeps
traveling as serverVersion.
first_launch now means the desktop product's first launch: a marker in
ui-state.json instead of the shared device_id file, whose existence only
proves some kimi-code product ran before (the CLI could silently swallow
the desktop's first launch). No migration for existing installs.
The OAuth verification URL opened from the login dialog and onboarding
wizard gains from=kimi_code_desktop, so the authorization page can offer
an "open the desktop app" handoff only for desktop-originated flows;
CLI and web URLs stay bare.
Design doc: docs/plans/2026-07-29-host-identity-unification.md. Requires
the kimi-code host-identity unification (submodule bump follows its
merge).
* chore: bump kimi-code submodule to the host-identity branch
Point the submodule at feat/host-identity-unification HEAD (f2a293c02)
so the desktop builds against the new kap-server hostIdentity API.
Update to the main merge commit once MoonshotAI/kimi-code#2382 lands.
* docs(desktop): trim the host-identity native-todos entry to the divergence essentials
* docs(desktop): drop the native-todos entries for deep link and host identity
* docs: drop the review-tool mention from the host-identity plan
* chore: bump kimi-code submodule to the merged host-identity commit
Point the submodule at the main merge commit of the host-identity
unification (MoonshotAI/kimi-code#2382), replacing the temporary pointer
at the feature branch head.
* chore: sync the lockfile with long-declared workspace deps
kap-server's minidb and node-sdk's agent-core-v2/klient links were
declared in package.json by earlier PRs but never written into the
lockfile; pnpm install registers them now.
* chore: drop the desktop host-identity changeset
* feat(desktop): add kimi-code:// deep link to surface the app after OAuth login
The OAuth device-flow completion page can now link to
kimi-code://auth/success to bring the desktop window to the front once
the user finishes authorizing in the browser. No credentials ride the
URL: the daemon's device-flow poll completes the login on its own and
the renderer converges through its existing daemon polling, so the only
deep-link action is showing the main window.
- main/deep-link.ts: scheme constant, a strict URL whitelist (any
webpage can fire the scheme), dev-only setAsDefaultProtocolClient,
and the showMainWindow handler
- main/app.ts: macOS open-url listener registered before ready, queued
until launch routing is up (same pattern as second-instance argv);
Windows/Linux deliver the URL via argv and are covered by the existing
second-instance / cold-start paths
- electron-builder.config.cjs: top-level protocols so packaged builds
register the scheme (macOS Info.plist, Windows registry, Linux
x-scheme-handler in the desktop file)
* fix(desktop): gate second-instance deep links through the URL whitelist
Windows/Linux deliver deep links as plain argv, and the second-instance
handler surfaced the window unconditionally — any webpage could fire
kimi-code://anything to steal focus. Route those argv through
handleDeepLink so only whitelisted URLs focus the window; plain
relaunches (no URL in argv) keep the existing always-show behavior,
including launches queued before the window is ready.
Also redact rejected deep-link URLs with redactUrlForLog before they
reach the persistent log, so a malformed auth link cannot persist codes
or tokens from its query or fragment.
* docs(desktop): trim the deep-link native-todos entry and note the custom protocol in the README
* docs: move the custom protocol note to the root README and drop the native-todos entry
* fix(desktop): match the deep-link scheme case-insensitively
Schemes are case-insensitive per RFC 3986, so an uppercased scheme like
KIMI-CODE://unknown/path slipped past extractDeepLink's startsWith check
and took the unconditional plain-relaunch focus path, bypassing the URL
whitelist. extractDeepLink now lowercases before comparing, and
isKnownDeepLink parses the URL — comparing scheme and host
case-insensitively while keeping the path case-sensitive — instead of a
fragile exact-string match.
* fix(desktop): self-register the deep-link scheme on Windows
electron-builder 26's protocols config only lands in the macOS
Info.plist, the Linux desktop file, and AppX manifests — NsisTarget
contains no protocol handling at all, so the NSIS installer never writes
the HKCU\Software\Classes\kimi-code registry entry and browsers fall
back to a web search for the scheme. The app now registers the scheme
itself on every Windows launch (packaged via the exe path, dev via
electron + app path); macOS keeps the Info.plist and Linux the deb
desktop file, where the runtime call stays dev-only.
* feat(sidebar): unify workspace session expand into one control
Replace the two separate controls at the bottom of each workspace group
("Load N more" fetching the next page, and a local expand/collapse
toggle for already-loaded rows) with a single expand/collapse pair:
- "Show more" steps a per-group display cap up by one batch of 5; the
next page is fetched only when the locally loaded rows can't cover
the step, so users can't tell whether a reveal came from memory or
the server. While fetching, the button shows "Loading…" disabled.
- "Show less" resets the cap to the first page without losing the
already-loaded data (re-expanding never refetches).
- Both controls share one row as compact content-width pills with
chevron icons (down/up) aligned to the session-title x, separated by
a faint middot; labels no longer show counts (the workspace
session_count is only a possibly-stale label).
Applied to the desktop sidebar, the mobile switcher sheet, and synced
to apps/web; design-system docs and i18n updated accordingly.
* chore: simplify changeset wording
* fix(sidebar): unstick adjacent template attrs, truncate show-more pills at min width
Two template attributes ended up concatenated without whitespace after
an edit (harmless to the Vue parser, but a typo); separate them. Give
.show-more min-width: 0 so at the narrowest sidebar (170px) the pill
labels truncate with the existing ellipsis instead of the row
overflowing the workspace group.
* fix: reconcile session work state after reconnect
* fix: order work reconciliation after replay
* fix: harden session work reconciliation
* fix: reconcile session work state by sequence
* fix: retain session activity ordering
* feat(desktop): inherit shell KIMI_* env with an explicit blacklist
The shell env probe used to drop every KIMI_* variable. Most of them are
legit power-user config the desktop should inherit: KIMI_CODE_HOME,
endpoint overrides for test environments (KIMI_CODE_OAUTH_HOST /
KIMI_OAUTH_HOST / KIMI_CODE_BASE_URL), experimental flags, log tuning.
The blacklist keeps out what would silently override the desktop's own
auth/settings or weaken the embedded server (it runs without a bearer
token, so CORS and Host checks are its only defenses):
- secrets and request-header injection (*_API_KEY, KIMI_CODE_PASSWORD,
KIMI_CODE_CUSTOM_HEADERS)
- endpoint overrides outside the managed-account flow (KIMI_BASE_URL,
KIMI_WEB_SEARCH_BASE_URL, KIMI_WEB_FETCH_BASE_URL)
- KIMI_MODEL_* (env-synthesized provider hijacks the default model)
- embedded-server defenses (CORS origins, allowed hosts, host check,
OAuth lock)
- desktop dev switches (KIMI_SERVER_URL, KIMI_RENDERER_DEV_URL,
KIMI_DESKTOP_NO_SHELL_ENV) and internal parent-to-child plumbing
Two follow-on fixes:
- when the probe imports KIMI_CODE_HOME, re-target the main log file to
the new home so server-side session export keeps finding it
- await the probe before reading the external-server token, which
resolves <KIMI_CODE_HOME>/server.token
Also drop the probe's own KIMI_PROBE_BIN from the dump merge.
* docs(desktop): trim shell-env policy comment and sync README
Review feedback: the blacklist rationale lives in the feature commit
message, so the code keeps only short labels; sync the desktop README
with the new contract (KIMI_* inherited minus blacklist, main log
re-targets when the probe imports KIMI_CODE_HOME, and the probe is
awaited before the external-server token read).
* fix(chat): revive the turn file-change summary's dead file rows
The summary card's file rows rendered as inert plain-text spans in the
main conversation, so they had no hover feedback and ignored every click:
- An optional boolean prop without a declared default casts ABSENT to
false, so "interactive unless explicitly false" silently resolved to
false wherever the flag wasn't passed (ChatPane → TurnFilesSummary).
Both boundaries now declare the default true explicitly; only the BTW
side chat keeps the plain-text rows, by design.
With the rows alive again, two latent defects on the never-exercised
interactive path surfaced and are fixed here:
- The turn-diff panel could not be toggled closed by tapping the same
row again: openTurnDiff compares the stored change by identity, but a
deep ref reads back a reactive proxy that never matches the raw
object. turnDiffChange is now a shallowRef.
- AgentDetailPanel's embedded ChatPane never forwarded open-turn-diff,
so an Edit row there would have been a dead button; the event now
flows through to the shared detail layer.
Plus two style corrections on the card:
- The row hover underline rode the full-strength text colour; it now
uses the faint text token, matching the spec's "lightly".
- The foot "N more files" toggle no longer inherits the shared Button's
press-scale, which read as the whole strip denting inward on a
full-width square-cornered row.
Tests: SSR-render assertions pin the interactive defaults and the
button-vs-span rows (turn-files-summary.test.ts), and composable tests
cover the second-tap toggle of both the turn-diff and file-preview
panels (detail-panel-toggle.test.ts) — mirrored desktop/web.
* refactor(chat): trim invariant comments, keep web tests pure-logic
Review feedback on the turn file-change summary fix:
- The shallowRef / prop-default notes carried bug-narrative detail that
repo rules keep out of source comments; each is now a one-line
invariant hint (root causes live in the earlier commit message).
- apps/web tests are pure logic only (apps/web/AGENTS.md), so the web
copies no longer render components: turn-files-summary pins just the
compiled prop defaults (rendered button-vs-span coverage stays in the
desktop renderer tests), and detail-panel-toggle calls the composables
directly instead of mounting a host. Same coverage, no renderer.
* fix(chat): show a real not-found state for moved or deleted preview files
Clicking a file the turn touched after it was renamed or deleted showed the generic unable-to-read message — the daemon precise fs.path_not_found (40409) was swallowed into null by readFileContent, collapsing every failure into one misleading message. readFileContent now rethrows the not-found code (its two loadFileDiff call sites keep their null-on-error behavior via .catch), and openFilePreview maps it to the dedicated not-found state that only the host-read branch used before.
* chore(chat): drop the css rationale note, trim the not-found changeset
Review feedback: the press-scale override carries its reason in the commit message rather than a source comment, and the changeset stops at the fixed problem per the changeset skill (no behavior supplement).
* chore(chat): drop the remaining prop-default rationale notes
Review feedback: the one-line boolean-cast note is still regression rationale; the defaults are guarded by the turn-files-summary tests, so source stays bare per the comment-restraint rule.
* chore(chat): drop regression narrative from the new tests
Review feedback: test names and assertions already carry the regression intent; setup-constraint notes (module mocks) stay, bug root causes live in the commit history.
* fix(chat): concatenate stream-chunk text parts on history reload
Third-party OpenAI-compatible providers can persist one text part per
stream chunk. The history projection joined every part with '\n', so a
reloaded long reply rendered as vertical text (MoonshotAI/kimi-code#2323).
Adjacent same-kind parts within one message are chunks of a single
segment — concatenate them verbatim; keep the newline only across
messages or an intervening tool call.
* fix(chat): dedupe chunked persisted copies of a streamed message
Sign message content for seenSigs after merging adjacent same-kind
parts, so a provider that persists one part per stream chunk still
matches the assembled live copy sharing the promptId instead of
rendering the reply twice. Also trim the new absorbContent comment to
the local invariant per the repo comment policy.
The standalone renderer-track-validation.ts module only existed to keep
the bootstrap path (index -> log -> track) free of the zod import.
ipc.ts is late-loaded via import('./app'), so inlining the 5-line
validator there preserves that constraint with one less module. The
whitelist matrix tests tied to the split module are removed; the area
is stable and the schema itself is enforced by the shared contract.
A Skill invocation persists the loaded skill body as a user-role message
(origin skill_activation, trigger model-tool / nested-skill) between the
tool call and its result. messagesToTurns treated it as a hard turn
boundary: it flushed the pending assistant group, then hid the message.
The turn split in two and each half rendered its own "worked Ns" fold
row, with the Skill card stranded between them.
Upstream's isAgentReplayUserTurnRecord already counts only user-slash
activations as turn anchors; mirror that here by skipping non-user-slash
skill activations the same way hidden injections are skipped, so the Skill
call folds into the same activity run as the rest of the turn.