* feat(server): add ACP base provider with Claude ACP integration
Implement a generic Agent Client Protocol (ACP) base provider that any
ACP-compatible agent can extend with minimal code. Includes a concrete
Claude ACP implementation via @agentclientprotocol/claude-agent-acp
with full parity to the existing Claude Code provider.
The base handles subprocess lifecycle, streaming translation, permission
bridging, terminal/fs callbacks, listModels, loadSession/resume, and
mode/model management. The concrete class only specifies the command,
modes, and availability check.
* fix: update lockfile signatures and Nix hash
* feat: add Copilot ACP provider, eliminate hardcoded provider unions, fix ACP streaming bugs
Add Copilot as an ACP provider (copilot --acp), with real modes and models
discovered from the ACP server. Fix two ACP base bugs: duplicate assistant
text (emit deltas not cumulative) and idle→running→stuck (fire-and-return
startTurn). Replace all hardcoded provider string unions with string/manifest-
derived values so adding a provider only requires: impl class, manifest entry,
registry factory, icon, and E2E config. Add provider docs and Copilot icon.
* fix: update lockfile signatures and Nix hash
* feat(app): add OpenCode provider icon
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
The SDK's supportedModels() API hides the 1M context variant behind a
"default" alias and doesn't expose it as a selectable model. Replace
dynamic SDK discovery with a hardcoded catalog that exposes both
claude-opus-4-6[1m] (1M context) and claude-opus-4-6 (200k) as
distinct models. Delete sdk-model-resolver which parsed SDK descriptions.
Reuse existing project when a matching git remote is found instead of
creating duplicates. Detect git worktrees via --git-common-dir and set
workspace kind accordingly.
Add listCommands() to OpenCodeAgentSession via the OpenCode SDK's
command.list() API, and route recognized /commands through
session.command() instead of promptAsync(). This fixes issue #168
where slash commands didn't load when OpenCode was the selected harness.
Remove the "agent can be a terminal" branching from the entire codebase.
An agent is now always a session-backed chat agent. Standalone terminal
infrastructure (terminal component, ANSI handling, terminal-stream-protocol)
is preserved.
Server: delete ManagedTerminalAgent, AgentKind, TerminalExitDetails,
launchTerminalAgent, registerTerminalAgent, handleTerminalAgentExited,
supportsTerminalMode capability, buildTerminalCreate/ResumeCommand from
all providers, terminal agent persistence/projections.
App: delete terminal-agent-panel.tsx, terminal-agent-reopen-store.ts,
terminal/terminalExit fields on agent state, "Terminal Agents" launcher
section, terminal-agent workspace setup flow, terminal badge in agent list.
CLI: remove terminal column from ls, terminal-agent error from send.
60 files changed, -3592 lines
- Align portless code with storage branch's numeric workspace IDs and new field names
- Update workspace kind comparisons (local_checkout → checkout/worktree)
- Add missing services, supportsTerminalMode, terminal fields to test fixtures
- Fix archive timestamp assertions to match dynamic archiveSnapshot flow
- Fix dictation, voice runtime, and service health monitor test timing
- Add xterm-addon-ligatures type declaration for terminal emulator
Windows daemon paths like C:\Users\... don't start with "/", breaking
the explorer sidebar, checkout status, terminals, and file attachments
when connected to a Windows daemon. Consolidate three private
isAbsolutePath implementations into a shared utility and use it
everywhere, with correct file URI conversion for Windows and UNC paths.
`paseo logs` was only showing the last 40 collapsed timeline items
due to DEFAULT_MAX_ITEMS. Setting to 0 disables the cap so the full
timeline is shown by default. --tail still works for limiting output.
Windows npm shims (e.g. C:\nvm4w\nodejs\codex) fail with ENOENT when
spawned without shell. Use `shell: true` on win32 for all provider
launches instead of the overly complex shouldUseWindowsShell function.
OpenCode's message.part.delta events use field="text" for all parts
including reasoning, because "text" is the property name being updated.
Track part types from message.part.updated events and use them to
correctly classify deltas for known reasoning parts.
Also set PASEO_SUPERVISED=0 in vitest setup to prevent process.send()
conflicts with vitest's fork pool.
Spawning a missing/broken codex binary emits an async "error" event on
the child process. Without a listener, Node.js crashes the daemon with
no log entry. Add child.on("error") in CodexAppServerClient and global
uncaughtException/unhandledRejection handlers that log via pino before
exiting.
* style: add subtle teal tint to dark mode surfaces
Replace neutral gray surfaces with a teal-tinted palette across
the app and website, giving Paseo a warmer, more recognizable feel.
App uses a restrained tint (G-R ~3), website is slightly stronger
(G-R ~6) as a brand showcase.
* fix(opencode): handle message.part.delta events for assistant text streaming
OpenCode v2 streams assistant text via `message.part.delta` events (with
field "text" or "reasoning"), but the translator only handled
`message.part.updated`. This caused assistant messages to be silently
dropped during live streaming.
* feat: show agent short ID in tab context menu and tooltip (#161)
Add agent short ID to the "Copy agent id" menu item as trailing hint
text and to the tab tooltip next to the title. Add leading icons to
all workspace tab context menu items.
* add batch close rpc for workspace tabs
Use non-recursive directory watchers on Linux since recursive fs.watch
is not supported. Dynamically discover and watch subdirectories, refreshing
the watcher tree on changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use non-recursive directory watchers on Linux since recursive fs.watch
is not supported. Dynamically discover and watch subdirectories, refreshing
the watcher tree on changes. Also pin rewind command first in the slash
command list and remove stale .gitignore entry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Built-in service proxy with branch-based URLs, service health
monitoring, workspace hover card with service status, and
"Forget about ports" homepage section.
* WIP: fix archive tab reconciliation
* fix: converge archive into AgentManager for cross-session propagation
CLI archive left agent tabs visible in passive app clients because
Session.archiveAgentState only notified the archiving session's own
subscription. LoopService had the same gap via AgentManager.archiveAgent.
Converge on AgentManager.archiveAgent as the single canonical archive
path: persist updatedAt, call notifyAgentState before closeAgent so all
sessions receive the archived snapshot. Delete Session.archiveAgentState
and make handleArchiveAgentRequest a thin wrapper.
Services defined in paseo.json get reverse-proxied through the daemon
via hostname-based routing on *.localhost. Each service receives $PORT,
$HOST, and $PASEO_SERVICE_URL env vars, and is accessible at
{service}.localhost:6767 (main) or {branch}.{service}.localhost:6767
(worktrees).
New CLI commands: ls, create, kill, capture (tmux capture-pane style with
line range slicing and ANSI stripping), and send-keys (with special token
interpretation). Server-side adds capture_terminal_request RPC and
list-all-terminals support (optional CWD). Includes e2e tests and skill docs.
Speech runtime was leaking 8+ individual resolvers into bootstrap and
blocking server startup for ~3s with synchronous Sherpa native model
loading. Refactor into a self-contained SpeechService that owns its
full lifecycle (init, download, monitor, cleanup) and defer start()
until after httpServer.listen() so native loading doesn't block
accepting connections. Server startup drops from ~3.2s to ~0.5s.
Also removes unused client-triggered speech model download/list path
(CLI commands, session handlers, message types) — the service manages
its own models.
The process.send check alone was insufficient because vitest also runs
workers with IPC channels. Now the supervisor sets PASEO_SUPERVISED=1
on the worker env, and bootstrap checks for it before sending.
The desktop app could time out connecting to the daemon on first launch
because the PID file advertised a listen address before the HTTP server
was actually listening. The supervisor now writes the PID lock with
listen: null and updates it only after the worker sends a paseo:ready
IPC message confirming the server is listening.
- Rename daemon-runner.ts to supervisor-entrypoint.ts
- PID lock acquired with listen: null, updated via paseo:ready IPC
- Worker sends paseo:ready after httpServer.listen() resolves
- Desktop polls until listen is non-null before returning to the app
- Remove PASEO_PID_LOCK_MODE and external lock mode
- Remove unnecessary env overrides (PASEO_HOME, PASEO_CORS_ORIGINS) from
desktop daemon spawn
- Reduce trace log bloat: inbound/outbound WebSocket messages now log
only message type and payload size instead of full payloads
- Supervisor restarts worker on SIGKILL (covers OOM)