Skip to content
Merged
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 @@ -90,6 +90,7 @@ If the docs themselves feel stale or scattered, also read `docs/reference/DOCS_R
- Auto-update: `docs/features/auto-update.md`
- Windows cross-platform work: `docs/plans/windows-support.md`
- Dev mock runtime (dual-guarded `src/dev/` Tauri shim that boots the real UI in a plain browser under `npm run dev` with seed data, for visual/screenshot work): `docs/features/dev-mock-runtime.md`
- Web remote access (embedded default-off HTTP+WS server in the desktop app; a browser on another device loads the same UI bundle and drives the same running instance via a `__TAURI_INTERNALS__` WebSocket shim — pairing-token auth, mirror-mode multi-client stream fan-out, web fallbacks incl. remote project creation): `docs/features/web-remote-access.md`; locked design contract + remaining work at `docs/plans/web-remote-access.md`
- Repo boundaries: `docs/reference/ARCHITECTURE.md`
- Design system (color tokens, theming layers, `.theme-warm`/density scales, the no-hardcoded-colors rule — shadcn stone base `b1HYEHloH`): `docs/reference/DESIGN-SYSTEM.md`
- Keyboard shortcuts: `docs/reference/SHORTCUTS.md`
Expand Down
2 changes: 2 additions & 0 deletions docs/core/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Landed on `main` after the `v0.13.1` tag (unreleased): **structural sharing in `

Landed on `main` after the `v0.13.1` tag (unreleased): **agent-browser sessions are reaped during the app's lifetime instead of only on restart** (issue #126). Agent-browser daemons (headless Chromium behind each `AgentBrowserSession`) previously only got torn down by the restart-time PID-file reconciliation, so a long-running app session leaked one Chromium process per closed workspace that had ever opened a browser. `AppStateStore::close_workspace` now returns `CloseWorkspaceResult.removed_agent_browser_sessions` (collected under the same lock that removes the workspace, so no TOCTOU race), and a shared `reap_agent_browser_sessions` helper in `commands/workspace.rs` fire-and-forgets `AgentBrowserManager::close()` for each on BOTH close paths — `close_workspace` and `close_workspace_with_worktree_impl` (the worktree/MCP `workspace_close` path had zero agent-browser teardown before this). A new 5-minute backstop sweep in `lib.rs` closes any tracked `ws-*` session whose owning workspace no longer exists, via the pure `agent_browser::orphaned_ws_sessions` helper and `AppStateStore::live_agent_browser_session_names` — in-memory-keys-only (never shells out to `agent-browser session list`, so it can't touch another codemux instance's daemon) and `ws-*`-scoped (never touches user browser-pane sessions). See `docs/features/browser.md` § "Session reaping during app lifetime (issue #126)".

In progress on the `web-remote-access` branch (unmerged): **web remote access** — the desktop app grows an embedded, **default-off** HTTP + WebSocket server (new `src-tauri/src/web_remote/`, axum, in the desktop process) so a browser on another device (laptop, phone on the LAN or the user's mesh VPN) loads the *same* UI bundle and drives the *same* running instance — same projects, workspaces, terminals, agent chats. It is a **UI transport** (a second frontend for one backend), the sibling of the remote-hosts **compute transport** (same frontend, remote backend), and the two compose: a paired web client transparently sees and drives the desktop's remote hosts with no web-specific host code. The seam is `window.__TAURI_INTERNALS__` — the production twin of the dev-mock shim (`src/remote/shim.ts`) backed by a real WebSocket instead of fixtures, so **zero app components change**; `main.tsx` selects real-IPC / dev-mock / web-shim three ways and both shims stay dormant under the desktop WebView. Invokes dispatch through the main webview's own `on_message` (reusing arg deserialization + `State`/`AppHandle` extraction + ACL + error formatting for all ~300 commands with no per-command wiring); a `Builder`-registered channel **interceptor** + `ChannelRouter` re-route each command's `Channel` frames to the owning browser (JSON `chan` frames, or the compact `[0x01][u32 cb][u64 idx][payload]` binary frame for PTY bytes), and a `listen_any` **event hub** multiplexes all subscribers onto one desktop listener per event name. Auth is **pairing-token → persistent revocable session**: a 32-byte, 10-min, single-use pairing token (QR / `#pair=` deep link) is traded at `POST /api/pair` for a SQLite session (SHA-256 at rest, constant-time compare, no early-return), and the session is traded for a 30s single-use **WS ticket** so the session secret never lands in a `/ws` URL; credentials ride a `Bearer` header or an `HttpOnly; SameSite=Strict` cookie through one `authenticate`; optional **approval mode** holds a new pairing pending until the desktop approves; **revocation severs live sockets** immediately (frame `Close` + trip a `watch`). Origin checks guard every state-touching route, pairing is rate-limited 5/min/IP keyed on the real TCP peer, and the browser-pane proxy validates the loopback port range + rejects control chars. Multi-client is **mirror mode** (all clients share `AppStateSnapshot` incl. focus); the terminal + agent-chat streams now **fan out** to N subscribers (per-generation attach/detach, replay only to the new attacher, pause only when ALL subscribers pause), and the **desktop window owns scrollback serialization** (web clients no-op it). Web fallbacks are gated on `isRemoteClient()` so desktop stays byte-identical: an in-app **path browser** replaces the native file dialog (unlocking **opening projects / creating workspaces from the browser**), **Web Notifications** (toast fallback) mirror desktop notifications, `convertFileSrc` → an auth-gated `/api/assets` streamer, the loopback `agent-browser` daemons are reached through an authed WS/HTTP **browser-pane proxy**, window chrome is hidden, and the desktop stays the single updater — it publishes availability to paired browsers, defers its auto-update restart while remote devices are attached, and a browser can trigger the desktop's download-and-restart. `web_remote_list_endpoints` enumerates loopback / LAN / tailnet / MagicDNS with per-endpoint **secure-context** notes (only loopback is secure over plain HTTP; TLS is delegated to the mesh VPN's HTTPS serve or a user proxy in v1). Account (OAuth) sign-in stays desktop-only by design; the localStorage session is a documented shared-machine trade-off (revoke severs instantly). Debug-only e2e affordances (`web_remote::e2e_autostart`, `auth::seed_dev_offline_login`, dev `?remote=1`) are all `#[cfg(debug_assertions)]` **and** env-gated, so no release path is weakened. Verified with the full matrix (cargo check/test, `npm run check`/`test`/`build`) plus a live end-to-end drive through the Codemux browser pane (pair via link + QR, live terminal replay/input, agent-chat turn, open project, create workspace via the web path-browser, desktop+web mirroring, revoke-mid-session socket drop, reconnect). See `docs/features/web-remote-access.md`; locked design contract + remaining work (relay/account tier, per-client views, per-command ACLs) at `docs/plans/web-remote-access.md`.

Shipped in `v0.13.1`: **first-class non-git projects + chat image attachments + first-send polish.** (1) **Non-git projects** (PR #147) — a plain folder can be opened as a workspace (`is_git` snapshot flag) with an opt-in "Initialize Git" affordance in the context bar / Context Row / Changes panel; worktree controls hide in Thread Scope and there's no add-path git gate. "Initialize Git" is a *bare* `git init` — it never stages or commits the user's files (PR #148). See `docs/features/workspace-creation.md` § "Non-Git Projects". (2) **Chat image attachments rendered in sent messages** (PR #151) — an image attached to a chat message now renders inline in the sent bubble (≤160px, broken-image fallback) with a click-to-open lightbox. See `docs/features/agent-chat.md` § "Attachments". (3) **Deferred-worktree auto-naming fix** (PR #146) — first-send worktree creation stops silently falling back to random names when a real name can be generated; PR #144's deferred-worktree chat sessions now launch *in* the worktree and restore the Context Row issue chip (PR #148). (4) **Transcript smooth-scroll fixes** (PRs #148, #149) — `scrollbar-gutter: stable` restored on the viewport and native scroll anchoring allowed to stabilize the unpinned transcript.

Shipped in `v0.13.0` (PR #144): **Context Row** — the permanent bottom `WorkspaceContextBar` now hides itself (`useAgentChatPaneActive()`, `src/hooks/use-gui-chrome.ts`) while an Agent Chat pane is the active pane of the active surface in GUI chrome, and its git/PR detail relocates into the composer's scope row instead. `AgentChatPane`'s `belowComposerSlot` now renders a new read-only Context Row (static project/branch labels + `<WorkspaceStatusCluster />`) once a thread has messages, and passes the same `WorkspaceStatusCluster` as `ThreadScopeRow`'s new `trailing` prop for the empty-thread state — so the behind chip, PR chip, the GUI-mode background-browser indicator (extracted to the shared `BackgroundBrowserIndicator` + `useBackgroundBrowserSession` in `src/components/browser/background-browser-indicator.tsx`, still consumed by the bar for terminal-pane cases), and a "workspace details" popover (branch/base/behind/ahead/uncommitted/PR/location + View PR / Sync quick actions) read identically on both sides of the first send. `PR_CHIP_TONE` moved to `src/components/github/pr-status-icon.tsx` as the single shared home. Frontend + docs only; no Rust changes. See `docs/features/agent-chat.md` § "Context Row (running-thread status)" and `docs/features/workspace-context-bar.md`.
Expand Down
18 changes: 14 additions & 4 deletions docs/features/agent-chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -966,10 +966,20 @@ to `codemux.exe` under `binaries/` on Windows. (It originally shipped
as an `externalBin` under `usr/bin/`; moved to a resource because
linuxdeploy's patchelf step corrupts the ~100 MB bun-compiled binary
during AppImage bundling — see commit 025fa19.) At runtime, the
`setup()` hook in `src-tauri/src/lib.rs` resolves the resource via
`AppHandle::path().resource_dir()` and pins the resolved path into
the `CODEMUX_CLAUDE_SIDECAR_PATH` env var so the adapter (which has
no `AppHandle` access at construction time) can find it. The release
`setup()` hook in `src-tauri/src/lib.rs` calls
`resolve_sidecar(resource_dir)`
(`agent_provider::claude::sidecar_path`), which prefers an existing
`CODEMUX_CLAUDE_SIDECAR_PATH` override, then the
`<resource_dir>/binaries/` copy, and — **in debug builds only** —
falls back to the dev-tree `<CARGO_MANIFEST_DIR>/binaries/` copy so
`npm run tauri:dev` (whose resource dir does not carry the sidecar)
still resolves it and the agent-chat GUI can spawn. The winner is
pinned into the `CODEMUX_CLAUDE_SIDECAR_PATH` env var so the adapter
(which has no `AppHandle` access at construction time) can find the
same binary. The debug fallback is compiled out of release via
`debug_assertions`, so the release resolution stays resource-dir-only;
when the binary exists nowhere the hook leaves the env var unset and
the Claude provider stays unconfigured rather than panicking. The release
workflow (`.github/workflows/release.yml`) installs Bun and
pre-stages the binary so tauri-action finds it before bundling. CI
(`.github/workflows/ci.yml`) does the same, with a zero-byte
Expand Down
Loading