ACP-based providers (Kimi and other custom ACP agents, Copilot, Cursor) were
absent from the import session picker. Two causes, both in the import path:
1. Never queried. The import refactor (51d1d007) gated provider listing on
capabilities.supportsSessionListing and set it for claude/codex/opencode/pi
but not for ACP providers, so agent-manager skipped every ACP provider in
listImportableSessions — the picker showed 'no sessions' even though
listImportableSessions is fully implemented. Set supportsSessionListing on
DEFAULT_ACP_CAPABILITIES and COPILOT_CAPABILITIES. Agents that don't support
session/list still return an empty list at the runtime probe, so this only
controls whether the daemon queries them.
2. Ignored cwd. listImportableSessions never forwarded the requested cwd to the
agent's session/list call, so agents returned globally-recent sessions that
the import limit could truncate before the current directory's sessions were
reached. Forward options.cwd as the session/list cwd filter (with the
pagination cursor) so the agent filters by directory at the source.
* Eliminate spiky terminal lag under load
Terminal output stuttered during heavy output and when the daemon was
busy. Two compounding causes, both confirmed by measurement:
- Every 256KB of output, the daemon dropped pending frames and re-sent a
full cell-grid snapshot (cloned across IPC, stringified to JSON) — a
10MB build did this ~40 times. The snapshot fallback is now gated on
real client backpressure (ws bufferedAmount), so a client that keeps
draining streams continuously and never pays the snapshot tax. This
also removes the periodic GC hitch the churn caused.
- Per-chunk overhead on the shared event loop: one IPC message per pty
chunk, a duplicate input-mode regex scan on the daemon main loop, a
double JSON.stringify per outbound message, and 16KB string realloc
per chunk. Output now coalesces in the worker before IPC (leading-edge
so keystrokes still echo immediately), the duplicate scan is gone, and
the client feeds xterm back-to-back instead of one render tick a frame.
Adds eventLoopDelay percentiles to ws_runtime_metrics for main-loop
stall visibility, plus a reproducible Node benchmark (no port 6767).
Measured: echo p50 7.7ms to 2.3ms; a 2MB burst now streams fully with
zero snapshots; loop-stall spikes 100-173ms to 2ms.
* Guard trace-level check against partial logger stubs
The isLevelEnabled gate added for the emit() perf fix crashed every
session test that injects a hand-rolled logger stub (10 of them omit
isLevelEnabled). Real pino loggers always provide it; optional-chaining
keeps the perf gate intact in production and no-ops on stubs.
* Make recent-output exit-summary test deterministic
The added test self-exited the child immediately after writing 3000
lines, then asserted the newest line was in the exit summary — but the
summary reads the headless xterm buffer, which parses writes
asynchronously, so a loaded CI runner saw a stale buffer (line-2707 not
line-2999). Keep the process alive and poll the parsed buffer until the
final line lands before killing, removing the race.
* Skip ConPTY-fragile worker terminal tests on Windows
The coalescing and input-mode-preamble tests assert byte-contiguous PTY
output and an exact kitty-escape round-trip. Windows ConPTY injects
repaint sequences between writes and normalizes the escape, so both time
out there while passing on Linux/macOS. Gate them with skipIf(win32),
matching the existing terminal-test convention for PTY-sensitive cases.
* Apply terminal barriers immediately when no writes are pending
A barrier op (snapshot/restore/clear) only needs the sentinel-write gate
to wait out plain writes still parsing in xterm's buffer. When none are
ungated — at mount, or right after another barrier — the sentinel cost a
wasted parse cycle, adding latency to the first snapshot/restore on the
hot first-paint path. Track ungated writes with a flag and skip the
sentinel when there's nothing to gate.
* Fix mobile startup saved-host e2e
* Unskip Windows worker terminal coverage
* Tighten worker coalescing assertion
* Reset terminal ungated writes on unmount
* Fix terminal regressions found in adversarial review
Three issues this PR's terminal changes introduced:
- Worker snapshot could duplicate coalesced output. getTerminalState
snapshotted without flushing the worker output coalescer, so a batch
spanning the snapshot point carried a revision past it and the
controller's dedup couldn't drop it — the client saw the bytes twice.
Flush before snapshotting.
- Relay clients lost backpressure protection. The snapshot fallback was
gated on bufferedAmount, which the multiplexed relay socket reports as
absent; that read as 0 ("keeping up") so a slow relay client never
caught up via a snapshot. Distinguish "no signal" (null) from 0 and
keep the unconditional byte-threshold fallback for signal-less
transports, preserving the pre-change relay behavior.
- Recent-output buffer was no longer a hard cap. A single chunk larger
than the limit was retained whole; slice its tail.
Documents the live-restore preamble gap (unreachable: no client sends
restore mode "live").
* Skip input-mode preamble test on Windows ConPTY
CI confirmed Windows ConPTY normalizes away the kitty keyboard escape
the child writes, so it never reaches the worker's input-mode tracker
and the preamble stays empty — the test times out. ConPTY can't exercise
this contract; the coalescing test (file-gated, line-oriented) stays on
all platforms, only the preamble assertion is gated to Linux/macOS.
* feat(server): refresh PR state promptly after agent merges via gh CLI
When an agent merges a PR with gh pr merge, the app could take up to
2 minutes to show the merged state because:
- gh pr merge is a remote GitHub operation that does not touch local git refs,
so file watchers never fire
- the GitHub self-heal poll runs on a 2-minute slow interval for settled PRs
- no refresh is triggered when agent tool calls complete
Add a detection path that intercepts completed shell tool calls in the
agent manager and signals the workspace git service when a command may
have changed external state (gh *, git push, npm publish). The git
service then invalidates the GitHub cache and schedules a forced
GitHub-inclusive refresh, so the updated PR state arrives within ~500ms.
The agent manager does not leak cache semantics; it calls a semantic
onWorkspaceStateMayHaveChanged callback and lets the git service decide
what that means internally.
* refactor(server): share workspace refresh merge logic
* Preserve Pi import model and thinking
* Format Pi import config helper
* Preserve Pi thinking in large imports
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* Add Claude Fable 5 to the model catalog
Fable 5 (`claude-fable-5`) is the new top-tier Claude model above Opus,
but the Claude provider's model list is hardcoded in `CLAUDE_MODELS` and
was not fetched dynamically, so it never appeared in the selector.
- Add `claude-fable-5` to `CLAUDE_MODELS` (non-default; Opus 4.8 stays the
default). Uses the Opus extended-thinking effort levels (low/medium/high/
xhigh/max). No `[1m]` variant: Fable 5 is natively 1M context, unlike the
Opus/Sonnet entries whose `[1m]` opts a 200K-default model into 1M.
- Teach `normalizeClaudeRuntimeModelId` the Fable family. Its version is a
single segment (`fable-5`) rather than the `{major}-{minor}` scheme, so it
needs its own match to map dated runtime IDs back to the catalog ID.
- Update the model-list and normalization test expectations, plus the CLI
provider e2e catalog fixture.
Fast Mode is correctly unaffected — its allowlist is Opus 4.6/4.7/4.8 only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Simplify Fable runtime-ID normalization
Drop the unused `[1m]` capture from the Fable match branch. There is no
`claude-fable-5[1m]` catalog entry — Fable 5 is natively 1M context, so
there is no 200K-default model to opt into 1M — which made that path dead
code (and an untested one). Keep the `[-_ ]+` separator class for
consistency with the sibling opus/sonnet/haiku regex right below it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: lumingjun <lumingjun@bytedance.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Provider listing now returns picker rows only. Importing a selected provider session hydrates its timeline before the daemon publishes the Paseo agent.
* Support imports from Pi-compatible providers
* Add OMP as a built-in provider
* Address Pi session scanner review
* Restore Pi session import discovery
* Update provider test for disabled OMP
Teach daemon stop to use lifecycle shutdown when the API is reachable even if the owner pid is stale, then wait for the API to disappear and clean the stale pidfile.
Launch desktop-managed daemons detached from desktop stdio, preserve stale reachable daemon ownership for version checks, and allow desktop stop/restart to use the CLI recovery path.
Add supervisor heartbeats so supervised workers shut down when their supervisor disappears instead of surviving as orphaned reachable daemons.
* fix(claude): preserve alwaysLoad on MCP server configs
Paseo's Claude provider normalizes McpServerConfig via toClaudeSdkMcpConfig
before passing it to claude-agent-sdk. The function copied type/command/args/
env (stdio) and type/url/headers (http, sse) but dropped the alwaysLoad
field for all three transports.
Without alwaysLoad making it to the SDK, every MCP server's tools were
deferred behind tool search. In practice the agent never discovered them
via search and would respond 'no chrome-devtools tools available' even
with the server fully running and connected.
Add alwaysLoad to the McpServerConfig types and zod schemas, copy it
through in toClaudeSdkMcpConfig for stdio/http/sse, and add unit tests
covering all three transports plus the default-undefined case.
Other providers ignore the field, consistent with the existing pattern
where each provider normalizes only what its CLI/SDK supports.
* test(claude): drop ternary in toClaudeSdkMcpConfig assertion
The 'alwaysLoad' field is now declared on every branch of the
discriminated union returned by toClaudeSdkMcpConfig, so the in-operator
guard is no longer needed. Direct property access expresses the intent
without embedding a conditional in the assertion.
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
The daemon's bearer middleware gated every route except /api/health behind the daemon password (PASEO_PASSWORD), including /api/files/download. That route already carries a single-use, 60s-TTL, crypto-random download token that is only ever issued over the authenticated WebSocket, so the token is the capability for the route.
Requiring the daemon password on top of the token broke browser and Electron downloads: those trigger the download via an anchor navigation, which cannot attach an Authorization header. The cross-platform download store (packages/app/src/stores/download-store.ts) therefore sent no bearer, and the daemon returned 401 whenever a password was configured.
Add /api/files/download to the bearer-auth bypass list. The endpoint still rejects requests without a valid token (400 missing / 403 invalid), so it stays authenticated; it just no longer demands the password a second time. This also fixes every already-released client without an app update.
* fix(claude): respect profile models for built-in claude provider
The built-in Claude provider was the only one in the registry whose
profile `models` array was treated as additive on top of the hardcoded
first-party catalog. Every other built-in provider (and every custom
provider) replaces the runtime model list when `models` is set, which
matches the documented behavior in docs/custom-providers.md.
For users pointing Claude Code at a third-party Anthropic-compatible
gateway (Z.AI, Alibaba/Qwen, MiniMax, custom proxies, …) and curating
the picker with `agents.providers.claude.models`, the old behavior
leaked the nine first-party Claude models into the dropdown, making it
impossible to ship a curated list. This is issue #1299.
The fix flips the built-in Claude entry in `provider-registry.ts` to
match the other providers, so `models` replaces the runtime catalog
(including the settings.json-discovered entries surfaced by
getClaudeModelsWithSettings). The existing `additionalModels` field
keeps its additive semantics for anyone who still wants to append
entries on top of the first-party list.
- provider-registry.ts: drop the claude-only `profileModelsAreAdditive`
branch and align with the rest of the registry.
- provider-registry.test.ts: update the "append to runtime models"
expectation for built-in Claude to "replace runtime models" and add a
regression test that mirrors the issue scenario (hardcoded catalog +
configured profile models, expect only the profile models).
- agent.test.ts: make the "returns hardcoded claude models" hermetic by
pointing CLAUDE_CONFIG_DIR at an empty temp dir; the test was reading
the host's real ~/.claude/settings.json (which now contains MiniMax
env vars from the issue report) and leaking that into the assertion.
- custom-providers.md: correct the note about Claude profile models
being additive and document the new replace semantics alongside
additionalModels.
Closes#1299
* test(claude): drop explanatory comments from new tests
* refactor(claude): inject configDir into ClaudeAgentClient for test hermeticity
Greptile flagged the previous hermeticity fix on agent.test.ts: mutating
process.env.CLAUDE_CONFIG_DIR inside a test leaks the redirection into
any concurrent test in the same process for the duration of the try
block.
Thread a `configDir` option through ClaudeAgentClient ->
getClaudeModelsWithSettings -> readClaudeSettingsModels ->
resolveClaudeConfigDir instead. The constructor follows the same
injection pattern as `resolveBinary`, so the test passes an empty temp
dir via the option and no shared global state is touched.
- models.ts: add an optional `configDir` to
getClaudeModelsWithSettings, readClaudeSettingsModels, and
resolveClaudeConfigDir. Env var remains the fallback for production
callers.
- agent.ts: add `configDir?` to ClaudeAgentClientOptions, store it on
the instance, and forward it to getClaudeModelsWithSettings from
listModels.
- agent.test.ts: drop the process.env save/mutate/restore dance and
pass `configDir: emptyConfigDir` to the constructor instead.
Refs #1311