Fixes#8116.
A chat conversation could be killed permanently by one early failure: the
daemon pinned a backend session id from a run that died during setup, and
every later message then failed resuming an id the CLI had never persisted.
Fixing the model config did not help — the run died at session/resume, before
any model handling — so the only escape was abandoning the conversation and
losing its context.
Two independent defects had to line up, and both are fixed:
- Eight backends (qoder, kiro, grok, hermes, kimi, traecli, reasonix, dim)
published a fresh session's id when setup failed before any prompt. The
existing clear was gated on opts.ResumeSessionID != "", so it only ever
covered resumes. hermes, grok and dim additionally pinned the id to the task
row before set_model ran, where FailAgentTask's COALESCE made it
unretractable; that pin now goes out once setup has succeeded.
- Six backends (qoder, hermes, kimi via session/resume; kiro, traecli, grok via
session/load) returned a bare failed Result from the resume RPC, so
shouldRetryWithFreshSession read the absent flag as "checked, not a
rejection" and never retried. They now share classifyACPResumeFailure, which
splits cancellation and timeout out first — a user who cancels mid-resume
must not be reported as a rejected session.
That second defect is why the blast radius is wider than the report: those six
never consulted the classifier on the resume path at all, so any permanent
resume failure wedged the conversation, not just a ghost pointer.
isACPResumeRejected widens the vocabulary at the resume boundary only, where
the RPC's single job is turning an id into a live session. It stays positive
evidence rather than treating every resume error as a rejection: session/resume
carries mcpServers, so an unreachable MCP server, a handshake crash or an
expired credential fail there too and none is cured by starting over. It
carries no error-code gate, deliberately — the reported qodercli frame is
-32602, but that rests on the reporter's capture and gating on it would make
the fix miss its own bug if a runtime picked a code outside the set.
No server-side change. The daemon's in-turn retry already retires the pointer
through retiredSessionID, so existing wedged conversations recover on their
first message after the daemon upgrade, with no backfill.
Behaviour change: a fresh setup-stage failure no longer reports a session id,
so three set_model tests and dim's config-failure resume test asserted the old
contract and were updated. dim's coverage of fail-closed config re-application
on resume is preserved, driven from a run that completed a prompt.
Reviewed by Steve Jobs over three rounds; the predicate was tightened twice as
a result — an over-wide adjacency window that matched auth errors, and a
stop-list for request-shaped complaints.
Built-in Hermes declares no mcpCapabilities in its ACP initialize response yet accepts both http and sse McpServer entries on session/new, so applying the ACP v1 default there discarded every remote MCP server users configured — visible only in the daemon log. Carve a narrow exception for genuine silence on the provider's own verified binary, keeping fail-closed for malformed responses, custom runtime profiles, and the seven unverified ACP backends.
Also warn when mcp_config carries servers under a runtime-native top-level key instead of mcpServers, which previously forwarded nothing with no log at all, and document how mcp_config reaches ACP runtimes.
Closes#6540
* feat(agent): Qoder ACP runtime, chat reconnect recovery, and task linkage
- Add Qoder CLI backend (ACP transport, model discovery, blocked-args policy)
- Wire daemon/runtime config, docs, and UI provider assets
- Retry terminal task reports; add backoff unit tests
- Chat: SQL attach user message to task; handler + optimistic cache reconcile
- Invalidate chat/task-messages caches on WS reconnect; extract helper + tests
Co-authored-by: Orca <[email protected]>
Co-authored-by: Cursor <[email protected]>
* chore: drop non-Qoder changes (chat reconnect, task link, terminal report retries)
Keep only Qoder runtime, docs, daemon config/execenv, and UI provider assets.
Co-authored-by: Orca <[email protected]>
Co-authored-by: Cursor <[email protected]>
* fix(agent): harden Qoder ACP drain and wire project skills path
- Stop streaming to msgCh after reader wait so grace timeout cannot race close
- Resolve injected skills to .qoder/skills per Qoder CLI discovery
- Update AGENTS.md skill copy and add execenv tests
Co-authored-by: Orca <[email protected]>
Co-authored-by: Cursor <[email protected]>
* feat(qoder): add provider logo and wire MCP config into ACP sessions
- Add inline SVG QoderLogo component to provider-logo.tsx, replacing
the generic Monitor icon placeholder
- Add convertMcpConfigForACP helper to convert Claude-style MCP server
config (object map) into ACP array format for session/new and
session/resume
- Add unit tests for convertMcpConfigForACP covering stdio, SSE,
empty/nil, and multi-server cases
Co-authored-by: Orca <[email protected]>
* fix(test): capture both return values from InjectRuntimeConfig in Qoder test
Co-authored-by: Orca <[email protected]>
* fix(qoder): preserve remote MCP headers and promote provider errors
Addresses review feedback on #2461 (Bohan-J): two runtime-correctness
issues in the Qoder ACP backend.
1. Remote MCP headers were dropped. The bespoke convertMcpConfigForACP
only forwarded url/type, so an authenticated remote MCP server looked
configured in Multica but failed inside the Qoder session. Replace it
with the shared buildACPMcpServers helper (same path Hermes/Kimi/Kiro
use), which preserves headers as [{name, value}], sorts for
deterministic output, and handles remote transport aliases. Fail
closed on malformed mcp_config instead of silently dropping servers.
2. Provider failures could report as completed tasks. stderr was wired
via io.MultiWriter and the result was only promoted to failed when
output was empty, so a terminal upstream error (HTTP 429 / expired
token) racing a stopReason=end_turn with text still became
"completed". Switch to StderrPipe + an explicit copier, drain it
(bounded by the existing grace window, since qodercli can leave a
child holding the inherited fds) before the decision, and run the
shared promoteACPResultOnProviderError.
Tests: replace the convertMcpConfigForACP unit tests with two
end-to-end Qoder tests — one asserts the Authorization header reaches
the session/new payload as {name, value}, the other asserts a terminal
stderr error with non-empty output reports failed.
Co-authored-by: Orca <[email protected]>
* fix(qoder): align ACP session handling
Co-authored-by: Orca <[email protected]>
* fix(agent): guard qoder late output after drain
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Orca <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>