* feat(kap-server): expose the managed-account profile at GET /oauth/userinfo
* refactor(oauth): serve the userinfo profile as the camelCase domain type end to end
* style(agent-core-v2): drop the method-local comment on getManagedUserInfo
* chore: drop the userinfo endpoint changeset
* test(kap-server): pass the required host identity in the userinfo route test
* refactor(oauth): make X-Msh-Platform an explicit host identity field
X-Msh-Platform was hardcoded to kimi_code_cli in createKimiDeviceHeaders,
so non-CLI hosts could not state their own platform and the desktop had
to patch the header after the fact. KimiHostIdentity now carries a
required platform (every host declares its own value; the CLI constant
stays the fallback only for direct createKimiDeviceHeaders callers), and
userAgentProduct is renamed to productName so the transport identity
uses one name everywhere.
All in-repo identity constructions pass platform explicitly; the wire
value for CLI and VS Code hosts is unchanged (kimi_code_cli).
* feat(agent-core-v2): carry the host identity in the bootstrap snapshot
Replace the flat clientVersion field with a required clientIdentity
(KimiHostIdentity) so every consumer reads the same host identity
object: OAuthToolkitService now passes it to the OAuth toolkit, which
means the OAuth device-flow endpoints (device authorization, token
polling, refresh) on the kap-server path finally send the full X-Msh-*
device headers instead of none, and the telemetry cloud appender reads
client_version from the same source. A built-in CLI fallback keeps bare
bootstrap() calls in tests working; composition roots must pass their
own identity.
The session export manifest grows an optional desktopVersion field
(payload plumbed through; filled by kap-server in a follow-up).
* feat(agent-core): thread the host identity into the managed auth facades
The v1 managed auth facade constructed its OAuth toolkit without an
identity, so token refreshes from inside the core went out without any
X-Msh-* device headers. createManagedAuthFacade now takes an optional
KimiHostIdentity and every call site supplies one:
CoreProcessService._defaultOAuthTokenResolver forwards the core
process's options.identity (the same source _defaultKimiRequestHeaders
uses), and the DI-held services (oauth / auth summary / model catalog)
read it from a new optional identity field on IEnvironmentService. The
library-level "no identity, no device headers" contract is unchanged.
* feat(kap-server)!: require the host identity and derive request headers from it
ServerStartOptions.hostIdentity is now a required ServerHostIdentity
(KimiHostIdentity + optional prompt display fields), replacing both the
old optional HostIdentityOverrides (renamed to PromptIdentityOverrides,
its productName field now displayName) and the version option (renamed
to serverVersion — it is the engine version reported as server_version,
while the host product version travels in hostIdentity.version).
The server now feeds bootstrap's clientIdentity from hostIdentity and
derives the default outbound headers (User-Agent + X-Msh-*) from it via
createKimiDefaultHeaders, so kap-server-hosted OAuth flows and model /
WebSearch requests carry the real host identity instead of a hardcoded
kimi-code-cli fallback UA. Explicit header seeds still win as an escape
hatch.
Session export manifests record the host product version: kimiCodeVersion
now carries hostIdentity.version (the engine version no longer appears),
and desktop exports (desktop: true) are additionally stamped with a
desktopVersion field. The instance registry keeps its host_version wire
field for compatibility (kimi-inspect reads it); only the in-memory name
changed to serverVersion.
* feat(cli): wire the CLI host identity into the kimi web server
kimi web now passes createKimiCodeHostIdentity(version) as the server's
hostIdentity, so web-UI OAuth flows and the engine's outbound requests
carry the explicit CLI identity (productName + version + platform). The
explicit hostRequestHeadersSeed is dropped — kap-server derives the same
headers from hostIdentity — and buildKimiDefaultHeaders goes away with
its only consumer.
* test(klient): drop clientVersion from the bootstrap contract parity list
* chore: add changesets for the host identity unification
* feat(cli): tag kimi web requests with a (web) User-Agent suffix
kimi web shares the CLI product token and platform, so its outbound
requests were indistinguishable from direct CLI runs upstream. Its host
identity now carries userAgentSuffix 'web', putting web-UI traffic at
kimi-code-cli/<version> (web) while X-Msh-Platform stays kimi_code_cli.
* fix(klient): keep the env() clientVersion wire field after the bootstrap identity switch
The bootstrap snapshot replaced the flat clientVersion scalar with
clientIdentity, which broke klient's env() fan-out (RPCError: method not
found). The wire surface keeps clientVersion — now sourced from
clientIdentity.version — and bootstrapService gains a clientIdentity
read (registered in envContract with an object schema) for consumers
that want the full identity.
* feat(oauth): send the product User-Agent on OAuth requests
The OAuth endpoints used to receive only the X-Msh-* device headers
(undici's default UA otherwise), which left the OAuth host unable to
distinguish runtime surfaces — notably kimi web, whose platform matches
the CLI and whose only distinguishing mark is the (web) UA suffix. The
toolkit now feeds the full identity headers (User-Agent + X-Msh-*) into
every device authorization, token polling, and refresh request; the
request-header type widens from DeviceHeaders to OAuthRequestHeaders.
* feat(vscode): report kimi_code_vscode as the extension's platform
The VS Code extension inherited the CLI's hardcoded X-Msh-Platform value;
with platform now an explicit identity field it declares its own, so the
managed endpoints and OAuth host can tell extension traffic apart from
CLI runs.
* refactor(agent-core-v2)!: require the client identity at the composition root
The bootstrap fallback identity fabricated a kimi-code-cli/unknown host
for any caller that forgot to pass one — the same silent-misreport
pattern this series set out to remove, and it made "required" a lie.
BootstrapInput.clientIdentity is now required, so a missing identity
fails at compile time instead of being papered over. Test and example
callers pass a shared fixture (klient examples and test engines get one
each); the node-sdk v2 client asserts its host identity with the oauth
helper. Also folds DeviceHeaders from an interface into a type alias so
it stays assignable to the widened OAuthRequestHeaders record.
* feat(oauth)!: require and validate the platform in device headers
Drops the quiet CLI fallback in createKimiDeviceHeaders (the same
silent-misreport pattern removed from the bootstrap identity): platform
is now a required option, validated with the same required-ASCII rule as
the version — empty or all-non-ASCII values throw instead of emitting a
blank X-Msh-Platform, and header-unsafe characters are stripped rather
than sent raw.
* fix(node-sdk): seed the host request headers on the v2 client path
The interactive v2 engine path (experimental flag) bootstrapped without
a hostRequestHeaders seed, so managed vendor calls went out with the
SDK's default User-Agent (OpenAI/JS) and no X-Msh-* at all — v1 passes
the full identity headers on the same requests. The v2 client now seeds
the headers from its asserted host identity, and a test pins the seed.
* chore: simplify the CLI changeset wording
* feat(oauth): return structured managed usage rows
Stop formatting plan-usage labels and reset hints into English strings
at the oauth layer. The parser still absorbs backend field drift, but
now emits a stable structured row (name / window{duration,unit} / used
/ limit / resetAt) that kap-server passes through and clients localize
themselves:
- window: normalized from duration/timeUnit (minute/hour/day/week),
whole-hour minute windows fold to hours, unnamed summaries are the
weekly limit
- resetAt: absolute ISO timestamp; relative reset_in/ttl seconds are
converted at parse time
- TUI /usage panel formats labels and reset hints locally
* refactor(oauth): parse managed usage strictly to the current payload shape
Drop the defensive drift tolerance (alternate reset-time spellings,
reset_in/ttl seconds, remaining-derived used, title/scope names,
top-level duration/timeUnit, fuzzy time-unit matching) and parse only
what the platform actually sends: numeric strings, resetTime, nested
detail/window records, TIME_UNIT_* enums.
* fix(node-sdk): update managed usage smoke example and facade tests for structured rows
* 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
* fix(agent-core-v2): keep invalidated OAuth flows observable during login
invalidateFlows() deleted flows outright when the provider configuration
changed, including terminal ones. That voided the TERMINAL_RETENTION_MS
window and, worse, left the status guards in handleSuccess /
finalizeAuthentication blind when the invalidation landed in the
microtask gap after toolkit.login resolved: setTerminal then wrote
'authenticated' onto an orphaned state the client could never observe,
so the login hung after a successful browser authorization.
Transition affected pending flows to a visible 'cancelled' terminal
status instead, mirroring abortExisting().
Also normalize the KIMI_CODE_BASE_URL env override at the source in
kimiCodeBaseUrl(): provision persists it verbatim while the model
refresh rewrites it normalized, and the deep-equal diff between the two
shapes fired a spurious providers-changed event mid-login.
* style(agent-core-v2): drop non-header comments from the oauth login fix
* chore: cover agent-core-v2 in the oauth login fix changeset
* fix: send the product User-Agent on provider registry and catalog fetches
Registry (api.json) and models.dev catalog fetches only carried the
runtime default User-Agent while every other outbound request sends
kimi-code-cli/<version>. Thread an optional userAgent through
fetchCustomRegistry / fetchCatalog and the shared refresh host, pass
the product UA from the CLI, TUI, and both daemons, and seed a default
product UA in kap-server that hosts can override via opts.seeds.
* refactor: use options for registry fetches
* feat(oauth): parse support_efforts/default_effort in custom registry import
- parse support_efforts / default_effort from api.json model entries
- map them onto model aliases as supportEfforts / defaultEffort
- treat both fields as upstream-owned in CUSTOM_REGISTRY_MODEL_FIELDS so
refreshes sync and stale values are dropped
* feat(oauth): parse boosterWallet extra usage from /usages
* feat(oauth): expose extraUsage on AuthManagedUsageResult
* feat(kimi-code): render Extra Usage section in /usage panel
* fix(kimi-code): address Task 3 review feedback for extra usage section
* feat(kimi-code): render Extra Usage section in /status panel
* feat(kimi-code): wire extraUsage into /usage and /status commands
* chore(extra-usage): address final review findings for fuel pack feature
- Update changeset to cover both kimi-code and kimi-code-sdk packages
- Add parser clamp tests and toolkit null-case test
- Replace 'as never' casts in usage-panel tests
- Wrap long import line in status-panel
* chore: temporarily log /usages raw response for debugging
* fix(oauth): accept BOOSTER balance type and drop debug log
* fix(oauth): drop reset hint from Extra Usage and revert periodEnd parsing
* fix(oauth): treat missing amountLeft as zero extra usage and drop debug log
* revert: keep missing amountLeft defaulting to 0 (fully used)
* feat(extra-usage): show monthly cap usage bar and balance in /usage and /status
* fix(extra-usage): show balance and unlimited marker when no monthly cap
* fix(extra-usage): show monthly used with unlimited marker and balance
* fix(extra-usage): label Used and use English Unlimited
* feat(extra-usage): render balance, monthly used and monthly limit as labeled rows
* fix(extra-usage): move Balance row to the bottom
* fix(extra-usage): format currency values with two decimals for column alignment
* fix(extra-usage): right-align currency values so numbers line up
* fix(extra-usage): align currency symbol and decimal point in usage rows
* feat: add model alias overrides
Preserve user model overrides across provider catalog refreshes and resolve effective model metadata for runtime, TUI, protocol, and ACP consumers.
* fix: apply model display name overrides
Show overridden model display names in the footer, welcome panel, status output, and model switch confirmations.
* fix: pass through kimi effort when undeclared
Keep support_efforts authoritative when declared, but pass requested Kimi thinking effort through when the model does not declare support_efforts.
* fix: honor model overrides in effort commands
Use effective model metadata for /effort choices and for always_thinking clamping when resolving thinking effort.
* feat: support multi-level thinking effort switching
- kimi provider: emit thinking.effort in the new wire format; keep reasoning_effort mirrored during the transition
- model catalog: thread support_efforts / default_effort from oauth through to /models
- config schema: add supportEfforts / defaultEffort on model aliases
- TUI: multi-segment thinking control in /model, new /effort command, footer effort display
- switch status uses displayName and distinguishes model vs effort-only changes
* docs: add thinking effort design plans
- thinking-effort-switching.md: implemented multi-level effort switching
- thinking-model-overhaul.md: follow-up refactor plan for the thinking state model
* docs: collapse thinking overhaul plan into a single PR
* refactor!: overhaul thinking config and effort resolution
Replace default_thinking and thinking.mode with a single [thinking] enabled/effort table. ThinkingEffort is now an open string ('off' | 'on' | model-declared effort); effort levels come from each model's support_efforts instead of a fixed enum.
Centralize default and always_thinking clamp logic in resolveThinkingEffort/defaultThinkingEffortFor, and honor an explicitly configured effort when an always_thinking model is forced back on.
TUI keeps a single thinkingEffort field instead of the boolean + level pair; 'on' is normalized to the model default at the UI boundary.
BREAKING CHANGE: default_thinking and thinking.mode are removed from config; migrate to [thinking] enabled/effort.
* refactor: rename residual thinking level wording to effort
Rename comments, error messages, parameter names, the SetThinkingPayload wire field (level -> effort), and TUI local variables so the thinking effort naming is consistent throughout. No behavior change.
* refactor: rename remaining camelCase thinking level identifiers to effort
Rename liveLevel/prevLevel/levelChanged/commitLevel/effectiveLevel to liveEffort/prevEffort/effortChanged/commitEffort/effectiveEffort in the TUI model picker and config commands.
* refactor: eliminate remaining thinking level wording in comments and tests
Rename levelLabel -> effortLabel, EffortSelectorOptions.levels -> efforts, and 'effort level(s)' / 'default level' / 'requested level' wording in comments, error messages, slash-command description, and test titles to effort. Also restore the withThinking(effort) parameter rename in the Kimi provider that was accidentally reverted.
* fix: address codex review feedback on thinking effort handling
- OpenAI thinkingEffortToReasoningEffort and Anthropic clampEffort now normalize 'on' / unrecognized efforts instead of throwing, so boolean non-Kimi models no longer crash on session start.
- ACP resolveCurrentThinkingEnabled treats a non-empty thinking.effort as enabled, matching agent-core's resolveThinkingEffort.
- REST promptThinkingSchema accepts any non-empty effort string so model-declared efforts are not rejected at the API boundary.
* test: align kimi e2e expectations with supportEfforts-gated reasoning_effort
The kimi provider now sends reasoning_effort only when the model declares support_efforts; boolean models (no support_efforts) send only thinking.type. Update the kimi e2e tests to drop the stale reasoning_effort expectation for the boolean test model.
* test: cover [thinking] effort parsing in config.test
Add effort = "high" to the documented [thinking] table in the config parse test and assert config.thinking.effort is resolved, so the new [thinking] effort field has direct parse coverage.
* docs: add thinking test coverage gap analysis
Capture the explore agent's test coverage review for the thinking overhaul PR, including P1/P2 gaps and the two open design questions, for follow-up test additions.
* feat(oauth): parse nested think_efforts from /models response
The /models endpoint now returns effort levels under a nested think_efforts object ({ support, valid_efforts, default_effort }). Parse it preferentially in both managed-kimi-code and open-platform model parsing, falling back to the legacy flat support_efforts / default_effort fields for older servers.
* refactor(oauth): only read nested think_efforts; gate on support=true
Drop the legacy flat support_efforts / default_effort fallback. The think_efforts object is now the single source, and its support flag gates the whole object — when support is not true, valid_efforts and default_effort are ignored entirely.
* chore: remove unused parseStringArray import in open-platform
* docs: finalize thinking effort release notes
Downgrade the changeset to minor with an English summary, drop the version-specific 'added in 1.0.0' info block, and present the deprecated config fields as a table (field / deprecated in 0.21.0 / description).
* refactor: drop temporary refresh toggles and kimi reasoning_effort mirror
Remove the always-true REFRESH_MODELS_ON_PICKER_OPEN / REFRESH_PROVIDER_MODELS_ON_STARTUP toggles and their stale re-enable TODOs, and stop sending reasoning_effort from the kimi provider (thinking.effort is the only wire field now).
* fix(tui): avoid persisting "on" as thinking effort
* fix: preserve persisted thinking effort across login and provider setup
* fix(tui): show actual thinking effort in /status and footer
* test(tui): align message-flow expectations with effort persistence and /status display
* fix(vis): rename thinkingLevel to thinkingEffort in config.update analysis
* feat(server): auto-refresh provider models and push change events
- add scheduled provider-model refresh in the daemon (configurable
interval + refresh-on-start) plus manual endpoints:
POST /providers:refresh and POST /providers/{id}:refresh
- publish global event.model_catalog.changed when a refresh changes
the catalog so connected clients can resync
- extract the refresh orchestrator into @moonshot-ai/kimi-code-oauth so
the CLI and server share managed/open-platform/custom-registry logic
- wire the web daemon client to the new refresh endpoints
* chore: add changeset for provider model auto-refresh
* fix(web): reload model and provider caches on catalog change events
When the daemon's scheduled refresh changes the catalog, the pushed
event.model_catalog.changed only advanced the websocket sequence, leaving
the web composer's model/provider refs stale until an unrelated reload.
Reload both caches when the event arrives.
* test(sdk): cover event.model_catalog.changed in event exhaustiveness
* feat(managed-kimi-code): route anthropic protocol via beta api
- kosong: add betaApi option to use client.beta.messages.create
- agent-core: thread alias betaApi into the anthropic provider config
- oauth: route managed models on the anthropic protocol through the beta Messages API
* feat(providers): add KIMI_CODE_CUSTOM_HEADERS support
- Add KIMI_CODE_CUSTOM_HEADERS env var for custom outbound LLM headers
- Send User-Agent to non-Kimi providers
- Forward Kimi identity headers to model catalog fetches
- Support defaultHeaders in Google GenAI provider
* feat(agent-core): add protocol attrs to turn and api error telemetry
- Add type/protocol/alias to api_error for per-protocol error attribution
- Add turn_ended event with reason/duration/mode/type/protocol
- Add type/protocol to turn_interrupted
* chore(oauth): remove hardcoded internal dev endpoint from shared OAuth base URLs
---------
Co-authored-by: haozhe.yang <yanghaozhe@moonshot.ai>
* fix(agent-core): recover from context overflow 413
- track provider-observed effective context limit after overflow
- compact with the reduced limit before retrying the turn
- treat large plain 413 responses as recoverable context overflow
- add CLI patch changeset
* feat(managed-kimi-code): support Anthropic-compatible protocol
- switch managed provider to anthropic when models declare anthropic protocol
- add base64 video content blocks to the kosong anthropic provider
- downgrade unsupported media parts to text placeholders by capability
- pass prompt cache key as Anthropic metadata.user_id for session affinity
* feat(agent-core): add protocol/type to request and video upload telemetry
- turn_started now carries `type` (configured provider wire type) and
`protocol` (effective transport, i.e. alias.protocol ?? provider.type)
- new video_upload event reports mime type, size, latency and
success/failure, plus type/protocol/model context
- ResolvedRuntimeProvider gains `type` and `protocol` fields
* feat(feedback): support attaching logs and codebase
Add an attachment picker to /feedback (none / logs / logs + codebase).
Codebase uploads scan the working directory with sensitive files excluded
and are sent through a new multipart upload API on the oauth/node-sdk layers.
* fix(feedback): fall back to logs when codebase scan fails
* tiny fix
* fix(feedback): make diagnostic uploads partial-safe
* refactor(feedback): reuse harness session export and normalize upload url types
* docs(slash-commands): note optional feedback attachments
* refactor(feedback): reorganize feedback upload modules
Move the attachment orchestration out of tui/commands/info.ts into a
dedicated feedback/feedback-attachments.ts, and split the former
codebase-upload/attach.ts into a generic multipart uploader
(feedback/upload.ts) and an archive lifecycle module
(feedback/archive.ts). Both session and codebase archives now flow
through a single upload lifecycle, which also removes the temp-dir
leak that occurred when codebase packaging failed.
Rename FeedbackCodebaseArchive to FeedbackArchive and the
codebase-upload/ directory to codebase/ so module boundaries match
their actual responsibilities (scan + package only).
* ci: run unit tests on windows
* fix(migration-legacy): align workdir bucket key with agent-core
computeWorkdirBucket used a local node:path-based resolve that yields backslash-separated paths on Windows, while agent-core's encodeWorkDirKey uses pathe (forward slashes on every platform). The SHA-256 inputs diverged, so migrated sessions were written to a bucket that the session picker never reads, making them invisible on Windows.
Alias computeWorkdirBucket to encodeWorkDirKey so both sides stay byte-identical, drop the local slugify copy, and update the workdir-bucket test reference accordingly.
* test(acp-adapter): expect platform-native separators in e2e-fs path
The e2e-fs test asserted the fs/readTextFile wire path as the raw POSIX targetPath, but AcpKaos.toClientPath converts '/' to '\' when the inner LocalKaos reports pathClass 'win32' (Windows). On Windows the wire path became '\Users\test\x.ts' and the assertion failed.
Mirror toClientPath in the test: expect backslash separators on win32 and the raw path otherwise. Implementation is unchanged.
* test(sdk): normalize workDir and skillDir paths in session tests
SessionStore.create/list and the skill loader normalize paths through pathe (forward slashes). The SDK tests compared the resulting workDir and skill loaded-dir against raw mkdtemp / node:path strings, which use backslashes on Windows (and node:fs realpath also returns backslashes for the skill dir), failing three toMatchObject assertions.
Build the expected paths with agent-core's normalizeWorkDir so they match the internal pathe representation on every platform. The skill dir keeps its realpath() (the loader realpaths the root) and only normalizes separators.
* test(skill): normalize realpath to forward slashes in scanner tests
resolveSkillRoots normalizes every root.path through fs.realpath followed by replacing backslashes with forward slashes (scanner.ts). The scanner tests compared root.path against node:fs realpath directly, which returns backslashes on Windows, so twenty assertions failed (toEqual / toContain / toHaveLength) even though the resolved paths were identical.
Wrap realpath at the top of the test file to mirror the implementation's normalization, so every comparison uses the same forward-slash form on every platform.
* test: skip Unix-only permission tests on Windows
The Unix file-permission assertions (mode bits like 0o600 / 0o700 and chmod 000 making a path unreadable) have no equivalent on Windows, which uses ACLs; fs.chmod there can only toggle the read-only bit. These six tests failed on Windows with mismatched mode values or a missing 40411.
Skip them on win32 via it.skipIf(process.platform === 'win32'): oauth FileTokenStorage (0600 file, 0700 dir), agent-core BackgroundTaskPersistence (0700 tasks dir), agent-core createPerIdJsonStore (0700 subdir), migration-legacy atomicWrite (0600 file), and server fs:browse (chmod 000 -> 40411).
* test(tui): make platform-sensitive assertions cross-platform
The TUI implementations are already platform-aware (pathe-style paths, pathToFileURL, quoteShellArg cmd/POSIX quoting, Alt+V on Windows for paste expansion), but the tests hard-coded POSIX expectations and failed on Windows.
Align the assertions with the implementation's platform behavior: footer-goal-badge matches the '[goal' badge prefix instead of /goal/ (toolbar tips contain '/goal'); tool-call expects backslash relative paths on win32; plan-box builds the file:// URL via pathToFileURL; custom-editor sends Alt+V on win32 for paste expansion; file-mention-provider normalizes the expected description to forward slashes; kimi-tui-startup builds the resume command with quoteShellArg; kimi-tui-message-flow builds the expected install path with resolve().
* test: align path assertions with pathe on Windows
Several test suites asserted paths produced by node:path/node:os/node:fs against values that agent-core, node-sdk and kaos normalize through pathe (forward slashes). On Windows the two forms diverge (backslashes vs forward slashes), failing about 19 assertions.
Mirror the implementation's normalization in the assertions via a local toPosix helper (or agent-core's normalizeWorkDir), so expected paths use forward slashes on every platform: kaos LocalKaos, node-sdk export/list/resume/config/transport sessions, cli FileMentionProvider, and agent-core skill-session.
* test(native): build path expectations with node:path.resolve
paths.mjs builds every path with node:path.resolve, which yields backslash-separated absolute paths on Windows. The path-helpers tests asserted against template strings that mixed the backslash appRoot with forward-slash segments, so Object.is failed on Windows even though the strings looked identical.
Build the expectations with the same resolve(appRoot, ...) helper so the separators match on every platform.
* fix: make Windows CI tests pass across all packages
Fix the remaining Windows CI failures so the Windows test job can go green. The changes fall into a few categories:
- Path separators: agent-core/node-sdk/kaos normalize paths via pathe (forward slashes); align test expectations and a couple of implementations (native cache base, workspace registry) with that.
- Platform-only services: skip launchd/systemd manager suites on win32 (Windows uses schtasks).
- Process/signal lifecycle: skip or relax tests that rely on POSIX signals / SIGTERM semantics that Windows does not support.
- Hook shell syntax: rewrite hook test commands from POSIX shell (single quotes, semicolons, stderr redirects, if/then/fi) to node -e / .cjs files that run under cmd.exe.
- CRLF: make Bash tool description stripping tolerate CRLF line endings.
- Misc: realpath short-name divergence, port-retry timing, telemetry spawn, fs-watch timing, snapshot path normalization, etc.
* fix: remove unused basename import in workspaceRegistryService
Fix lint error (no-unused-vars): basename from node:path is no longer used after switching to posixBasename from pathe.
* fix: align resume harness pathClass and wait for banner state on Windows
Two more Windows CI fixes:
- createResumeNoSideEffectKaos now reports pathClass 'win32' on Windows so tool descriptions (e.g. Glob's Windows note) match the live agent in expectResumeMatches, fixing usage/description deep-equal drift.
- kimi-tui-startup once-banner test now waits for writeBannerDisplayState to land before asserting, since the atomic write can lag behind the render on Windows.
* fix: resolve remaining Windows unit test failures
Make the new Windows CI job green across agent-core, kaos, node-sdk and server:
- Align the resume harness kaos pathClass with the live agent so platform-conditional tool descriptions (Glob's Windows note) match in expectResumeMatches instead of drifting on win32.
- Rewrite hook commands in agent-core tests as cross-platform node one-liners; single-quote echo, >&2 and ';' do not work under cmd.exe.
- Add .gitattributes enforcing LF so raw-imported templates (e.g. the compaction instruction) produce byte-identical token counts on Windows and POSIX.
- Terminate the full process tree on Windows in both the hook runner and kaos (taskkill /T /F) so grandchildren cannot outlive their parent and keep the cwd locked.
- Normalize workDir path separators in two kimi-sdk session tests to match the stored canonical form.
- Avoid cmd.exe arg-quoting pitfalls in the kaos cmd.exe test, and run the Windows process-tree kill test from a script file with the pid path passed via argv.
- Give the first fs-git e2e test more time on Windows and retry the temp-dir cleanup; skip the fs-watch overflow-burst assertion on Windows where fs-event coalescing prevents the single-window spike.
* ci: retrigger checks
* fix: resolve remaining Windows failures after merging main
- Terminate the spawned git/gh process tree on Windows in FsGitService (taskkill /T /F on timeout) so a timed-out 'gh pr view' cannot leave a grandchild holding the workspace cwd, which made the fs-git e2e cleanup fail with EPERM.
- Give the fs:git_status e2e suite a longer timeout on Windows and retry the temp-dir cleanup longer to ride out the slower child-process teardown.
- Make the third-party plugin install trust test assert the resolved install path via node:path so it matches the Windows-resolved path (D:\tmp\...) as well as the POSIX one.
* fix: align workspace registry roots and harden fs-git cleanup on Windows
- workspace-registry test: compare normalized (forward-slash) roots, since the registry and session index both store workDir via pathe.resolve (forward slashes on every platform). realpath() yields backslashes on Windows and diverged from the stored root.
- fs-git e2e: bump the temp-dir cleanup retries and the afterEach timeout, since Windows child-process teardown after server.close() is asynchronous and can keep the workspace cwd locked for several seconds.
* test: stub openUrl in kimi-tui-message-flow feedback tests
The /feedback command falls back to openUrl(FEEDBACK_ISSUE_URL) when submission fails, which spawned a real browser window on every test run. Mock #/utils/open-url (matching the existing login/message-replay/server test convention) so the suite never opens a browser.
* test: harden fs-git e2e cleanup against Windows cwd locks
On Windows, git/gh child processes and the session core process can outlive server.close() and keep the temp workspace as their cwd, so rmSync fails with EPERM even after a long retry. Add rmSyncRobust that retries and, if the cwd is still locked, swallows EPERM/EBUSY on Windows — the OS reclaims the temp dir and a cleanup hiccup must not fail an otherwise-passing test.
* test: harden server e2e cleanup against async teardown races
server.close() does not fully await the server's asynchronous teardown, so on a loaded CI runner the temp home/workspace dirs can still be held or written to when the afterEach rmSync runs, failing with EPERM (Windows) or ENOTEMPTY (Linux). Use a rmSyncRobust helper (retry + swallow EPERM/EBUSY/ENOTEMPTY) in the fs-git and question e2e cleanup. Also fix a leftover `throw err` (renamed to `throw error`) that broke the typecheck.
Map OAuth token-fetch failures to distinct public error codes instead of collapsing them all to auth.login_required:
- missing/revoked tokens or 401/403 from the refresh endpoint -> auth.login_required
- transport failures and 429/5xx after internal retries -> provider.connection_error
- anything else is rethrown as-is (surfaces as internal) rather than guessed
The refresh helper already retries internally, so the agent loop does not
re-retry these. Both the managed provider and the standalone SDK provider
share the same mapping.
* feat(update): roll out automatic updates in staged batches via CDN manifest
* chore: remove changeset
* refactor(update): single-source the CDN latest file names
* refactor(update): reuse the CDN latest URL constants in update checks
* refactor(update): drop the test-only CDN base override
* fix: preserve first launch attribution
* fix: use refreshed rollout manifest for telemetry
* fix: abort hung cdn update checks
* fix: preserve update cache with bad manifest
* chore: remove review report from branch
* feat(providers): sync custom registry providers on startup refresh
- group registry providers by URL and retry available API keys\n- automatically add new providers and remove disappeared ones\n- coalesce duplicate source URLs to avoid false config-change reports\n- clear defaultThinking when default model is removed\n- update docs and add tests for registry sync scenarios
* fix(tui): only show provider refresh status for added models
Skip removed / metadata-only provider updates when reporting model list changes.\n\n add: test to enforce the behavior.
Map the /models three-state supports_thinking_type field ('only' /
'no' / 'both', taking precedence over the legacy supports_reasoning
boolean) onto the existing always_thinking capability:
- oauth: parse the field in both /models parsers; 'only' emits
always_thinking alongside thinking, 'no' suppresses thinking even
when supports_reasoning is set, absent falls back to the legacy
boolean. Default thinking selection is forced on for 'only' (and
off for 'no') models during login and provider refresh
- TUI: render the thinking control with a fixed On/Off layout — locked
models show a greyed-out "Off (Unsupported)" segment, and
non-thinking models mirror the style with "On (Unsupported)"
- agent-core: clamp thinkingLevel at the getter so a stale
thinking-off config can never reach the request builder, status
events, or subagent inheritance
- acp-adapter: derive alwaysThinking from capabilities, collapse the
thinking select to a single locked "on" entry, and ignore off
requests for locked models while re-emitting the snapshot
- Add `applyCustomRegistryEntries` helper to apply all entries in-memory before a single write
- Update CLI Add Platform flow to use it, replacing interleaved in-memory mutations with removeProvider RPCs
- Add regression tests for repeated imports and provider field refreshing
* feat(tui): add /provider command, custom registry import, and tabbed model selector
- add "/provider" slash command for managing AI providers with CRUD UI
- add custom registry import via api.json URL and Bearer token
- introduce tabbed model selector grouped by provider
- add fetchCustomRegistry and applyCustomRegistryProvider in oauth package
- replace deprecated "/connect" command with unified "/provider" flow
- update provider and slash-command documentation
* feat(tui): background provider model refresh at startup
- add `refreshAllProviderModels` utility supporting managed OAuth, open platforms and custom registries
- wire background refresh into `KimiTUI` startup via `AuthFlowController`
- add `providerSwitchHint` option to `ModelSelector` and enable it in `TabbedModelSelector`
- update `TabbedModelSelector` hint wording from "switch" to "provider"