Removing an option from a running agent drops the key from its stored and snapshot
options, but providers no longer revert to a starting value. The provider default
applies from the next open, and each control's default value shows the live setting.
This deletes the reset-to-baseline machinery in the plugin, ACP, OMP, Claude, Codex
and OpenCode sessions and keeps the options_changed reconciliation.
Also fixes two review findings:
- ACP startup overrides are reported once after all startup writes, so an
intermediate selection change no longer overwrites a requested option.
- A create hook that removes providerOptions or featureValues keys keeps them
removed instead of having the unchanged featureValues mirror restore them.
An options patch that never mentioned a setting reset it to its starting value whenever the provider had changed it on its own. The reset now applies only when an option the session held becomes absent, for ACP selections and plugin settings alike.
An ACP provider can answer a startup write with a different value. That option change was dropped because nothing was subscribed yet; the first subscriber now receives it, so the stored option matches what the provider kept.
Every options patch without fast_mode reset Fast to the value it had when the session started, so an unrelated patch undid a model family's own Fast setting. The reset now applies only when a fast_mode option that was held becomes absent.
OMP tracks Fast per model family, so switching models can turn it off. The session now reports that as an option change when a fast_mode option is held, instead of leaving the stored option true while the runtime reports false.
An agent stored with features and no controls showed its toggles as off and its selects as unset in a snapshot built while its runtime was unloaded, because only the default choice survived the migration. The recorded value is now the migrated control's unset value; an explicit effective option still wins.
When a plugin session declines configuration and is reopened with its settings, the reopen continues the native session the first open created instead of creating a second one.
An ACP provider response to a write that only resets an absent option to its default no longer emits an option change, so a removed option stays absent.
A plugin session that declines session.configure although the connection offers it is reopened with its settings in the open request, so the requested setting is not lost. Opening settings the plugin settles on are compared with the agent's options for every plugin, including those that cannot be configured.
An ACP provider notification that arrives before the response to a reset write no longer stores the default as an explicit override: while options are being applied, the requested options are the ones held.
A plugin that cannot be configured takes its settings in the open request as before; only a configurable plugin opens without overrides so its reported values can serve as reset defaults.
Option changes a plugin makes while its session opens are held and delivered to the manager when it subscribes, so the stored option follows the committed value. The reset defaults belong to the logical session and survive the session being reopened after a rewind.
A plugin only reports the value a setting has after it opens, so overrides passed in the open request left no default for a later option removal to return to. The session now opens without setting overrides and applies them afterwards, which records the default.
A plugin that completes a configure request but keeps its previous value reported nothing new, so the requested option was stored anyway. Config reports are now compared with the options the agent holds, not with the previous report.
- A plugin setting returns to the value it had before Paseo first changed it when its option is
removed. Plugin-reported changes are reported only for settings the agent holds an option for,
so removing an option does not bring it back.
- Claude accepts an unrelated options patch on a model without Fast while the saved Fast preference
stays dormant. Turning Fast on for such a model is still refused, and the runtime setting is the
preference filtered by model support.
- ACP reports a select the provider changed in a mode, model or thinking write response as
options_changed, the same as it does for config_option_update.
- ACP applyOptions leaves the session holding exactly the options it was given, so a removed
option stays absent and a later provider selection is not taken as a change to a stored one.
- Unattended-parent classification reads the parent's effective options, so auto-accept supplied by
provider configuration counts.
- setAgentOptions keeps the requested options as a candidate outside the agent's record. Provider
reports (options_changed) made while applying are folded into the candidate, and the record is
published and persisted once, after acceptance. A rejection leaves the record, snapshots and
stored copy as they were.
- ACP reports a select the provider changed on its own (config_option_update, or a write that
kept another value) as options_changed when the agent holds an explicit option for it, so the
stored option follows the provider.
- setAgentOptions runs in the agent's lifecycle lane, so overlapping patches to different keys
no longer overwrite each other. Options the provider reports while applying land on top of the
requested ones; a rejected update restores the previous record.
- ACP applies auto_accept after the select writes, so a rejected bundle leaves permission
handling as it was.
- A plugin provider's own setting changes are reported as options_changed, so the stored option
follows what the plugin reports.
- Stored-only agent updates carry the effective options, so metadata-only updates keep reporting
saved values and config defaults.
Reverts the storage migration from a987b4a43. The Codex speed control now takes its default from
the session's current options with the same rule the runtime uses (an explicit service_tier wins;
fast_mode: true applies only while service_tier is absent). This holds on create, resume, live
apply and for config.json defaults, and stored options stay as set.
A record holding `fast_mode: true` without `service_tier` ran Fast but the speed control showed
Normal, and picking Normal then wrote "default". The Codex session now normalizes the old boolean
at construction and reports the new shape through its existing options_changed event.
- Codex: drop displayOptions; a select choice may carry aliases so a saved Fast preference selects the model's Fast tier from options + controls alone.
- OMP: removing fast_mode restores the runtime's starting setting.
- ACP/Cursor removal tests run through real session initialization.
- agent.config.apply of a non-control option reaches the provider and shows in the next snapshot (daemon e2e).
- Daemon config store persists legacy-featureValues profiles as options on the next profile save.
- Removing auto_accept (OpenCode, ACP) turns live approval off; ACP selects return to their session default.
- agent.create.before hook featureValues edits override options the create already set.
- Claude commits fast_mode only after the runtime accepts it.
- Codex snapshots show a saved Fast preference as the tier the model resolves it to (AgentSession.displayOptions).
- Profiles are folded into options in the daemon config store; the config API still answers with featureValues.
- Snapshot options and provider.controls.list carry all effective options.
- Test: Claude fast_mode survives a restart through stored providerOptions.
Providers emit option controls over top-level providerOptions keys; the daemon stores one
options record per agent, merges config.json defaults as effective values, and applies patches
live via AgentSession.applyOptions. Legacy featureValues (wire, stored records, profiles,
schedules, hooks, MCP) fold into providerOptions; snapshots still carry features built from
controls. Snapshot options expose only keys that have a control.
Add AgentOptionControl descriptors, snapshot options/controls, an options patch on
agent.config.apply, provider.controls.list RPC, and server_info.features.providerOptionControls.
featureValues stays accepted on the wire (COMPAT).
* fix(pi): open an archived Pi agent's history after its folder is removed
Opening an archived agent's timeline resumes its provider for history.
Pi's resumeSession never received the resume purpose, so it launched the
Pi RPC process, and its MCP support probe, in the agent's stored working
directory. When that folder had been removed, for example with a merged
worktree, Node failed the spawn with "spawn <command> ENOENT" and the
history could not be read.
Starting the process elsewhere is not enough: with --session, Pi checks
the cwd stored in the session file's header and, outside interactive
mode, exits when that folder is missing. A history resume whose stored
folder is no longer a directory now starts Pi from the home directory on
a temporary copy of the session file whose header names that directory.
The copy streams everything after the header unchanged and is deleted
when the session closes. The agent keeps reporting its stored folder and
original session file, and the original file is not written to.
The directory check moves out of the ACP provider, which already starts
history loads from the home directory (#6228), into a shared helper both
providers use.
Fixes#7969
* test(terminal): keep the argv test child alive through its assertion
The POSIX argv test reads the terminal screen while its child can exit and dispose the parser. Keep the child alive until the assertion completes, as other screen tests do, and let the existing session cleanup terminate it.
Co-authored-by: Dominic Anaeto <dominic.anaeto@gmail.com>
---------
Co-authored-by: Dominic Anaeto <dominic.anaeto@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(server): close the upstream connection when a proxied client leaves
The service proxy piped the service's response to the client but never
destroyed the upstream request when the client went away. The upstream
socket stayed open and unread, so a Server-Sent Events service kept
writing into it and grew in memory. The proxy now destroys the upstream
request when the client response closes before it finished, without
logging that deliberate abort as an unreachable upstream, and a
WebSocket upgrade closes the service socket when the client socket
closes.
Fixes#8149
* test(server): publish terminal activity fixture atomically
File existence did not mean the child had finished writing its JSON. Publish the completed fixture by rename so a concurrent reader cannot parse an empty file.
Co-authored-by: Dominic Anaeto <dominic.anaeto@gmail.com>
* test(server): keep rollback listener ports distinct
Selecting both free ports before binding either lets the OS return the same port twice. Hold the intentionally occupied main port before selecting the standalone port so the rollback probe cannot reach the wrong server.
---------
Co-authored-by: Dominic Anaeto <dominic.anaeto@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(terminal): render emoji at their intended width
The native grid scales glyphs to the columns assigned by the terminal buffer. Use grapheme widths across current hosts and clients so emoji fit their slots and replay preserves cursor positions.
* fix(terminal): make width regression test portable
* test(terminal): wait for replay fixture output to finish
A trailing newline could arrive after the readiness marker and change the retained history between replay captures.
SendMessage resumes a finished Claude subagent under its original task_id
but a new tool_use_id, and the child's frames still name the original Task
call as their parent. In the same process the live source already maps the
new tool id onto the original row. After the session reopens, the child is
only known from its transcript, so the resume was declared as a new
subagent keyed by the SendMessage id: a second row, and every child frame
dropped as undeclared. That row never showed the model or the child's
reply.
Replay now hands the live source each restored child's task id, id and
presentation. The first task_started for that task id adopts the restored
row, so the resume takes the same path it takes without a reopen.
Co-authored-by: luke <luopeihuan@gmail.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Invalidate refresh satisfaction when subscriptions are released, and retry
if demand returns before the overtaken refresh completes. Otherwise the
same connection source can stay satisfied with no live list listeners.
Cover both demand sources and completion orderings with live delta checks.
Co-authored-by: Okan YILDIRIM <63511519+ragokan@users.noreply.github.com>
The web renderer does not map accessibilityState.checked onto aria-checked. Supply the checked property explicitly so accessibility clients agree with the checkmark and connection URI.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(server): forward a created agent's current state, not its pre-prompt copy
create_agent_request forwarded the snapshot createAgent returned when
registration finished, after the initial prompt had already published
the agent as running. Subscribers received that idle copy last, with an
older updatedAt, and providers that report nothing until the turn ends
left the agent listed as idle for the whole turn.
Forward the agent manager's current state instead.
Closes#8022
* test(server): drain agent persistence before fixture cleanup
---------
Co-authored-by: luke <luopeihuan@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): keep the open workspace visible in collapsed sidebar groups
A collapsed group showed its first seven rows plus working and
needs-attention rows, so an idle open workspace past the limit was
hidden behind Show more and no sidebar row was highlighted.
selectSidebarGroupVisibility now takes the route selection. When the
collapsed limit would hide the open workspace, it is returned as
leadingItem and rendered first in project groups, status groups and
both Pinned sections. Rows the group already shows stay in place,
expanded groups keep group order, and drag lists render the leading
row outside their sortable data, so a reorder never saves its borrowed
position.
Refs #8078
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(app): keep shortcut targets visible in collapsed groups
The navigation journey still required the selected overflow workspace to stay hidden. Assert its visible selection while preserving collapsed-group navigation and the seven ordinary rows.
Co-authored-by: 0xRoy <1997roylee@gmail.com>
---------
Co-authored-by: 0xRoy <1997roylee@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(server): report a Codex thread only once Codex can resume it
Codex writes a new thread's rollout at its first turn, but the adapter
starts a thread when the agent is created and reported its ID at once.
AgentManager stored that ID as the persistence handle, so a Codex agent
that was reopened before its first prompt (idle runtime release, daemon
restart, plugin tool reload) failed every prompt with "no rollout found".
The adapter now reports a thread's ID, as runtime info and as a handle,
only once it is resumable: after Codex announces the thread's first
turn, or when the thread was resumed or forked. Until then a reopened
agent starts a new session, as an unprompted Claude agent does.
Document that a persistence handle must be resumable.
* test(server): reopen promptless Codex agents without model requests
Exercise the real app-server before any first turn, so publishing an unsaved persistence handle fails at the actual resume operation.
Co-authored-by: Jeong-Uk,Kim <dnrtoki@gmail.com>
---------
Co-authored-by: Jeong-Uk,Kim <dnrtoki@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
The direct connection form treated any host containing a colon as a bare
IPv6 address, so entering "100.66.69.36:6767" or "tcp://host:6767" built
"tcp://[100.66.69.36:6767]:6767" and surfaced the browser's raw "Invalid URL"
error. Normalize the host input first: strip a leading scheme and any
path/query/fragment, split "host:port" (the inline port wins), and only treat
unbracketed hosts with two or more colons as IPv6.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Browsers omit navigator.clipboard outside secure contexts, such as the daemon web UI served over plain http on a Tailscale or LAN address. The non-Mac Ctrl+C/Ctrl+V terminal shortcuts then threw synchronously, and Ctrl+V had already prevented the native paste, so terminal copy/paste broke. When the Clipboard API is unavailable, the shortcuts now leave the default action alone so xterm's native copy/paste event handlers do the work, and the browser clipboard rejects instead of throwing.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(providers): wait for the ACP command inventory to settle
An ACP agent publishes its slash-command inventory in batches. MiniMax
Code sends its built-in commands first, then merges in every discovered
skill roughly 350ms later. `listCommands()` settled on the first batch,
so a session that had to discover its commands at creation time offered
the 10 built-in commands instead of the 396 the agent eventually
publishes. Every skill was missing from `/` until the session was
reopened and the cached inventory was read again.
Wait for the inventory to go quiet (600ms without another batch) instead
of for the first batch. An empty batch still settles immediately, and
`initialCommandsWaitTimeoutMs` (1500ms default) still bounds the whole
wait for providers that publish once or never publish at all.
* test(acp): verify late commands through the provider boundary
Exercise initial command discovery over real stdio, including a first batch cached before session creation returns. Keep the later skill and subsequent cached read in the same public session contract.
Co-authored-by: huazhongmin <peanut_hzm@163.com>
---------
Co-authored-by: huazhongmin <peanut_hzm@163.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(server): resolve ACP providers installed after the daemon started
Windows hands a new process the PATH its parent had when it started. The
daemon's supervisor therefore keeps the PATH it inherited at launch, and any
CLI installed afterwards stays invisible to it. A provider installed after the
daemon started reported "not installed" forever, even though the binary was
on PATH in a fresh shell.
Refresh the process PATH from the Windows user/machine registry environment
when the daemon worker starts. Existing entries keep their order and take
precedence; registry entries are appended. This runs once at startup so
PATH-isolated availability checks and explicit env overrides stay
authoritative.
Spawn the absolute path the launcher resolver verified rather than the bare
command. The resolver already finds installs outside the daemon's inherited
PATH (Winget packages, directories added after startup); the resolved path
was previously discarded.
An ACP agent that exits during the handshake never rejects the SDK's
initialize request, so the provider refresh waited out its full 120s deadline
and reported "Timed out refreshing <provider> after 120000ms; pending:
initialize" instead of the child's stderr. Race a child-exit rejection
against initialize so the real failure surfaces.
Also repair persisted MiniMax Code commands. The first catalog entry ran
`npx -y @minimax-ai/code@<version> acp`; npx derives the executable name from
the package name (`code`), but the package publishes `mcode`, so npm exited
before the ACP handshake. The catalog was fixed separately, but commands
already persisted by an older client were never migrated.
* fix(acp): report startup exits across probes and sessions
Extend the shared process failure handler so initialize rejects with stderr after the child exits, including session creation. Keep this fix scoped to startup error reporting; remove the separate PATH and persisted-command changes.
Co-authored-by: peanut_hzm <peanut_hzm@163.com>
* fix(acp): validate provider options before starting probes
Reject malformed options before creating a child or its process-failure promise, so cleanup cannot cause an unhandled rejection in the daemon.
---------
Co-authored-by: peanut_hzm <peanut_hzm@163.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(server): let a message steered into a running Claude turn be rewound to
A steered message never got a provider message id, because Claude takes it
into the running turn without echoing it back, so rewinding to it refused
with "Cannot rewind before the provider acknowledges the submitted prompt".
The steer admission now reports the uuid Claude minted for the message, and
a Claude conversation rewind to it forks at the entry Claude read it after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(server): keep messages steered into a running Claude turn in the replayed timeline
Claude records a steered message as a queued_command attachment rather than a
user entry, and the history replay skipped it, so every such message, replies
from other agents included, vanished from the timeline after a daemon restart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(claude): verify steer identities across lifecycle tests
Match the accepted-steer contract used by conversation rewind and verify that its identity names the message delivered to the provider.
Co-authored-by: tieo <65707274+tieo@users.noreply.github.com>
* fix(claude): release transcript streams after rewind lookup
Closing readline leaves a partially read transcript open after an early match. Destroy the owned stream on exit and cover repeated lookups with unread data.
---------
Co-authored-by: tieo <65707274+tieo@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* test(plugins): trace recurring global recovery failure
* test(plugins): control cleanup in global switch recovery
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* feat(composer): insert suggested commands into drafts
* fix(composer): close suggestions after command insertion
* fix(composer): place caret after inserted commands
* fix(app): open a nested-repo file when the workspace-root path is missing
A workspace that contains another git repo often gets paths written
relative to that nested repo. `schema/x.md` is really
`repo-a/schema/x.md`. A markdown link was joined onto the workspace
root and opened a missing file. Inline code already asked the daemon
for a suffix match.
Resolved workspace-relative paths that contain a slash now use that
same lookup before opening. An exact hit stays on the original path.
A suffix hit replaces it and keeps the line range. A failed lookup
still opens the original path. Bare names, absolute paths, and `~/`
paths are unchanged.
Example:
- Input: workspace `/repo`, link `schema/x.md`, file at `repo-a/schema/x.md`
- Before: open `/repo/schema/x.md` (missing)
- After: open `/repo/repo-a/schema/x.md`
* fix(app): preserve explicit destinations when finding nested files
Relative chat links can name a file inside a nested repository. Retain the suffix lookup while using the original href and document base to preserve absolute paths, preview links, and folder destinations.
Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com>
* fix(app): keep network share links on their absolute destination
Use the shared absolute-path classifier so network share hrefs retain their destination alongside drive and POSIX paths.
* fix(app): keep nested-file lookup inside ordered path opening
Resolve the original directory before considering suffix matches, and allocate click ordering before asynchronous lookup. The existing opener now owns the complete operation so a slow earlier click cannot replace a later selection.
* test(app): load UI modules before test lifecycle hooks
Cold module transformation exceeded the lifecycle hook deadline before these suites could run. Complete module initialization before registering the tests, retaining their assertions and timeout settings.
---------
Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Refresh direct plugin history from authoritative native persistence after conversation/combined rewind, matching the built-in providers' history refresh strategy. Reopen/replay rebuilds translation baselines, rewind tokens and child bindings while preserving outward subscriptions; file-only rewind stays unchanged.
Keep replay internal and replace the manager timeline after it completes. Preserve the committed native handle and close routing across refresh failures so retry and reload recover the retained branch.
The full native session reload is intentionally inefficient, pending a smarter timeline cut that restores translation state, reused IDs, children and permissions without reopening.
Closes#4667
Supersedes #4668 and the rewind-history proposal in #4754.
Verified with 72 targeted tests, green CI, and real OMP browser UI A/B/C -> rewind B -> replacement D with native history/timeline equality and a clean browser reload.
Co-authored-by: Matt Cowger <matt@cowger.us>
Co-authored-by: Omer Cohen <639682+omercnet@users.noreply.github.com>
* test(server): diagnose recurring config responsiveness failure
* test(server): compare profiled and plain config reads on Windows
* test(server): correlate config profiles with long callbacks
* test(server): probe config timing under CPU contention
* test(server): exclude filesystem pool startup from config budget
* test(terminal): finish output before comparing replay captures
A marker can arrive before its trailing newline. Comparing captures taken on either side of that output compares different terminal states. Split that output deliberately and wait for its parsed completion before capturing the expected replay.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>