Skip to content

feat: chat workspace mode integrated with the-controller-daemon#541

Merged
kwannoel merged 29 commits into
mainfrom
session-1-07ce2e
Apr 22, 2026
Merged

feat: chat workspace mode integrated with the-controller-daemon#541
kwannoel merged 29 commits into
mainfrom
session-1-07ce2e

Conversation

@kwannoel

Copy link
Copy Markdown
Owner

Summary

  • Adds a new chat workspace mode (hotkey Space c) alongside development, agents, and kanban
  • Integrates the Tauri app with the-controller-daemon (the HTTP+WebSocket daemon at ~/projects/the-controller-daemon/) for structured, typed chat sessions against Claude Code and Codex
  • Chat UI: sidebar session list (grouped by project), new-chat dialog (agent + initial prompt), transcript with live streaming deltas, inline collapsed tool calls, inline Approve/Deny tool approvals, disabled input on ended sessions, daemon-unreachable empty state with retry

Design and plan

  • Design: docs/plans/2026-04-22-chat-mode-design.md
  • Plan: docs/plans/2026-04-22-chat-mode-plan.md

What's inside

Daemon client library (src/lib/daemon/):

  • Typed wire schema mirroring the daemon's model.rs
  • DaemonClient — HTTP wrapper with bearer auth, typed endpoints
  • WS stream with exponential-backoff reconnect and resume-from-seq
  • Pure reduceTranscript (delta accumulation, dedupe, status tracking)
  • Svelte 5 reactive store using SvelteMap for reactive Map updates
  • classifyError helper mapping HTTP status codes to UX kinds (401/404/409/422/503/network/unknown)

Rust:

  • New Tauri command read_daemon_token (reads ~/.the-controller/daemon.token, honors $TCD_STATE_DIR)

Chat UI (src/lib/chat/):

  • ChatWorkspace, DaemonEmptyState
  • ChatSessionList (grouped by project.repo_path), NewChatDialog
  • ChatView, Transcript (tool_call/tool_result join by call_id)
  • MessageBlock, UserMessage, AgentMessage (markdown via existing markdown.ts; plain text for streaming deltas)
  • ToolCallBlock (collapsed/expand), ToolApprovalBlock (Approve/Deny/Deny-with-reason)
  • ChatInput (Cmd/Ctrl+Enter send, Esc interrupt, disabled when ended)

Integration:

  • WorkspaceMode type, picker entry, hotkey handler, App.svelte branch, Sidebar.svelte branch, HotkeyManager navigation, HotkeyHelp label
  • Playwright e2e test: empty-state assertion always runs; daemon-reachable test is test.fixme pending /api/read_daemon_token exposure on the Axum test server

Dependencies / infrastructure

  • Pinned jsdom to ^26.1.0 to fix a pre-existing ERR_REQUIRE_ESM (html-encoding-sniffer / @exodus/bytes) that blocked vitest workers

Follow-ups

Tracked in #540 — includes: exposing read_daemon_token on the Axum test server (to un-fixme the full e2e), 401 retry-once, "+ New chat" keyboard nav, dedicated Chat help section, quality polish (toast on tool-approval failure, trim whitespace, orphan transcript GC, virtualization).

Test plan

  • pnpm test — 300/300 pass
  • cd src-tauri && cargo test — all pass
  • pnpm exec svelte-check — no new errors (3 pre-existing on main)
  • Playwright empty-state test passes locally
  • Manual smoke with a running daemon + real Claude binary (reviewer may run)

🤖 Generated with Claude Code

kwannoel and others added 28 commits April 22, 2026 18:09
Design for a new 'chat' workspace mode in the Tauri app that renders
the-controller-daemon sessions as structured messages, alongside the
existing development and agents modes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step-by-step TDD plan (21 tasks) for adding a 'chat' workspace mode to
the Tauri app that integrates with the-controller-daemon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jsdom@27.4.0 and later pull in CJS packages (html-encoding-sniffer@6,
whatwg-url@16, cssstyle@5) that internally require() ESM-only dependencies
(@exodus/bytes, @csstools/css-calc@3), which crashes vitest fork workers
with ERR_REQUIRE_ESM and prevents any test from running. jsdom@26.x
predates that dependency refactor and still uses CJS-compatible
html-encoding-sniffer@4 and cssstyle@4, restoring a working test suite.

Revisit this pin when the upstream packages are republished with proper
conditional exports (or dual CJS/ESM builds) that don't mix CJS require()
with ESM-only modules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task 12 of the chat mode implementation plan. Adds a modal
for creating daemon-backed chat sessions from a project row.
Includes agent dropdown, optional initial prompt, 422 inline
error handling, and a shared `newChatTarget` field on the
daemon store so Sidebar and ChatWorkspace can coordinate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add ChatView (header + transcript area) that hydrates from readEvents
on mount and opens a WS stream; Transcript renders finalized agent/user
text plus in-progress delta blocks. ChatWorkspace now mounts ChatView
when a session is active and shows an empty-state prompt otherwise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds ToolApprovalBlock which renders Approve / Deny / Deny-with-reason
controls beneath a pending tool_call. Transcript now detects pending
approvals via statusState === 'waiting_for_tool_approval' and a missing
tool_result for the tool_call, and forwards sessionId to the block.

Clicks send { kind: 'tool_approval', call_id, approved, reason? } via
DaemonClient.sendMessage. Buttons disable while the request is in flight.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task 17 of the chat mode plan. Adds a textarea input mounted below the
transcript in ChatView. Cmd/Ctrl+Enter sends user_text via DaemonClient;
Esc sends interrupt only when statusState is working, starting, or
waiting_for_tool_approval. Textarea disables itself when the session is
ended or failed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add classifyError helper that maps DaemonHttpError status codes and
network/unknown errors into a stable ErrorKind union. Wire into
ChatView.onMount (clean up session + toast on 404) and
ChatInput.sendText (banner on 409 session_ended, toast otherwise).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend HotkeyManager so j/k walks through projects and their daemon chat
sessions in chat mode, and Enter on a focused chat session sets
daemonStore.activeSessionId (instead of the PTY focus-terminal path).

- getVisibleItems gains a chat branch that lists daemon sessions whose
  cwd matches each project's repo_path. The "+ New chat" row is
  intentionally not keyboard-walkable for v1.
- navigateItem and the Enter (expand-collapse) handler route to
  daemonStore.activeSessionId when currentMode === "chat", leaving
  development and agents behavior unchanged.
- Adds a "chat mode navigation" describe block in HotkeyManager.test.ts
  covering j/k across project + chat session boundaries, PTY sessions
  being ignored in chat mode, Enter setting daemonStore.activeSessionId,
  project Enter still toggling expand, and collapsed projects skipping
  their chats.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract mode label to a $derived so chat mode renders as "Chat"
instead of falling through to "Development".
Adds chat-mode.spec.ts with:
- An always-runs test asserting Space -> c switches to chat mode and the
  DaemonEmptyState ("Daemon not running") is rendered when the daemon is
  unreachable; Retry keeps the empty state when still unreachable.
- A skippable suite scaffolded to spawn the real daemon against the
  fake_agent binary (from the-controller-daemon project), with beforeAll /
  afterAll lifecycle + isolated TCD_STATE_DIR. The test inside is marked
  fixme with a clear note: it cannot pass under the browser-based e2e
  harness because the Axum test server does not expose
  /api/read_daemon_token. Full end-to-end validation is left as a
  follow-up once the harness can either run under Tauri or proxy the
  daemon-token command.

Adds scripts/chat-integration-daemon.sh: helper with build/run/status
subcommands so contributors can easily produce the daemon + fake_agent
binaries at the paths the spec probes for.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- client.test.ts: cast fetchMock.mock.calls[0] via unknown for type safety
- commands/daemon.rs: apply rustfmt's default line wrapping

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kwannoel kwannoel enabled auto-merge (squash) April 22, 2026 10:21
CI's stable clippy now flags sort_by with a full comparator as
unnecessary_sort_by; apply the suggested sort_by_key + Reverse form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kwannoel kwannoel merged commit 6911ef5 into main Apr 22, 2026
3 checks passed
@kwannoel kwannoel deleted the session-1-07ce2e branch April 22, 2026 11:01
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