Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ If the docs themselves feel stale or scattered, also read `docs/reference/DOCS_R
- MCP host runtime (Step 9 — Codemux as MCP host/client for chat): current behavior in `docs/features/agent-chat.md` (§ MCP host runtime) + `docs/features/mcp-server.md`; research at `docs/plans/step-9-mcp-servers.md`; Codex MCP gateway feasibility spike (future Step 11) at `docs/plans/step-9-codex-mcp-spike.md`
- Beta Features toggle (Step 13): `docs/plans/step-13-beta-toggle-research.md`; operator UI smoke at `docs/plans/step-13-ui-smoke-checklist.md`
- Setup/teardown scripts: `docs/features/setup-teardown.md`
- Agent-run rollback checkpoints (opt-in run-start snapshot + restore, issue #80): current behavior in `docs/features/agent-chat.md` (§ Run-start rollback checkpoints); plan at `docs/plans/agent-run-checkpoints.md`
- Worktree bootstrapping: `docs/features/worktree-setup.md`
- Browser work: `docs/features/browser.md`, `docs/plans/browser.md`, `docs/reference/BROWSER-AGENT-COMMANDS.md` (browser stream stability fix archived at `docs/archive/browser-stream-fix.md`)
- OpenFlow work: `docs/features/openflow.md`, `docs/plans/openflow.md`
Expand Down
4 changes: 4 additions & 0 deletions docs/core/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Codemux is past Linux MVP and shipping cross-platform binaries. The workspace sh

Landed on `main` after the `v0.7.9` tag (unreleased) are three changes: **OpenCode conversation sync across cloud-push** (issue #16), a **project-favicon cache-bust fix**, and a **dev-only Tauri mock runtime**. (1) **OpenCode conversation sync** — the OpenCode counterpart to PR #15's Claude conversation sync. Pushing a workspace with an active OpenCode pane now continues that conversation on the remote (and pull-back continues it on the laptop), verified monotonic across 3+ cycles. Because OpenCode keeps every conversation in one SQLite DB (`~/.local/share/opencode/opencode.db`) that can't be rsynced without clobbering the host's other history, the sync uses OpenCode's **own `export`/`import` CLI** instead of a bespoke `rusqlite` row extractor (deliberately *not* the issue's original plan — the schema is large and fast-moving). `opencode export <id>` produces a portable bundle; `opencode import` on the receiving side preserves the session id, associates the session with the cwd it runs from, and is idempotent — so only the one synced session is touched and the host's unrelated OpenCode sessions are never clobbered (the explicit acceptance criterion). New module `src-tauri/src/ssh/opencode_db_sync.rs` (push `sync_opencode_session` + pull `pull_opencode_session`), wired into `commands/hosts.rs` alongside the Claude JSONL sync; `terminal/daemon_backed.rs` gained a generalized `build_agent_relaunch_command` that builds `opencode --session <id>` / `--continue` (the Claude-only relaunch synthesis is now per-agent), plus an `opencode` session adapter for local app-restart parity. Verified end-to-end against a Docker SSH host (`scripts/e2e/opencode-sync-e2e.sh` + env-gated `src-tauri/tests/opencode_sync_roundtrip.rs`): a session grew 4→644 messages across a host continuation, preserved over 3 push/pull cycles, with the unrelated host session held intact throughout. See `docs/features/opencode-conversation-sync.md`. (2) **Project-favicon cache-bust fix** (PR #71) — re-saving a project image (or re-opening the picker) now appends a changing `&v=` token to the derived favicon-service URL via `resolveImageUrl(input, cacheBust)`, so a site that changed its favicon visibly refreshes instead of the WebView serving the same stale cached bytes forever; direct/data URLs are passed through untouched. Touches `src/lib/project-image.ts`, `src/components/ui/project-avatar.tsx`, `src/components/layout/sidebar-project-group.tsx`, `src/components/overlays/project-image-dialog.tsx`. (3) **Dev-only Tauri mock runtime** (PR #82) — a `src/dev/` shim (`tauri-mock.ts` + `mock-fixtures.ts`, ~1k lines) installs a faithful `window.__TAURI_INTERNALS__` so the real React UI boots in a plain browser tab under `npm run dev` (Vite at `localhost:1420`) with seed data and no desktop window or Rust backend, enabling the Codemux browser-pane screenshot workflow for UI work. Dual-guarded in `main.tsx` (`import.meta.env.DEV` tree-shakes it out of production; `!("__TAURI_INTERNALS__" in window)` keeps it dormant under `npm run tauri:dev`). See `docs/features/dev-mock-runtime.md`.

Also landed on `main` after `v0.7.9` (unreleased) is the remainder of the **Tauri/React performance & architecture epic (issue #81)** — four changes: (1) **agent-chat events stream over per-thread Tauri Channels** (issue #75) — `forward_event` routes thread-scoped provider events (including the high-frequency `content_delta` token stream) to channels registered via `attach_agent_chat_output`/`detach_agent_chat_output` in a new `AgentChatChannelRegistry`, mirroring the PTY output path, instead of `app.emit`-broadcasting every token of every thread to every webview listener; only threadless global `RuntimeWarning`s keep the legacy `agent_chat_event` bus. The frontend hook (`use-agent-chat-events.ts`) attaches a `Channel` per pane/thread, the dev mock mirrors the channel contract, and integration tests cover routing, cross-thread isolation, detach, and delta ordering. (2) **`files.rs` commands moved off the GTK main thread** (issue #79) — `list_directory`, `search_in_files`, `search_file_names`, `reveal_in_file_manager`, `read_file`, `write_file`, and both clipboard-image commands are now `async` + `spawn_blocking`, same fix class as `commands/git.rs`. (3) **Headless daemon worktree creation reaches desktop parity** (issue #78) — `worktree_create` on `codemux-remote` now runs worktree includes (`.env` copy) + the project's setup scripts in the background with the same `CODEMUX_*` env + deterministic port the desktop injects (`scripts.rs` gained a `SetupEmitter` enum: desktop emits Tauri events, the daemon logs to stderr/serve.log); fetch-before-branch was already shared from issue #76. The tool response gains a `setup: { configured, port }` field; covered by a live-daemon e2e test (`http_worktree_create_runs_setup_scripts`). (4) **Opt-in background rollback checkpoint at agent-run start** (issue #80) — when `git.agent_checkpoint_enabled` is on (Settings → Git, default off), `agent_chat_start_session` fires a background scratch-index snapshot (captures modified AND untracked files, never touches the user's index/worktree, pinned under `refs/codemux/checkpoints/<thread>`, recorded on the `agent_chat_sessions` row) with zero added latency to the first token; the chat pane header gains a "Restore checkpoint" action (tree-only restore — refs never move; a pre-restore safety snapshot is pinned first). See `docs/plans/agent-run-checkpoints.md`.

Shipped in `v0.7.9` is **"Operate a remote workspace in place" (Open on host)** — the no-pull remote-operation capability (issue #64). The Workspaces overview's host-backed sibling row gains an **"Open on host"** action (enabled when the host is configured locally) that creates a local *attach-in-place* workspace: `WorkspaceSnapshot` gains `remote_cwd` (the workspace's real on-host directory) + `attach_only` (operated in place, no local files), `create_remote_attach_workspace` builds a ready single-terminal workspace with `host_id` set and **nothing copied under `~/.codemux/` locally**, and the daemon-backed terminal path (`remote_spawn_cwd`) spawns into `remote_cwd` over the existing SSH-tunneled pty-daemon so commands run on the host with live streaming. Persistence is real: `ssh::tunnel::build_remote_command` now **reuses a still-running daemon** (via a `<socket>.pid` liveness probe) or **spawns it detached** (`setsid`/`nohup`, stdio redirected) instead of `exec`-ing it in the SSH foreground, so closing the app leaves the host process running and reopening re-tunnels + `client.list()`-reattaches the live sessions (a strict improvement for the push flow too). The command (`workspace_open_on_host`) resolves the host-backed sync row → local host → `origin_path`, is idempotent, and is excluded from `reconcile_from_snapshot` so it never creates a duplicate cloud row; the overview dedupes the sibling card against the open in-place view and renders an "on host" badge with detach-only close. See `docs/features/remote-in-place.md`.

Shipped in `v0.7.9` is a **multi-device robustness + remote-persistence pass** layered on top of repo-unit sync. (1) **SSH tunnel health is now surfaced in the UI**: the `TunnelStatus` the supervisor already computed (`connected`/`pending`/`reconnecting`/`circuit_open`) is bridged to the frontend via a new `tunnel-status-changed` event + `spawn_tunnel_status_forwarder` (self-terminating per supervisor), a zustand `tunnel-status-store` fed by an app-root `useTunnelStatusEvents` hook, and a sidebar pill — amber **"Reconnecting…"** on a sleep/wake or WiFi flap, red **"Connection lost — re-push"** once the circuit breaker trips — so a dropped tunnel no longer looks like a frozen workspace. (2) **Host persistence**: auto-upgrade no longer kills host-side agents — `hosts_upgrade` probes the daemon's `live_terminals` (via `codemux-remote serve status`) and **defers** the systemd-unit restart when sessions are live (`UpgradeOutcome::Skipped`); separately, the local pty-daemon now **idle-reaps** itself after 1h with zero sessions (hard re-check under lock so it can never reap a live session). (3) **Workspaces-sync robustness**: project-first remote pull with a real protected root (new local-only `default_branch` column + `resolve_default_branch`/`ensure_origin_head` + `workspaces_adopt_project` "Pull project" action), serialized adopts via a per-row creation lock, client-side `dedupe_sibling_rows` collapse of cross-device duplicate cards, daemon-side `collapse_main_for_uid`/`normalize_main_workspaces` (one repo root per project), uid-keyed collision-safe host paths (`<basename>-<short-uid>`), and a non-destructive `workspaces_reconcile_copy` action for legacy divergent copies. (4) **OpenFlow comm-log fix**: the daemon-backed agent spawn path (default since persistent agents) now tees cleaned PTY output to the communication log via the shared `comm_log_entry_for_chunk` helper, so daemon-spawned OpenFlow agents stop producing an empty log that blinded stuck-detection. See `docs/features/remote-hosts.md`, `docs/features/persistent-agents.md`, `docs/features/workspaces-sync.md`, `docs/features/workspaces-overview.md`, `docs/features/openflow.md`, `docs/plans/repo-unit-sync.md`.
Expand Down Expand Up @@ -200,6 +202,8 @@ The repo structure is clean and domain-split:
- Worktree-include listener no longer re-attaches every backend tick
- `ensure-draft-when-empty` effect uses a primitive fingerprint
- Chat transcript rows + file-tree nodes memoised to skip per-token re-renders
- Agent-chat streaming uses per-thread Tauri Channels (issue #75) — no global event-bus fan-out for `content_delta` tokens
- `files.rs` commands (directory listing, in-files/file-name search, file read/write, clipboard images, reveal) run on the blocking pool, not the GTK main thread (issue #79)

## Partial / Being Hardened

Expand Down
62 changes: 51 additions & 11 deletions docs/features/agent-chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,22 +516,62 @@ string.

## Event bridge

Every registered provider's canonical event stream is forwarded to
the frontend on a single Tauri channel named `agent_chat_event`.
Payloads carry the originating `thread_id` alongside the raw
`ProviderRuntimeEvent` so subscribers can filter without re-parsing.
Global events (`RuntimeWarning` without a thread id) are still
forwarded, with an empty `ThreadId`. The frontend hook
`useAgentChatEvents(threadId, handler)` in
`src/hooks/use-agent-chat-events.ts` wires this up with a
thread-id filter.
Every registered provider's canonical event stream is routed to the
frontend over **per-thread Tauri Channels** (issue #75 — Tauri's
recommended mechanism for high-throughput streaming, mirroring the
PTY output path). When a pane binds to a thread, the
`useAgentChatEvents(threadId, handler)` hook
(`src/hooks/use-agent-chat-events.ts`) invokes
`attach_agent_chat_output(thread_id, channel)`; on unmount it calls
`detach_agent_chat_output(thread_id, subscription_id)`. The backend's
`AgentChatChannelRegistry` (`commands/agent_chat.rs`) maps each
thread id to its attached channels and `forward_event` sends each
thread-scoped event — including the high-frequency `content_delta`
token stream — only to that thread's channels, so a pane never
receives (or filters) another thread's traffic. Multiple panes may
attach to the same thread; dead channels (webview reload without
detach) fail on send and are pruned lazily.

Only **threadless** events (global `RuntimeWarning`s with no owning
pane) still go out on the legacy `agent_chat_event` broadcast bus,
with an empty `ThreadId`.

Replay semantics: transcript-mutating events are persisted to
`agent_chat_messages` (unchanged), so a late-attaching or resumed
pane hydrates history from the DB via `agent_chat_list_messages`
while the channel carries only live deltas. Partial deltas are never
persisted — they're superseded by their `item_completed`.

The bridge is a thin loop: one background Tokio task per provider,
each consuming the provider's `event_stream()` and re-emitting each
event via `AppHandle::emit`. `broadcast::error::RecvError::Lagged`
each consuming the provider's `event_stream()` and routing each
event through `forward_event`. `broadcast::error::RecvError::Lagged`
is already swallowed by each provider's event-stream helper, so slow
subscribers never crash the loop — they just drop old events.

## Run-start rollback checkpoints (issue #80, opt-in)

When `git.agent_checkpoint_enabled` is on (Settings → Git; default
**off**), `agent_chat_start_session` fires a **background** snapshot
of the workspace working tree after the session is live — nothing on
the first-token path awaits it. The snapshot uses a scratch index
(`GIT_INDEX_FILE`), so it captures modified **and untracked** files
without ever touching the user's real index or worktree, and is
pinned under `refs/codemux/checkpoints/<thread>`; the commit + HEAD
hashes are recorded on the `agent_chat_sessions` row
(`checkpoint_commit` / `checkpoint_head`).

The pane header shows a "Restore checkpoint" action (History icon,
hover-reveal) when the thread has a recorded checkpoint. Restore is
**tree-only**: a safety snapshot of the current state is pinned under
`refs/codemux/pre-restore/<thread>` first, then
`git read-tree --reset -u` + `git clean -fd` make the tree match the
snapshot — files created after the checkpoint are removed, ignored
files and branch refs/commits are untouched. Commands:
`agent_chat_get_checkpoint`, `agent_chat_restore_checkpoint`; git
helpers in `src-tauri/src/git.rs`
(`git_create_workspace_checkpoint` / `git_restore_workspace_checkpoint`).
Design notes: `docs/plans/agent-run-checkpoints.md`.

## Feature flag

The new flag `enable_agent_chat` lives on the existing `FeatureFlags`
Expand Down
1 change: 1 addition & 0 deletions docs/features/setup-teardown.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ The "Configure" button opens Settings > Projects. Dismiss persists per-project.
- Re-run setup via context menu, Tauri command, socket API, and CLI
- DB-stored project scripts via `get_project_scripts` / `set_project_scripts` Tauri commands
- Unit tests for config reading, git root resolution, worktree fallback, and DB roundtrip
- **Headless daemon parity (issue #78)**: `worktree_create` on `codemux-remote` runs the same pipeline — worktree includes copy + setup scripts with `CODEMUX_*` env and the deterministic per-workspace port — in a background thread after registering the workspace. File-based config only (`.codemux/config.json`; the daemon has no settings DB). Progress/failure events that the desktop emits to the setup overlay are logged to stderr instead (captured in `serve.log`) via the `SetupEmitter::Log` variant in `scripts.rs`; the tool response carries `setup: { configured, port }`. Covered by a live-daemon e2e test (`http_worktree_create_runs_setup_scripts` in `src-tauri/tests/codemux_remote_serve_mcp.rs`).

## Current Constraints

Expand Down
Loading