* feat(workspaces): record workspace creation parentage
Retain the caller workspace as informational ancestry for future sidebar nesting, independently of workspace placement and lifecycle.
* fix(app): preserve workspace parentage in replica cache
Paseo no longer checks these in front of the provider. A running agent's model or thinking
change no longer waits for the catalog or checks it, a plugin provider is sent every option
key and refuses what it cannot apply, and a draft setModel keeps the id as the model picker
does.
The fake providers listed one model and no thinking options, so once the daemon validated
selections against the catalog the live model and thinking switches had nothing valid to switch to
and skipped. Every fake provider now lists two models with thinking options, and the switches run
for all three providers without depending on installed binaries.
The daemon rejects a model or thinking option its provider does not list, so the live-preferences
e2e can no longer switch to a made-up id. A provider that lists nothing to switch to skips the case.
setAgentModel and setAgentThinkingOption checked the provider's global catalog,
while the picker reads the catalog of the agent's directory, so a model only
the project lists was rejected. The validation input now carries the directory.
The composer read the catalog once with no observer, so reconnect repair, which
refetches only active queries, never refreshed it and a failed load never
retried. While anything watches, the composer now observes the catalog query.
The shared composer map kept every agent's composer for the session. It is now
dropped when nothing watches it and no composer is mounted.
- `{ focused: true }` is the agent Command Center hands to agent items, published by its
registration through plugins/focused-agent.ts and empty outside a workspace. The composer source
reads that module instead of the remembered workspace selection, the navigation store and the
layout store, which also removes the require cycles those imports added.
- A composer the app retains in a workspace the user left has no live input. `insertText` and
`setPrompt` now store the text with `replaceDraftText`, which asks the mounted input to show it;
before, the text reached the store but the retained input never displayed it.
- `model` and `thinking` read exactly what the picker resolves before the catalog loads (no
models), then re-resolve when it lands, instead of reading null.
- The daemon validates a thinking option against the model the agent runs: the runtime model when
the provider lists it, else the configured one.
- The composer sources subscribe to query-cache updates only. A query another component creates
during its render is announced as `added`, and a subscriber that updates then made React warn
about updating a component while rendering a different one. A catalog read no longer starts its
load synchronously either.
useComposers(selector) in any plugin component and composers(selector) on every command context return the composer of a running agent, named by id or by focus, whether or not its tab is open.
A running agent now has one store-backed composer model apart from any mounted tab. The mounted composer plugs its cursor-aware insert in while mounted; otherwise insertText and setPrompt write the agent's saved prompt. model and thinking resolve through the provider catalog with the picker's own function, so a read equals what the picker shows.
The host reads one ComposerModel from context and no longer knows whether it is in an agent, a draft or New workspace. The daemon rejects an unlisted model or thinking option for a running agent, replacing the app-side guard.
The daemon marked every new branch-off worktree for automatic naming, recording the chosen branch
as the placeholder, so the first agent's prompt could rename a branch a plugin or client had
named through branchName. Only a branch the daemon derived from the worktree slug is a
placeholder now.
The loader reads the agent record, then queues a resume. A setMetadata
queued ahead of that resume saves a newer record, and the open hooks saw
it, but registration took metadata from the loader's older read and wrote
it back over the saved patch. Registration now uses the metadata read
inside the lifecycle lane.
Same handoff as agent.create.before: a legacy hook that returned only
{ source, ... } dropped the caller's metadata and anything an earlier
hook added, and workspace provisioning then defaulted it to {}. The
omitted field is carried forward at each handoff; an explicit {} still
replaces.
An agent.create.before hook that returned its own { config, env } dropped
metadata that an earlier hook in the chain had added, because only the
caller's original metadata was restored at the end. The omitted field is
now carried forward at each hook handoff, as agent.open.before already
does.
The agent.open.before result type also required metadata, so an older
TypeScript plugin that returned its own object no longer compiled even
though the daemon accepted it. The result type now makes it optional.
The result schema required metadata, so a plugin written before metadata
existed that returns its own object instead of spreading the request
failed validation and the session open failed, even for agents with no
metadata. An omitted metadata now means unchanged and is filled back in
from the request so later plugins in the chain still see it; a present
value that differs is still rejected.
The restart test now sets metadata through the PaseoApi handles and
checks that agent.open.before sees the patched value after the restart.
Plugins had nowhere to keep their own data on an agent or workspace, so
Phase 5 and 6 surfaces would each have invented a side store. Both now
hold an opaque Record<string, JsonValue> that Paseo stores, snapshots and
hands to hooks without reading a key.
Set at creation or changed with agent.metadata.update.request /
workspace.metadata.update.request (top-level keys replace, null removes).
Persisted on the agent and workspace records, so it survives a restart.
Reaches agent.create.before (which may add to it), agent.open.before
(read-only), agent.create.after and workspace.create.before/after.
Gated once on server_info.features.entityMetadata; the client throws
"Update the host" instead of sending to a host without it.
A plugin may run an option it never reports. Only a key it reported before can be
reported as removed, and a rewind reopens with the accepted request plus what the
plugin reported back.
Applying and unsolicited reports now share one conversion from the plugin's
report to a patch, so a key the plugin stops reporting is a removal on both.
The adapter only visited keys present in a new report, so a key the plugin
dropped to return to its default kept its stored value and its rewind value.
A previously reported key that disappears is now reported as removed.
The reopen merged the plugin's reported options over the launch record, so a
key the plugin reset by leaving it out came back at its launch value. The
session now keeps the committed options, removals included. The provider-direct
example also refuses a combined request before changing anything.
A plugin reporting `controls` and `providerOptions` receives every key a live
change touches, with or without a control, and applies them or rejects the
whole request. The daemon's live-key guard now runs only for a plugin still
reporting `settings`, which cannot refuse a key.
A control's `defaultValue` only says what the control shows for a key with no
value. Opening and applying store every option the session reports, defaults
and values nobody set included, as built-in providers do; the special case that
left a value at the default unset is removed.
A provider plugin reports `controls` and the `providerOptions` it runs in
`session.config`, and receives running-session changes as
`session.configure` `changes.providerOptions` (null returns a key to its
default), under the same apply-or-throw contract as built-in providers.
A control may declare a `defaultValue`; a value a plugin reports at that
default for a key nobody set stays unset instead of being pinned to the agent.
A plugin that still reports `settings` keeps working: the daemon reads each
setting as a control plus an option and sends changes in both shapes
(COMPAT(plugin-provider-settings)).
The app resolves a control's icon as a Lucide name when it is not one of the
built-in kebab-case names, so a plugin's icon shows live and in drafts.
Public docs and the provider-direct example use only `providerOptions` and
controls; the migration doc gains the rows.
Change a worktree workspace's base from the Changes pane. The choice persists and diffs, counts, commits and Git actions follow it.
Supersedes #6872.
Co-authored-by: Tianyu Yang <yangtianyu.cl@bytedance.com>
The stored snapshot projected only the options the agent owns, so an agent
that ran with a config.json default (for example Claude fast_mode: true)
showed the option as missing, and its legacy Fast control as off, until it
was resumed. The projection takes the same resolver the live agent uses
(the provider's defaults under the agent's own options).
A closed agent's snapshot is projected from its stored record, which kept
controls and provider options but dropped them from the payload, so the
app drew no speed or other controls for it. The stored projection now
carries them, with the legacy features derived from the same source.
The draft read the form's provider and the model's options separately, so
for the render after a manual provider change it asked the new provider
about the previous provider's options. The request is now built from one
snapshot of the model and is absent until the model has observed the new
provider.
The agent snapshot's `options` are the state and `controls` only draw them.
Running and closed agents draw from the snapshot; drafts and profile forms ask
`provider.controls.list.request` with their `providerOptions`, so aliases such
as a legacy Fast profile's `service_tier: "fast"` are resolved once, by the
daemon, and the draft keeps Fast highlighted. A draft adds only the user's
edits and each provider's last-used control values; applying a profile seeds
its non-control options (e.g. `sandbox_mode`) into the draft, and a profile
form saves them back instead of pruning to the controls it can draw.
A daemon without `server_info.features.providerOptionControls` still sends
`features` and `featureValues`. `LegacyProviderOptions` in the client package
is the one place that translates snapshots, draft listings, create and apply
requests, and daemon-config profiles, so the app never branches on it.
Persisted preferences and tab layouts rename `featureValues` through tagged
COMPAT preprocessors.
The features/controls bridge moves from the server into the protocol package
so the client can reuse it.
* fix(sidebar): clear the failed status once the error is read
Reading an errored agent already cleared its error attention, but the
state bucket also mapped lifecycle status "error" to failed. Lifecycle
only leaves "error" when the next turn starts, so the red dot and the
Failed group stuck until the agent was prompted again. The failed bucket
now follows unread error attention, the same way the green dot follows
unread finished attention; lifecycle status stays "error".
Because failed now depends on attention, a failure must replace unread
finished attention, otherwise a reload that fails on an unread agent
would show green.
* fix(sidebar): keep failed subagents red and let read failures be marked unread
The failed bucket now follows unread error attention, which broke three
places that relied on lifecycle "error" alone:
- Subagent track rows and provider-subagent tabs have no read state of
their own, so failed children lost their red mark. They now map the
child's lifecycle directly.
- A read errored workspace became "done" and offered Mark as unread, but
the daemon only accepted idle or closed agents. Marking it unread now
restores error attention.
- A reload that failed again while the lifecycle was already "error"
raised no new attention. A failed reload now raises error attention
directly instead of relying on a lifecycle transition.
Plugin sessions let two applyOptions overlap. The second finished and re-recorded the
settings the plugin held, which included a change the first had already made and not yet
reported; when the first then failed, nothing was left to report and the stored options
kept the old value while the plugin ran the new one. Applies now queue on the session,
the way the ACP adapter's do, so each reconciles against what the one before left.
OpenCode normalizes the legacy full-access mode to build with Auto Accept on, but the
session never said so, so the options the manager keeps did not hold Auto Accept and a
reopen turned it off. The session now reports it through openOptionsPatch on create and
resume.
* fix(app): hold the loading screen until first-run setup is decided
A new user saw the empty home, the sidebar and a logo jump before setup
popped in. Startup now keeps the splash while setup may still open over
the empty home, for at most 3 s and only while the setup host is
reachable. Remembered-workspace restore does not wait. Web dialogs now
fade in as they already fade out.
(cherry picked from commit a0a7620b62)
(cherry picked from commit 72d7c0bf26)
* fix(app): fade the dialog card in and land the dim at once
(cherry picked from commit 6ef05705a0)
(cherry picked from commit 71c28b431c)
* fix(app): keep the loading screen up while startup redirects
(cherry picked from commit 0b28d4cbff)
(cherry picked from commit 13cc485e6f)
* test(app): cover a setup host that drops while startup waits
(cherry picked from commit 2a906ef83e)
(cherry picked from commit 7b6e21207a)
* fix(app): type the web-only dialog enter animation style
The CSS animation props are not part of ViewStyle. CI typechecks without the
generated expo-env.d.ts, which declares them, so the cast is required there.
(cherry picked from commit b887d979d2)
* fix(app): keep the setup window mounted when the theme changes
The shell's appearance boundary remounts its dialog hosts whenever a theme
color changes, so picking a theme in setup, or a system light/dark switch,
replaced the open setup window and replayed its fade. Setup draws colors
through CSS variables and themed props, which update in place, and changes
no font size, so it now sits outside that remount.
The dialog enter fade is skipped with reduced motion, and its style is typed
for CI, which typechecks without the generated expo-env.d.ts.
(cherry picked from commit b24c6eacf8)
(cherry picked from commit cb0e9907f5)
* docs(changelog): prepare 0.15.2
(cherry picked from commit 5af1a8ff66)
* chore(release): cut 0.15.2
(cherry picked from commit e896c05b09)
* fix(plugins): install registry npm pins through the configured registry
Registry npm pins carry an npmjs.org tarball URL. Passing that URL to npm made it rewrite the host onto the configured registry and drop any path, so mirrors such as https://mirrors.tencent.com/npm/ returned 404. Install the pinned version instead and identify the reviewed artifact by version and integrity, since a mirror records its own tarball URL.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): address review on npm artifact comparison
Take an object parameter in isSameNpmArtifact, and clean up the mirror fixture when test setup fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(plugins): verify mirror artifacts with a warm npm cache
A shared npm cache lets the mirror regression reuse tarballs from earlier
fixture installations, so successful installs made no tarball requests.
Check the installed artifact's mirror URL, version and integrity instead
of requiring network transfers.
Co-authored-by: finofliu <finofliu@tencent.com>
---------
Co-authored-by: finofliu <finofliu@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
On 0.15.0 the daemon launches the detached terminal worker while it is
being created, and the worker refuses to start unless it can read its own
birth time and command line (Windows PowerShell 5.1 `Get-CimInstance`,
`ps` elsewhere). Any failure of that query used to escape
startWorkerTerminalManager and take the whole daemon down with
"Daemon failed to start (exit 1)", as two Windows machines hit after the
update: one whose PowerShell 5.1 cannot load CimCmdlets, one where
powershell.exe cannot be spawned at all. 0.14.0 never ran that query at
startup.
The launch error is now logged and the daemon starts without a current
worker. The pool already launches one lazily on the first terminal
creation, so that request reports the same error while the machine still
cannot run the query, and terminals come back once it can. The identity
requirement itself is unchanged: a worker without a verified identity is
never written or adopted.
The fork checkpoint check refused a fork when any projected row spanned the
selected reply. A plugin card re-appended with the same id keeps its first
position and stretches to its latest update, so it spanned every reply in
between, although the fork never copies plugin cards. The check now looks only
at the row kinds the fork copies, taken from the same constant as the copy.
Closes#8745
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A removal asks the select for its empty choice and compared the result against that
same empty choice, so applyOptions returned nothing and the manager stored the removal
as null while the session ran the Default choice. The difference is now computed against
what was asked (null), so the options hold the value the session runs with.
The Codex plan-mode end-to-end tests called the removed session.setFeature through an
optional call and silently skipped plan mode; they apply the option and fail loudly if
the method is gone. The MCP parity wrapper forwards listControls instead of the removed
listFeatures.
Two overlapping applies shared one transaction guard, so when the first was refused after the agent had moved a select, its report was dropped as 'still in a transaction' and the second saw no difference. Applies are serialized, as OpenCode's are, so each reconciles against the state the previous one left.
Draft discovery reported the options as requested, so a Fast configured as a legacy fast_mode or the fast alias read as Normal while creating sent the model's own Fast tier. The draft now reports the options the scratch session opened with, as registration does.
A write that edited a provider and a profile together folded the profile against a shallow merge that dropped the provider's extends, so its legacy Fast was not converted. The fold now sees the same deep-merged providers the write commits.
Profiles in config.json are old data entering, so the store converts them (stored value and incoming patch) with the same conversion the agent manager uses. Storage and clients now share one vocabulary: resetting a legacy Codex profile's speed removes what was shown, and a malformed fast_mode rejects the write instead of breaking later config reads. The config API goes back to copying the field shape only. The extends chain is followed by one function over either the registry's definitions or config.json's providers.
convertLegacyOptions read any truthy fast_mode as Fast, so the string "false" ran priority. It now accepts only true, false or null. The profile store no longer renames keys: a literal codex profile was renamed in storage while an extends-codex profile was not. The config API now converts each profile's options with the manager's adapter-aware conversion, so codex and my-codex profiles read the same.
OMP installed runtime state in several places and only one compared Fast; every state read from the runtime now goes through one install step. Claude kept a Fast default on a model without Fast, which made unrelated options reject until the user cleared it; the session now opens with Fast off and reports it in openOptionsPatch, as the model-switch path already does.
The approval-mode restart installed the new runtime's state without comparing its Fast result to the session's, so a refusal never reached options_changed, the remembered value, or storage. The replacement now goes through the same comparison as refreshState.
Draft controls and commands convert the config they hand to the provider, so inspecting a
legacy Fast preference no longer fails Codex's strict options. A profile naming codex converts
its saved fast_mode for the app's controls. OpenCode serializes auto_accept applies so a failed
one restores exactly what it changed, and a replacement OMP runtime starts from the Fast the
session opened with.
applyOptions committed the new flag before approving pending requests, so a failed reply left
the session auto-approving while the stored option stayed off. The flag is restored when the
apply throws.