* 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(config): tolerate invalid config.toml sections instead of failing startup
Schema errors now drop only the offending sections (single entries for
providers/models) with a warning, so a typo no longer prevents startup or
drops the login state. TOML syntax errors still fail fast with the parse
location. Mid-run reloads keep the last good config when the file breaks.
Warnings surface via the new getConfigDiagnostics API: as a startup notice
in the TUI, on stderr in print mode, and in the status bar after /new.
Write paths stay strict so a broken file is never silently rewritten, and
now fail with a short actionable message instead of raw validation JSON;
the /provider TUI flow and the kimi provider CLI report these errors
instead of crashing on an unhandled rejection.
* fix(config): keep entry-keyed sections when one entry has multiple issues
A providers/models entry with several validation issues was deleted by the
first issue, and the remaining issues from the same safeParse pass then
escalated to deleting the entire section — one badly-typed custom provider
could drop every provider, including the managed OAuth login. Issues on
entry-keyed sections now only ever target the entry itself; once it is
gone, later issues are no-ops.
* fix: align datasource plugin environment
* refactor: inject managed Kimi env into all stdio plugins
Pin the datasource credential-name test to the canonical
resolveKimiCodeOAuthKey so a digest drift in the standalone plugin
fails CI, and drop the hardcoded plugin-name special case so every
stdio plugin receives the active managed Kimi base URL / OAuth host
consistently (process.env and KIMI_CODE_HOME are already shared with
all plugins).
* feat(plugin): install plugins from a GitHub repository URL
Allow `/plugins install <github-url>` (and marketplace `source` entries) to
take a GitHub repo URL directly. A new `github` source kind joins the
existing `local-path` and `zip-url` kinds.
Recognized URL forms (parsing in source.ts):
- `https://github.com/<o>/<r>` — bare; resolves to latest
release tag, falling back
to default branch HEAD.
- `https://github.com/<o>/<r>/tree/<ref>` — branch / tag / SHA;
value passed to codeload
in its short form so the
backend resolves either.
- `https://github.com/<o>/<r>/releases/tag/<tag>` — explicit tag, uses
refs/tags/<tag> to avoid
same-named-branch ambiguity.
- `https://github.com/<o>/<r>/commit/<sha>` — explicit commit SHA.
The resolver deliberately avoids `api.github.com`: its 60/hour anonymous
quota is shared with every other tool on the egress IP (browser, gh CLI,
IDE integrations) and a first-time install failing because some other tool
ate the budget is unacceptable UX. Instead we:
- GET `github.com/<o>/<r>/releases/latest` with manual redirect and parse
the `Location` header (302 → tag URL; 404 → no own release).
- Fall back to `codeload.github.com/<o>/<r>/zip/HEAD` for repos with no
releases (or for forks that inherit upstream tags but have no own release
page, which redirect to bare `/releases`).
- Only treat the explicit 404 from `/releases/latest` as "no release" — 5xx,
403, 429, and any other non-2xx status surface a hard error rather than
silently installing the default branch, so the user knows when transient
GitHub issues changed the install path.
UI changes in the TUI:
- `/plugins install` now shows a live Braille spinner while resolving and
downloading, then flips to a final status that distinguishes Installed
(fresh) vs Updated (same repo identity, new version) vs Migrated (source
changed, e.g. CDN zip-url → GitHub).
- `/plugins list`, the `/plugins` overview, and `/plugins info` show the
install provenance inline. `zip-url` installs now display the URL host
(e.g. `via code.kimi.com`, `via 127.0.0.1:port`) instead of the opaque
`zip-url` literal. GitHub installs show `github <owner>/<repo>@<ref>`.
- Three-tier trust badge driven by the marketplace context recorded at
install time: `official` (green) for `tier: official`, `curated` (blue) for
`tier: curated`, `third-party` (muted) for anything not installed through
the marketplace selector. CLI `/plugins install <url>` always records as
third-party; the marketplace selector passes the tier through. A
re-install replaces the marketplace context: switching to a third-party
source clears the badge, which matches the underlying trust change.
`installed.json` gains optional `github` and `marketplace` fields
(back-compatible). PluginSummary surfaces `source`, `originalSource`,
`github`, and `marketplace` so the TUI can label installs without an extra
round trip to PluginInfo. The SDK's `session.installPlugin(source)` gains
an optional `{ marketplace }` second argument so the marketplace selector
can forward `{ id, tier }` through RPC; the CLI install path omits it.
Tests: 112 plugin-suite tests (URL parser, resolver, store round-trip,
manager integration). The manager integration tests assert codeload URLs
shape (short form for `/tree/<ref>`, explicit `refs/tags/` for
`/releases/tag/`) and verify marketplace context is persisted across
reloads and cleared on a third-party re-install.
* chore(changeset): plugin install from GitHub
* docs(plugins): document GitHub install URLs and trust badges
* fix(plugin): preserve URL-encoded characters in GitHub ref names
Git permits ref characters that have special meaning in URLs — most
notably `#`, which is a valid tag character (e.g. `release#1`) but the URL
fragment delimiter. The resolver decoded the tag from GitHub's
`/releases/latest` 302 redirect Location header and then interpolated the
raw value into the codeload URL. The literal `#1` became a fragment and
the HTTP request reached the server as `…/refs/tags/release` — a wrong or
truncated ref, leading to install failure for a release whose URL was
otherwise valid.
Two symmetric changes:
- The codeload URL builder now splits the ref on `/` (so multi-segment
refs like `feat/foo` keep their path separators) and percent-encodes
each segment.
- The GitHub URL parser now percent-decodes each segment from the URL's
pathname when extracting `/tree/<ref>`, `/releases/tag/<tag>`, and
`/commit/<sha>`. Storage and display see the human-readable Git ref
name; the resolver re-encodes on the way out.
Malformed `%xx` sequences in user-typed URLs are tolerated: we keep the
raw segment so the caller surfaces a normal "ref not found" error
downstream instead of crashing during parse.
* fix: restrict plugin trust badges
* chore: remove fetch when show plugin list
---------
Co-authored-by: qer <Anna_Knapprfr@mail.com>