* fix(server): include OpenCode console subscription providers in model list
Providers with source "api" (e.g. Pi coding agent via OpenCode Go
subscription) appear in providers.all but not in providers.connected,
which only lists env/config providers. Paseo was filtering exclusively
by connected, so subscription-managed providers were silently dropped
and users saw an empty model list or a misleading "set env vars" error.
Fix: treat source === "api" as accessible in both listModels() and the
context-window lookup helper. Also update the error message to mention
the console login option.
chore: add portless as a devDependency so npm run dev works without
a global install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove portless dependency
* test(server): add unit tests for OpenCode api-source provider inclusion
Covers the source="api" logic added in 08e3c6bf: providers managed by the
OpenCode console/subscription must appear in listModels and the context window
lookup even when absent from the connected list.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: drop unrelated package-lock.json churn
The lockfile changes were peer metadata additions from a newer npm
version, not actual dependency changes. Restore to match main.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
The reconciler updated project.kind when a directory became a git repo
but never updated the workspace.kind underneath, leaving the sidebar in
a split-brain state: an expandable git project containing a workspace
with no leading icon (workspaceKind stayed "directory" instead of
flipping to "local_checkout" or "worktree").
* Add Auto Review permission mode
* Align Codex Auto Review with auto permissions
* Defer Codex Auto Review to dedicated PR
* fix(app): drop unconditional snapshot refresh on mode selector open
Opening the mode selector was reusing the model selector refresh path, which forced a provider snapshot refresh every time. That made the agent status bar briefly lose its selected provider data and flash empty while the picker opened.
Keep the stale model refresh on the model selector only, and let the mode selector open from the existing snapshot state.
* refactor(claude): rename Claude auto permission mode label to "Auto mode"
Claude Code itself calls this permission setting Auto mode in its CLI/TUI, so match that label in Paseo. The supporting test names and transport eligibility errors now use the same terminology.
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* Fix relay E2EE reconnect races
* fix(relay): use WebSocket protocol ping for daemon control keepalive
The daemon's control socket pinged the relay every 10s with a JSON
`{type:"ping"}` message. Every JSON message wakes a hibernated Cloudflare
Durable Object via `webSocketMessage`, so an idle daemon was billing
relay DO CPU 24/7 just to echo `pong`.
Switch to `socket.ping()` (RFC 6455 protocol ping frames). Cloudflare's
runtime auto-responds at the edge without waking hibernated DOs, so an
idle daemon-to-relay connection now costs zero DO CPU.
Liveness still works: the `ws` library emits a `pong` event on the
daemon when the edge replies, which refreshes `controlLastSeenAt`.
`markControlReady` stays tied to DO-originated messages (sync,
connected, disconnected), so we still detect a broken DO at connect.
The DO keeps its JSON ping handler tagged `COMPAT(relay-json-ping)` so
old daemons (< v0.1.76) continue to work.
* test(relay): assert protocol-ping pongs arrive in e2e idle test
Adds a `relay_control_pong_received` debug log to the daemon's pong
handler, then asserts in the existing idle-relay e2e test that:
- at least one pong arrived during the 12s idle wait (proving the
Cloudflare edge auto-responds to RFC 6455 protocol pings)
- no stale terminations fired (proving the keepalive path is healthy)
If the daemon ever regresses to JSON-level keepalive pings (which wake
the hibernated relay Durable Object and incur CPU billing), this test
will catch it: the `pong` event would never fire because the DO's reply
would arrive via `message`, not as a protocol pong frame.
The test's capturing logger is bumped to `debug` level so the assertion
can read the new debug log; production daemons stay at `info`.
Confirmed locally against wrangler-hosted relay: 2 pongs / 0 stale per
12s window.
* test(relay): catch dual-ping regression via DO-side legacy ping log
The existing idle-test assertion catches "revert to JSON ping only" via
the missing pong event, but misses the dual-ping regression where
someone adds `socket.send(JSON.stringify({type:"ping"}))` alongside
`socket.ping()`. Both paths would work, the pong assertion still
passes, and DO CPU billing silently returns.
Add a console.log in the DO's handleControlKeepalive (which only runs
when a JSON ping reaches the DO), then assert in the idle e2e test
that no such log appeared during the 12s idle window.
Verified the assertion catches the regression: temporarily injected a
`socket.send(JSON.stringify({type:"ping"}))` next to socket.ping(),
test failed with `expected 1 to be 0` and the relay logged
`legacy_json_ping_received`. Removed the injection.
Log noise during the COMPAT window doubles as useful telemetry: it
counts how many old daemons are still pinging, which informs the
removal date for the COMPAT shim.
* fix(app): auto-set ref picker from a single attached PR
Attaching a PR in the new-workspace composer signals "I want this PR's
context", and almost always also means "base the worktree on this PR".
Until now the two paths were independent: the ref picker stayed on the
default branch, so the worktree was branched off main even though the
sidebar chip looked like a PR checkout.
Auto-promote the picker when exactly one PR is attached and the user
hasn't manually picked a ref. Manual picker selections are preserved;
auto-promoted ones are cleared when their backing attachment is removed.
* refactor(app): derive auto-promoted picker selection instead of syncing
The previous version drove `pickerSelection` from a useEffect that
inspected attachments and overwrote state. That meant the shared
`PickerSelection` type had to grow a `source: "manual" | "attachment"`
discriminator just so the effect could tell its own writes apart from
manual picks. Classic effect-as-derived-state.
Split the state instead. `manualPickerSelection` holds explicit picks;
`autoPickerItem` is a useMemo over composer attachments; the effective
`pickerSelection` is `manual ?? auto` via a small precedence helper. No
effect, no `source` flag, and `PickerSelection` stays a screen-local
shape since nothing outside needs it.
OpenCode 1.14.42+ closes the /event SSE stream cleanly right after
server.connected, breaking the entire turn lifecycle: prompts get
queued and run, but Paseo never sees session.idle / message deltas /
tool calls / questions. Every turn either fails with a generic stream
EOF or hangs waiting for events that never arrive.
Switch the post-EOF recovery to the canonical messages REST endpoint
(via the upgraded SDK) and poll incrementally so tool calls and
clarifying questions surface live during the SSE gap. Bound the wait
with a completion cap and a separate liveness cap so silent rejections
fail fast instead of hanging until the cap. Cap session.abort similarly
so explicit cancels land within seconds.
The recovery path is gated on the for-await loop exiting without a
terminal event, so healthy turns never enter it - if upstream restores
SSE delivery, this code becomes dead and is mechanically removable via
the COMPAT(opencodeEofRecovery) and COMPAT(opencodeSlowAbort) tags.
Also tighten the type of projectSettingsRoute on WorktreeSetupCalloutPolicy
so router.navigate accepts it (was widened to string, broke app
typecheck on main).
Refs: getpaseo/paseo#861, anomalyco/opencode#26697,
anomalyco/opencode#26635
* fix(server): recover completed opencode turns after SSE EOF (#861)
* fix(server): wait for opencode completion after EOF
The original EOF recovery only checked OpenCode storage once after the SSE stream ended. That missed the real failure mode from #861: OpenCode can drop /event while the turn continues behind the scenes, then persist the assistant completion a little later.
Poll the persisted session for the active turn before failing EOF, while still requiring strong completion evidence and ignoring messages that predate the turn. This preserves failure behavior when there is no persisted completion.
Add behavioral coverage for delayed completion after EOF, partial streamed text plus persisted completion without duplication, stale old completions, and the no-evidence failure path.
---------
Co-authored-by: pluto <plutofog@proton.me>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>