Skip to content

feat(chat): Part B(1) backend — session/prompt pipe over the live /acp socket - #48

Merged
brettchien merged 2 commits into
mainfrom
feat/chat-part-b-backend
Aug 14, 2026
Merged

feat(chat): Part B(1) backend — session/prompt pipe over the live /acp socket#48
brettchien merged 2 commits into
mainfrom
feat/chat-part-b-backend

Conversation

@brettchien

Copy link
Copy Markdown
Contributor

First implementation slice of ADR agent-chat-panel (#47). Adds the bidirectional chat pipe on the existing reverse-MCP session. No UI yet (Part C) and no cross-reconnect session/resume yet (Part B2).

Changes

crates/acp-tunnel (unit-tested, 23 pass locally):

  • Session::prompt(text)session/prompt {sessionId, prompt:[{type:text,text}]}
  • Session::cancel() → the one-way session/cancel notification
  • Inbound::AgentChunk classified from session/update / agent_message_chunk; other update kinds (thoughts, tool_call) stay Other — openab doesn't emit them yet (per Jellyfish's live eval)

src-tauri/src/remote.rs:

  • RemoteState grows an mpsc write-handle (prompt_tx), published only once the session is active, retracted by run_reconnecting/disconnect — so the UI can never prompt a half-open session
  • run_once's read loop becomes a tokio::select! over inbound frames and outbound OutMsg (Prompt/Cancel); the WS write sink never leaves the task
  • id-correlation: a method-less frame in SessionActive ends the turn only if its id matches the in-flight prompt (pending_prompt), never by phase — emits agent-update {kind:"turn_end", stopReason}
  • agent_message_chunkagent-update {kind:"chunk", text} (the drop site is now the chat hook)

src-tauri/src/lib.rs:

  • agent_prompt / agent_cancel commands, registered in invoke_handler!

Scope / what's next

  • B2: cross-reconnect session/resume + persistence store (needs the Session to live across reconnects; the acp-tunnel resume() builder + {sessionId,cwd,mcpServers} re-declaration are already in place and Jellyfish-verified).
  • Part C: the vanilla-TS chat panel (spinner → single-shot render → markdown; stop/retry; history).

Verification

  • cargo test -p acp-tunnel — 23 pass (incl. prompt_needs_a_session_then_builds_a_text_turn, cancel_is_an_idless_notification…, agent_message_chunk_is_classified…).
  • studio-desktop compilation left to CI (desktop.yml) — separate GUI workspace, not built on the ECS host (OOM).

Reference: brettchien/katashiro.

🤖 Generated with Claude Code

brettchien and others added 2 commits August 14, 2026 16:51
…p socket

First implementation slice of ADR agent-chat-panel (Part C). Adds the
bidirectional chat pipe on the existing reverse-MCP session; no UI yet
(Part C) and no cross-reconnect session/resume yet (Part B2).

acp-tunnel:
- Session::prompt(text) — builds session/prompt {sessionId, prompt:[text]}
- Session::cancel() — the one-way session/cancel notification
- Inbound::AgentChunk classified from session/update/agent_message_chunk
  (other update kinds — thoughts, tool_call — openab doesn't emit yet)
- unit tests for all three (23 pass)

remote.rs:
- RemoteState grows an mpsc write-handle (prompt_tx), published only once
  the session is active, retracted by run_reconnecting/disconnect
- run_once's read loop becomes a tokio::select! over inbound frames AND
  outbound OutMsg (Prompt/Cancel); write never leaves the task
- prompt results are id-correlated (pending_prompt), not phase-driven —
  a method-less frame in SessionActive ends the turn only if its id
  matches the in-flight prompt; emits agent-update {kind:"turn_end"}
- agent_message_chunk → emit agent-update {kind:"chunk", text}

lib.rs:
- agent_prompt / agent_cancel commands (delegate to Remote), registered

Verified: acp-tunnel unit tests. studio-desktop compilation left to CI
(separate GUI workspace). Follow-ups: B2 session/resume + persistence,
then Part C the panel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…relay, clearer errors (review #1–4)

Addresses the four findings from the Part B(1) backend review:

1〔med〕 In-flight guard for `session/prompt`. A second prompt while a turn
   was pending unconditionally overwrote `pending_prompt`, orphaning the first
   turn's result id so its `turn_end` never fired and the panel spinner hung.
   The single-shot loop now rejects (and logs) a prompt while one is in flight
   instead of clobbering the pending id. Part C's turn management still gates
   this, but the backend no longer depends on the UI for correctness.

2〔low〕 Non-blocking sidecar relay. `handle_inner` (`tools/list`/`tools/call`)
   awaited inline in the select! read arm, stalling queued prompts/cancels and
   chat chunks behind a slow round-trip. The relay now runs in a spawned task
   and returns its reply through a new `reply_tx` channel drained by the loop's
   single writer — responsiveness restored, wire frames still serialized. Each
   reply carries its own id, so out-of-order completion is fine for MCP.

3〔low〕 Clearer error when the outbound channel's receiver is gone. A send
   into a torn-down connection reported a bare "connection closed"; it now
   matches the not-connected message so the teardown window reads correctly.

4〔info〕 out_rx/reply_rx arms use Some(..) patterns, disabling the arm on a
   closed channel so a None can never busy-spin the loop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@brettchien
brettchien merged commit b10b631 into main Aug 14, 2026
2 checks passed
@brettchien
brettchien deleted the feat/chat-part-b-backend branch August 14, 2026 10:35
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