- when given a task, examine the code deeply and understand the intent of the current code. examine the code surrounding it as well to understand the constraints that the context the code lives in puts on it.
- understand the problem through this new light of how the code works. understand why it is acting the way it does through mental modeling of the workflow of the code.
- propose a solution to this in a markdown file that will outline what it is you propose to change, highlight how this will not create regressions because you've carefully analyzed code for side effects.
- execute on the solution you just documented using the approach above after taking one last look around to see if you missed anything. measure twice, cut once.
- DO NOT rely only on logs to verify behavior. Always test end-to-end in the browser (localhost:1420). Check that the actual UI responds, not just that RPC calls succeed.
- The user already has
npx tauri devrunning — don't ask them to start it. Rust auto-rebuilds in dev mode. - Be extremely careful with git operations. Never use
git revert. Alwaysgit diffbefore destructive operations.
- we will be working with a lot of ACP so always fetch https://agentclientprotocol.com/llms.txt to better understand ACP specs if the topic arises, and it will arise often
- The crow-cli agent implementation lives in
research/crow-cli/crow-cli/src/crow_cli/agent/main.py— study it when debugging agent-side behavior - ACP protocol log:
~/.local/share/crow/logs/acp.log(can be large, use grep/sed to search)
The chat feature is a 4-layer pipeline. Understanding how data flows through all 4 layers is essential for debugging.
Frontend (TypeScript) → Tauri Commands (Rust) → ACP Session (Rust) → Agent Process (Python/crow-cli)
acpStore.ts acp_chat.rs session.rs main.py (crow-cli)
acpChatView.ts (invoke/listen) manager.rs
Event flow (agent → frontend):
- Agent writes JSON-RPC to stdout
session.rsI/O task reads lines, parses JSON-RPC, broadcasts viaevents_tx: broadcast::Sender<SessionEvent>manager.rsforwarding task subscribes toevents_tx, forwards toglobal_events(the Tauri bridge)acp_chat.rsbridge task emitsacp:sessionUpdateTauri eventsacpStore.ts_handleSessionEventfilters bysessionId === this._sessionId, dispatches to UI
Prompt flow (frontend → agent):
acpStore.tssendMessage()→invoke('acp_chat_prompt', { sessionId, blocks })acp_chat.rsacp_chat_prompt→ looks up session bysessionId→session.prompt(blocks)session.rsrun_prompt()→ sendssession/promptJSON-RPC to agent stdin- Agent processes, streams
session/updatenotifications back through the event flow
| File | Purpose |
|---|---|
crates/crow-acp/src/session.rs |
AcpSession struct — owns agent connection, JSON-RPC over stdin/stdout, I/O task, event broadcast |
crates/crow-acp/src/manager.rs |
AcpSessionManager — session lifecycle, connection pooling, event forwarding setup |
crates/crow-acp/src/agent.rs |
AgentManager — spawns/kills agent subprocesses, stdin/stdout pumping |
src-tauri/src/commands/acp_chat.rs |
Tauri commands — bridge between frontend invoke() and Rust session layer |
src/vs/workbench/contrib/acpChat/browser/acpStore.ts |
Frontend store — manages chat state, event filtering, sendMessage/loadSession |
src/vs/workbench/contrib/acpChat/browser/acpChatView.ts |
Chat view — DOM rendering, connecting bar, event bindings |
src/vs/workbench/contrib/acpChat/browser/components/toolbar/chatHeader.ts |
History dropdown — session list, onSelectSession handler |
src/vs/workbench/contrib/acpChat/browser/media/acpChatView.css |
Chat CSS — connecting bar animation, message styles |
research/crow-cli/crow-cli/src/crow_cli/agent/main.py |
crow-cli ACP agent — load_session, prompt, cancel, react_loop |
Session switching must spawn a fresh agent process.
session/load on an existing connection causes the crow-cli agent to immediately cancel subsequent prompts (stopReason: "cancelled" in 2ms). The correct flow is: kill old agent → spawn new → initialize → session/load on fresh connection. This is implemented in manager.rs switch_session().
Event forwarding must be set up per-connection.
Each AcpSession has its own events_tx broadcast channel. bind_new_session, bind_load_session, and switch_session all spawn a forwarding task that subscribes to events_tx and forwards to global_events. Without this, events never reach the frontend.
Session ID field mapping: sessionId not id.
The ACP agent returns camelCase sessionId in responses. The frontend store must map s.sessionId not s.id when building the session list.
Frontend event filtering by sessionId.
acpStore._handleSessionEvent drops events where sessionId !== this._sessionId. After a session switch, if the store's _sessionId doesn't match the events' sessionId, all events are silently dropped.
The session_id_cell is shared with the I/O task.
AcpSession.session_id_cell: Arc<Mutex<String>> is cloned into the I/O task so it knows the current session ID for tagging events. This must be updated atomically during session operations.
npx tauri dev— runs both Vite dev server (localhost:1420) and Rust backend with auto-rebuildcargo check -p crow-acp— quick check of the ACP cratecargo check— full workspace check- Frontend TypeScript hot-reloads via Vite
- Rust backend auto-rebuilds when Tauri detects changes
- No need to restart anything after code changes in dev mode
This codebase is a fork/derivative of VSCode's web workbench. The src/vs/ directory contains heavily modified VSCode source. Key differences:
- Uses Vite instead of VSCode's custom build system
- Tauri provides the native shell (file system, terminal, process management)
- The
contrib/acpChat/directory is entirely custom (not from VSCode) - Extensions run in a web worker, same as VSCode web
src/vs/crow-bridge.tsis the integration point where crow features (Tauri commands, Rust backends) are wired into the VSCode workbench lifecycle
The frontend is NOT React/Vue — it's manual DOM manipulation using VSCode's patterns:
- Views extend VSCode's
ViewPaneor similar base classes and build DOM inrenderBody() - Components in
contrib/acpChat/browser/components/are custom classes that create DOM elements directly (no JSX, no virtual DOM) - State management is in
acpStore.tswhich uses an event emitter pattern — views subscribe to store events - CSS lives in
contrib/acpChat/browser/media/acpChatView.cssand is loaded via VSCode's CSS loader - The Tauri webview is at
localhost:1420— use the browser tools to inspect and test
The workspace has many crates. The most important ones for day-to-day work:
crow-acp— ACP protocol client (agent sessions, JSON-RPC, event streaming)crow-dap— Debug Adapter Protocol (similar pattern to ACP but for debuggers)crow-lsp— Language Server Protocol clientcrow-terminal— Terminal emulation and PTY managementcrow-workspace— Workspace/project managementcrow-git— Git integrationcrow-db— SQLite database layer (session storage, etc.)crow-auth— Authentication (OAuth flows for AI providers)- The Tauri binary is in
src-tauri/— commands are split into modules undersrc-tauri/src/commands/ - Extension host (Node.js worker for VSCode extensions) lives in
src-tauri/extension-host/
When frontend doesn't respond to backend events:
- Check
acpStore._sessionIdmatches the event'ssessionId(open browser console, add breakpoints) - Check the event bridge is running — look for
acp:sessionUpdateevents in browser's Tauri event listener - Check the forwarding task is alive in
manager.rs— if theevents_txsubscription was dropped, events go nowhere
When agent returns unexpected results:
- Search
acp.logfor the specific method/session to see the raw JSON-RPC exchange - Check the crow-cli agent's
main.pyfor how it handles that method - Remember: the agent process is a subprocess — check if it crashed or was killed unexpectedly
When Tauri commands fail:
- Check the browser console for the invoke() error
- Check Rust stderr (Tauri dev shows it in the terminal running
npx tauri dev) - Commands are async — make sure the frontend
.catch()handles rejections
- Don't confuse
crow(the editor platform) withCrow(the AI product built on top). The Tauri app is named "Crow" but the codebase is "crow". - The
dist/directory contains pre-built frontend assets — don't edit files there, they get overwritten by Vite builds - VSCode's
nls(national language support) wraps all user-facing strings —localize('key', 'default')pattern everywhere invoke()calls from frontend to Rust are async and can fail — always handle the error case- The
acp.logfile grows fast (60MB+) — always usegrep,tail, orsedwith line ranges, never try to read the whole thing