This commit scaffolds the @moonshot-ai/acp-adapter package and introduces
the full ACP (Agent Communication Protocol) server implementation for
Kimi Code CLI, including:
- Scaffold @moonshot-ai/acp-adapter workspace package with build skeleton
- `kimi acp` CLI subcommand and stdout-safe logging
- ACP version negotiation and AgentSideConnection wrapper
- Auth gate for session creation
- Session lifecycle: new, list, load with history replay
- Prompt content conversion (text, image, embedded resources, resource links)
- Assistant streaming with thinking support and end-turn handling
- Tool call streaming (started, delta, progress) with result conversion (text / diff)
- Approval handling with diff/text display blocks mapped to ACP options
- Kaos read/write interface (AcpKaos) for unsaved buffer access
- Session mode (yolo/auto) and model management
- Config options builder with thinking toggle
- MCP server forwarding from ACP to harness
- Agent plan updates and available commands updates
- AskUserQuestion bridged to session/request_permission
- Plan review options surfaced through requestPermission
- Error mapping, ext_method stubs, and graceful shutdown
- IDE integration guide (Zed + JetBrains)
- End-to-end tests against ACP TS SDK client
* feat(cli): unify TUI dialog interaction and visuals
Align every list dialog and selector to a single spec: shared selection
pointer and current-item marker, single-border header with a (type to
search) title suffix, consistent search line, and a uniform keyboard-hint
vocabulary. Replace ad-hoc exit wording (close/back/exit/dismiss) with
cancel, hardcoded pointers with the shared constant, and ▲▼ with ↑↓.
Also add fuzzy search to /provider, restyle the /model provider tabs, and
require a token with multi-field navigation in the custom-registry import.
Introduce the write-tui skill (carrying the DESIGN.md spec) and refresh
the apps/kimi-code development guide to match the current TUI architecture.
* feat(cli): drop arrow-key navigation in the plugins selector
The plugins overview and its marketplace/MCP sub-views used Left/Right to
enter and exit details. Remove that hierarchy navigation: Enter opens a
detail, Esc returns, and the arrow keys no longer jump between levels.
Update the sub-view hints from `←/Esc cancel` to `Esc cancel`.
* feat(cli): install marketplace plugins on Enter only
The marketplace install action also fired on Space, which collides with
the Space-toggle convention used elsewhere. Bind install to Enter only and
update the hint to `Enter install/update`.
* feat(cli): reword the plugin inline change hint
The inline badge shown on a changed plugin row read `pending /new`, which
was cryptic. Reword it to `require run /new to apply`. Also switch the
marketplace-install message-flow tests to Enter, matching the Enter-only
install binding.
* fix(cli): stop the editor flash when toggling a plugin
Each plugin picker's onSelect unconditionally restored the editor before
the handler re-mounted the refreshed picker, so an in-place action like
Space-toggling a plugin flashed: picker → editor → picker. Drop the
pre-restore from the overview/marketplace/MCP onSelect callbacks and let
each handler branch mount its next view; the two branches that close to the
editor (show-list, info) restore it themselves.
* feat(cli): drop /provider search and delete with D
Remove the fuzzy filter from the provider manager: no query state, search
line, or type-to-search title suffix, and Esc closes directly. With no
type-to-search to clash with, bind delete to the D key (matching /plugins)
instead of Del/Ctrl+D. Update the write-tui spec to specify D as the
delete shortcut.
* fix(cli): default thinking-capable models to thinking on
The model selector seeded a single thinking draft from the global thinking
flag, so highlighting a thinking-capable model showed Off whenever the
active model had thinking off (e.g. a non-thinking current model). Compute
the draft per model instead: the active model keeps its live state, any
other thinking-capable model defaults to On, and a ←/→ toggle is remembered
per model.
Run the external editor through Node's platform shell instead of spawning
/bin/sh directly. Quote the appended temp file path per platform so Ctrl+G
works on Windows while preserving shell-style editor commands.
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
* feat(agent-core): add dead-end detection for repeated tool calls
- inject stronger dead-end reminder at streak 10 and force-stop turn at streak 15
- emit `tool_call_repeat` telemetry events for streak >= 2
- pass telemetry client from TurnFlow to ToolCallDeduplicator
- add comprehensive tests for dead-end stop and telemetry behavior
* fix(agent-core): tune tool-dedup thresholds and preserve isError state
- Lower repeat-reminder thresholds to 3, 5, 8 and force-stop to 12
- Force-stop no longer overrides successful tool results to isError
- Move stopTurn field into ExecutableToolSuccessResult for explicit loop-control without requiring isError
- 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
* fix(glob): normalize pattern before brace expansion
* fix(glob): preserve backslash-escaped metacharacters and normalize braces correctly
- expand braces before normalizing to prevent `..` from collapsing across brace groups
- skip normalization for patterns containing backslash-escaped glob metacharacters
- add test coverage for escaped braces and nested `..` inside alternatives
* feat(cli): add `kimi provider` subcommand
Add a non-interactive equivalent of the TUI `/provider` command:
- `kimi provider add <url> --api-key <key>` imports every provider in a
custom api.json registry, persisting `source` so the next TUI launch
refreshes the model list automatically.
- `kimi provider remove <id>` deletes a provider and its model aliases.
- `kimi provider list [--json]` prints configured providers with model
counts and source labels.
- `kimi provider catalog list [providerId] [--filter] [--url] [--json]`
browses the public models.dev catalog.
- `kimi provider catalog add <providerId> --api-key <key> [--default-model]`
imports a known provider straight from the catalog.
All actions reuse `fetchCustomRegistry`, `applyCustomRegistryProvider`,
`fetchCatalog`, and `applyCatalogProvider` from the existing oauth/SDK
helpers.
* fix(cli): satisfy oxlint rules in provider subcommand
- Use `Array#toSorted()` instead of `Array#sort()` to avoid mutating
arrays returned from `Object.keys()` / `Object.entries()`.
- Drop redundant boolean-literal comparisons on `model.capability.*`
fields (already typed as `boolean | undefined`).
- Remove the unnecessary `source as Record<string, unknown>` assertion
in `providerSourceLabel` — `ProviderConfig.source` is already typed
that way in the schema.
- Drop the empty-object fallback in `{ ...(config.models ?? {}) }`
inside the test harness.
* fix(cli): address review findings on provider subcommand
P1 — `provider add`: `harness.removeProvider` re-reads the config from
disk (see `agent-core/src/rpc/core-impl.ts removeKimiProvider`), so
calling it mid-loop discarded providers we had already applied in
memory but not yet persisted. Importing a registry that added a new
provider then replaced an existing one silently lost the new one.
Drop every stale id up front in a single batch, then apply each entry
against the resulting fresh config.
P2 — `catalog add`: `applyCatalogProvider` always writes
`defaultThinking`. Hardcoding `false` would silently disable thinking
for thinking-capable models when the user had it on. Thread the prior
`defaultThinking` through.
P2 — `catalog add`: `removeProvider` clears `defaultModel` when it
pointed at one of the provider's aliases, so capturing
`previousDefaultModel` AFTER the removal yielded `undefined`. Capture
both `defaultModel` and `defaultThinking` BEFORE the removal so
re-importing a configured provider (e.g. to rotate the api key)
preserves the user's chosen default.
Tests:
- `makeHarness` now models the on-disk semantics of `removeProvider`
(clears `defaultModel` when an alias matches, returns fresh disk
view), so behavior that depended on the buggy in-memory mock is
exercised honestly.
- Three new regression tests, each verified to fail against the
pre-fix handler.
* fix(cli): address follow-up review on catalog default semantics
Two more findings on `catalog add`:
P2 — `default_thinking` fallback to `false` was wrong even after the
previous fix. `resolveThinkingLevel` (agent-core/.../thinking.ts:23)
treats `defaultThinking === false` as an explicit "off" request and
silently disables thinking before per-model defaults kick in. A
first-time `kimi provider catalog add anthropic --default-model
claude-opus-4-7` was therefore still persisting `default_thinking =
false` for thinking-capable models. The handler now always restores
the previous `defaultThinking` (including `undefined`) — the only
way to let the runtime resolver pick the per-model default.
P2 — Restoring `default_model` was unconditional, even when the
refreshed catalog no longer ships that model. `applyCatalogProvider`
drops the old aliases and only populates the current catalog, so
restoring an alias the catalog no longer contains would point
`default_model` at a non-existent entry and break the next session.
The handler now checks whether the alias still resolves and clears
it otherwise.
Test harness:
- The fake `setConfig` now mirrors the real `mergeConfigPatch`
semantics (deep-merge with `undefined` keys skipped), so tests can
honestly assert that `setConfig({defaultModel: undefined})` does
NOT wipe a key from disk — only `removeProvider` can.
Two new regression tests, each verified to fail against the pre-fix
handler.
---------
Co-authored-by: 7Sageer <158020838+7Sageer@users.noreply.github.com>