mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-09 09:54:40 +00:00
279211e110ff2673fbf0ddd40ac42efa38e57f08
921
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
44b608b854 |
fix(app): stop showing Updating messages for a chat that has not changed
Every return to a chat fetched from the daemon just to learn nothing had changed, and the toast covered that fetch. The subscribe request that attaches a chat now carries its cursor, and the daemon answers current or behind in the same round trip, so an unchanged chat needs no fetch and shows nothing. The daemon attaches before it compares, so a commit landing in between is either delivered live or counted as behind. It never loads an agent to answer: an agent it has not loaded answers behind and resumes in the fetch, as before. The delay before an unanswered check shows the toast starts when the request is sent. A cold start's cache read alone can outlast the delay, which would flash the toast for a chat the daemon then reports current. |
||
|
|
b2b82e5cb0 |
Keep the running footer until a chat's messages are current (#9170)
* fix(app): change the turn footer together with its messages The footer followed the agent directory while the messages followed the timeline catch-up, so a chat returning to a turn that ended in the background applied the idle snapshot first: the running footer vanished or a completed footer sat under stale messages until the catch-up landed. The timeline now holds the turn its messages were captured during, and releases it in the same store write as the rows that bring the chat current. While held, the footer shows an updating state in the running footer's slot. The sidebar, tab and composer keep reading the true status. Losing the connection does not release the hold. No catch-up can run or fail until the host returns, and releasing it would put a completed footer under messages known to be behind. * test(app): expect the updating flag on submission-only activity * fix(app): keep a failed catch-up from restoring the updating footer A subscription failure could land while the chat's cache read was still pending. The failure released the held turn, then the read restored the turn saved with the messages, and no catch-up remained to release it, so the chat showed a sync error beside "Updating...". The replica writes the held turn from the cache, so it now also hears when a catch-up settles or is owed again, and a read in flight respects it. * fix(app): keep the running footer until a chat's messages are current A separate catching-up footer added a visible state nobody asked for. A chat that is behind now keeps showing the running turn it already shows, so the idle status changes nothing on screen until the messages land with the completed footer. The held turn carries its start time, and saves it with the cached messages, so the elapsed timer keeps counting from the turn's real start through the hold and on first paint from cache. * fix(app): let a newer turn's timer replace a held one on hosts without turn ids Old hosts report a null turn id for every turn, so comparing ids alone kept the older turn's start time and the held footer counted from it until the chat caught up. |
||
|
|
fdee02bdb6 |
Keep the composer's trailing action in place while typing (#9151)
The waveform and Send decided "is there content" separately: Send read the input's live text, while the waveform was gated on the draft store, whose web write lands after paint for typing performance. Between the two commits the toolbar showed both discs, moved the mic and context ring, or painted a disabled Send. MessageInput now picks voice mode, Send or stop from its live text. The composer only says whether voice mode is on offer. The focus hint reads the same live text, since the composer no longer rerenders when the draft gains or loses text. |
||
|
|
28c188513e |
feat: plugin API for agent options, metadata, composer and New workspace (#8744)
* feat(protocol): option controls, options patch, and provider.controls.list
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).
* feat(server): fold featureValues into providerOptions and describe composer controls
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.
* fix(server): address Phase 1 audit findings on option controls
- 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.
* fix(server): keep snapshot options as configured, restore OMP baseline, cover options through public interfaces
- 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.
* fix(server): read a pre-v0.10 Codex fast_mode record as service_tier "fast" and store it that way
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.
* fix(server): show Fast for a stored Codex fast_mode: true without rewriting the record
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.
* chore(website): regenerate the config schema for profile options
* refactor(server): providers report option differences once, after create, resume and apply
Providers used to write their own reports into the agent's options while a session was being
created, resumed or updated, which needed a growing set of reconciliation, baseline and
suppression rules to stay in step with the daemon.
The contract is now transactional. A provider opens or updates with the options it is given and
emits nothing while doing so; if the result differs it says so once, afterwards, as
`options_changed`. Live apply hands the provider only the keys the patch sets and the daemon stores
them onto the agent's current options when the provider accepts, so the last write wins. A change
the provider makes on its own is stored and forwarded, not undone. Removing an option drops the key
and sends nothing live. Controls carry no state; `options` is the only option state.
A legacy `agent.create.before` hook's `featureValues` edits are applied as a patch over the
returned options, so its deletions and replacements hold. The unreleased `aliases` field on option
choices is gone; a pre-v0.10 Codex Fast preference is reported once as the model's tier id.
* refactor(server): providers return what apply and open ended with instead of reporting it afterwards
applyOptions returns the options the session ended up with and the session
exposes its open difference as openOptionsPatch, so the manager stores the
patch and the provider's answer in one step. Removal is an ordinary patch:
the provider receives the default the key falls back to, or null, and returns
what it kept. Controls no longer take a default from a stored legacy feature
value.
* fix(server): apply an old app's profile featureValues over the profile's stored options
The unchanged app's profile form only knows the controls, so a name-only
edit dropped options it never shows, such as sandbox_mode. Its
featureValues now apply as a patch over the options already stored for the
profile (null removes a key), and a write with no featureValues keeps them.
* fix(server): ACP reports the selects it moved during open or apply as that call's result
A config_option_update that arrived before the write response was emitted
as an event on top of the result the manager stores. Notifications during
open or apply now fold into the open patch or apply result, and a rejected
apply reports what the agent moved since nothing is stored. Live options
are validated with the creation schema. Tests drive a stdio ACP agent.
* fix(server): providers validate live option patches and Claude reports Fast dropped by a model switch
Live patches go through the schema used at creation. A Claude model switch
that cannot keep Fast reports fast_mode as removed so the stored options
match the session.
* fix(server): Claude reports Fast as off, not removed, when a model switch drops it
A removed key lets a config.json default of fast_mode: true fill back in
while Fast is off at runtime.
* fix(server): fold an old app's profile featureValues by the provider and options the write carries
A write with options folds featureValues over those options. A write
without them patches the stored options, unless the profile moved to
another provider, which starts from no options.
* docs(server): a rejected apply reports what the provider moved after the call ends
* test(server): a patch key is resolved against nested configured defaults
* test(server): a setting a plugin changes on its own is forwarded as options_changed
* test(server): assert the forwarded plugin setting without nesting callbacks
* test(server): removal of auto_accept returns ACP to asking; Claude keeps opening restrictions across a patch
* fix(server): Codex returns the tier it resolved and drops a speed the new model lacks
A live service_tier: fast is stored as the catalog's Fast tier id instead of
a value the control cannot show, and a model switch to a model without the
stored speed reports Normal so choosing a supporting model again does not
bring the paid tier back.
* fix(server): Claude applies only the keys a patch sets over its current options
A Fast patch kept a copy of the options taken before the runtime answered, and
assigning it afterwards dropped a patch that landed meanwhile.
* fix(server): a plugin that moves settings and then refuses an apply reports what moved
* refactor(server): the legacy featureValues folds take one named-input object
* fix(server): a live patch is rejected for an option the session only reads at start, and opening reports every option it ended up with
A stored option has to describe what runs. OpenCode, OMP and ACP now reject a live change
to a key they apply only when the agent opens; Claude applies such a change from the next
turn and rejects it while a turn is running. Opening reports every select or setting the
session came up with, including ones nobody set, so a native default (Cursor opening with
Fast on) is stored as option state like any other.
* fix(server): Claude rejects a live change to an option it reads when the query starts
Replacing a started query to apply the option would end its background shells and
tasks. Once the query exists only Fast can change; before the first prompt every option
still applies, because it is read when the query is built.
* fix(server): a live options patch is rejected when the session cannot apply it
A session with no applyOptions (Pi) now rejects a patch instead of storing a value that
fails on reopen. Codex applies only service_tier, plan_mode and fast_mode live, since the
approval policy and sandbox are fixed when the thread starts. Claude also rejects a launch
option change while its first query is starting, after the options have been copied.
* fix(server): plugins reject keys they do not report as settings, and Codex opening reports the speed it runs
A plugin session applied the settings it knew and silently stored the rest; it now rejects
a patch that names a key the plugin does not report. Codex opening on a model without the
saved speed ran Normal while the stored options still said priority; opening now reports
the tier it runs, as a later model switch does.
* refactor(server): option helpers take named inputs, control types come from their schemas, and a plugin accepts launch options it opened with
A whole profile is applied by the unchanged app, so a launch-only plugin option whose value
has not changed must not fail the apply. applyProviderOptionsPatch and featuresFromControls
take one object, and the protocol control types are inferred from the wire schemas.
* fix(server): ACP reports a select that appears after startup with its native value
A model switch can introduce a select (Cursor Fast) that the session did not have at open.
Its value is now stored like any other native selection instead of being skipped because
the previous snapshot did not name it.
* fix(server): a loaded agent's options stay what its session opened with when a provider default is edited
effectiveOptions was recomputed from the current config.json defaults on every state emission, so editing a default changed the snapshot of an agent whose session never received it. It is now computed when the session opens and moves only through an apply or an options_changed. Editing a default affects agents the next time they open.
* fix(server): a live apply shows the merged defaults the provider was given in the options snapshot
The snapshot took the raw patch, so a nested patch over configured defaults dropped the defaults the provider had accepted. The snapshot now takes what the provider was sent plus what it reports back; stored options still keep the raw patch.
* fix(server): an option a session drops when opening is not brought back by a configured default
The opening snapshot merged defaults over the options after the session's openOptionsPatch had removed a key. Defaults now merge into what the session was given and the patch applies after.
* fix(server): Codex keeps the speed it opened with when the legacy Fast key is removed
Opening moves a legacy fast_mode into service_tier, so removing fast_mode afterwards changed the runtime speed to Normal while the stored options still said Fast. Removing the alias now changes no speed.
* fix(server): opening snapshots keep the defaults the session launched with, and config-change pushes carry legacy profile values
An opening resolved defaults once for the session and again at registration; an edit in between showed options the session never received. Create, resume and reload now resolve once, before the provider opens, and registration starts from that. The daemon_config_changed push skipped the featureValues projection that config replies apply, so the unchanged app lost a profile's legacy values when any setting changed.
* fix(server): every opening path hands registration the options it launched with
Registration took the launch options as optional and reread the defaults when a path did not supply them, as provider-session import did. It is now required, so create, resume, reload and import each resolve once before opening. The option helpers take named inputs.
* fix(server): a saved legacy Codex Fast preference wins over a configured service_tier default
The defaults merge put a configured service_tier next to the agent's own legacy fast_mode, and Codex reads service_tier first, so the agent opened Normal. The agent's own alias now replaces the default of the key it stands for.
* fix(server): Codex keeps a Fast chosen by alias as the catalog's tier id
A live fast_mode or service_tier "fast" was stored as the alias while the apply reported the catalog id, so a later switch to a model with a different Fast id ran Fast under an option that said otherwise. The session now stores the id it accepted and drops the folded fast_mode.
* fix(server): the legacy Codex Fast alias folds into one speed on apply and in configured defaults
A patch with both speed keys left the alias in the stored options, so a reload started Fast again after the speed was removed; the apply now reports the folded alias. A configured fast_mode default now supplies the service_tier default, so removing an agent's own speed falls back to the configured Fast like opening does.
* fix(server): an OMP replacement runtime starts with the Fast the session has now
A restart (for example an approval-mode change) restored Fast from the options the session opened with, turning back on a Fast the user had switched off. The session now keeps the Fast it applied or the provider changed, which is what a replacement reads.
* refactor(server): Codex's legacy fast_mode converts to service_tier where old data enters
Stored records, config.json defaults, profiles, older clients' requests, MCP settings and
plugin hooks go through convertLegacyOptions as they come in. Past that point the defaults
merge, the manager and the Codex session never see fast_mode for Codex, so the alias branch
in the defaults merge and Codex's fast_mode handling are deleted.
* fix(server): a legacy hook's replaced speed and an apply request's options convert before they are compared or applied
A hook that swaps the canonical speed it was sent for fast_mode is diffed in canonical form, so
its replacement is not read as a deletion. An apply request carrying featureValues and options
sends one options patch with options winning, so a superseded legacy value is never applied.
* refactor(server): the agent manager converts Codex's legacy fast_mode for the adapter behind the provider id
The rename is adapter-specific, so it lives where the provider id resolves to its adapter:
AgentManager converts an agent's own options on create, resume, import, reload, apply and
draft controls, following derivedFromProviderId, so a custom provider extending codex keeps
working with a saved or requested fast_mode. Storage, MCP, schedules, hub and profiles pass
options through. The featureValues-to-options fold stays provider-agnostic at the legacy edges.
* fix(server): a rejected OpenCode auto_accept leaves the session asking for permission
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.
* fix(server): legacy Fast reaches draft discovery and saved profiles, and overlapping or replaced options keep the state they committed
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.
* fix(server): an OMP replacement runtime that refuses the session's Fast publishes what it accepted
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.
* fix(server): OMP turn completion publishes a Fast change, and Claude opens with Fast off on a model that has none
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.
* fix(server): a malformed Codex fast_mode is rejected, and profile options are renamed for clients by the agent manager's conversion
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.
* fix(server): a profile is stored in the current option keys, converted for the adapter its provider extends
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.
* fix(server): profile options convert against the provider graph a write commits
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.
* fix(server): draft speed shows the tier the first turn will run with
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.
* fix(server): ACP option applies run one at a time
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.
* fix(server): an ACP removal reports the choice the select lands on, and the Codex plan tests enable plan mode
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.
* fix(server): plugin option applies run one at a time, and OpenCode reports the Auto Accept legacy full-access turns on
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.
* refactor(server): openCodeOpenOptionsPatch takes one named-fields object
* feat(app): the client and app work on options and controls
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(app): drafts and profile forms send every option layer to the daemon
A cold create against a daemon without providerOptionControls routed control
options (Claude fast_mode, Codex service_tier) as providerOptions, which that
daemon does not read for them. The client now lists the provider's features
before the first create or workspace create that carries options, so callers
never prime a cache.
Draft options moved into a plain model. Applying a profile replaces provider
and options in one step, so the form's provider change that follows no longer
clears a cross-provider profile's non-control options; choosing a provider by
hand still starts over. The model sends its complete options (seed, last-used
values once the provider says it still draws them, edits) to the daemon and
the controls draw only the answer, so a remembered legacy fast is drawn as
what it runs as. A toggle shows over the last answer until its own arrives.
The profile form does the same: its edits go through resolution too.
* fix(client): a legacy create routes its options from the controls of its own config
Which options a legacy daemon exposes as features depends on the model, mode
and thinking level, not only the provider: a Haiku snapshot with no features
says nothing about Opus. The provider-keyed memory of control keys sent an
Opus create's fast_mode as providerOptions, which that daemon does not read.
A create that carries options now lists the controls for its own draft
config and splits on that answer. The memory, and the snapshot and listing
hooks that fed it, are gone.
* fix(app): a closed agent shows its stored options, and a provider switch asks about its 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.
* fix: persisted options keep their full shape, and every legacy snapshot gains controls
Draft setups and form preferences validated option values as booleans,
strings or null, but the options they now receive come from agents and
profiles and can be objects, arrays or numbers. One such value failed
validation and wiped the whole saved layout or the saved preferences. Both
schemas now accept any JSON value.
The legacy-daemon translation skipped the snapshots carried by
agent_created, agent_resumed and timeline responses, so a created agent or
a timeline refresh left the SDK handle without options and controls.
The profile journey asserted the create request's options field; the wire
field is providerOptions.
* fix(client): a legacy profile with both featureValues and options keeps both
A legacy daemon keeps an unknown `options` field on a profile it stored
(profiles are passthrough), for example after a downgrade, while its own app
keeps editing `featureValues`. Reading such a profile took `options` and
dropped `featureValues`, so a Fast setting there was lost and the next save
replaced it. Both are merged on read with an explicit `options` key winning,
the same rule the daemon applies when it folds `featureValues`.
The draft form input of draftControlsRequest is a named type.
* fix(app): a draft stops sending a toggle the model in use does not offer
A toggle made on one model stayed in the draft's edits after the form moved
to a model whose controls do not include it, and was sent with the create.
On a legacy daemon that key is not a feature for the new model, so it went
out as a native provider option the daemon rejects. The draft now sends an
edit only while the latest answer draws a control for it, and sends it again
when a control for it returns.
* fix(server): a closed agent's options include the provider defaults, like a live one's
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).
* fix(app): a draft keeps sending an option no control has drawn
The previous change stopped sending any edit the latest answer drew no control
for, which also dropped options set without a control (a plugin setting a
token budget on the draft). Only an edit of a key an earlier answer for this
draft drew as a control stops applying when a later answer draws none; every
other edit always applies.
* test(server): the MCP test agent manager resolves effective options
* fix(app): a profile stops saving a toggle the model in use does not offer
Switching a profile from Opus to Haiku kept the Fast edit, so the saved
profile carried fast_mode that no control draws and an old daemon's
provider rejects at create. The profile editor now drops a key an earlier
answer drew as a control and the latest one does not, the rule the draft
already follows; options no control ever drew are never touched.
* fix(client): resetting an option on an old daemon writes the default it can take
setAgentOptions({ service_tier: null }) sent a feature write with value
null, which the old Codex setter rejects, leaving the paid Fast tier on.
A toggle now resets to off and a choice to its default; an option with
no control to name a default fails with the host-update message before
anything is written.
* fix(client): applying a config with a reset option on an old daemon writes the default it can take
applyAgentConfig({ options: { service_tier: null } }) reached an old
daemon as featureValues with null, which its Codex setter rejects. The
config now takes the same translation as setAgentOptions: a toggle goes
off, a choice goes to its default, and an option with no named default
fails with the host-update message before anything is sent.
* feat(plugin): provider plugins declare option controls and receive option changes
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.
* fix(server): a plugin provider's reported options are stored like any provider's
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.
* fix(server): a plugin decides which options it applies live
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.
* fix(server): a rewound plugin session reopens with its options as they stand
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.
* fix(server): a plugin that stops reporting an option has reset it
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.
* fix(server): an apply reports the options the plugin reset along the way
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.
* fix(server): an apply never reads a requested option as one the plugin dropped
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.
* test(server): an apply to a plugin that reports controls sends its changes as provider options
* feat(plugins): agents and workspaces carry plugin metadata
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.
* fix(plugins): an agent.open.before result may omit metadata
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.
* fix(plugins): omitted metadata in a hook result means unchanged
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.
* fix(plugins): omitted metadata in a workspace.create.before result means unchanged
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.
* test(server): fixtures for agents and workspaces carry metadata
Agent snapshots and workspace records now always carry metadata, so the
hand-built fixtures and expectations in three existing suites name it.
* docs(plugins): the hook result comment names metadata as the field that stays
* fix(server): reopening a closed agent keeps metadata saved just before
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.
* docs(server): name the metadata gap when a handle-less agent is recreated on load
* docs(server): the handle-less recreate gap spans the agent.create.before hook
* docs(plugins): an agent.open.before hook starts writes to its own agent without awaiting
An agent.open.before hook that awaits setMetadata (or any write) on the
agent being opened deadlocks: the write joins the agent's lifecycle lane
behind the open, and the open waits on the hook until it times out. The
hooks reference now says so and shows the supported shape, a write
started without awaiting, which runs right after the open.
Deferred, not in this stack: reject such a write immediately instead of
letting the hook time out. The host knows which plugin hook it is
awaiting for the agent and which channel the write arrives on, so the
rejection can be keyed on "this plugin is inside an open hook for this
agent" without cross-process tagging, and an unrelated client's write
would still wait as it does today.
* feat(plugins): composer items and controls in all three composers
A plugin registers a component with addComposerItem and reads and changes the
composer through useComposer(): provider, model, mode, thinking, options,
prompt, attachments and agent metadata. The agent, draft and New workspace
composers provide the same ComposerInput and ComposerAgent halves, so a
command reaches the right model without the plugin knowing which composer
it sits in.
Toggle, Select and Action are drawn by one ComposerControl renderer that
the thinking select and provider option controls now use too. Controls sit in
the toolbar when there is room and become rows in the existing overflow sheet
otherwise, so on a phone they appear in the overflow. Plugin items count in
the density presence so the composer condenses to make room. Model and mode
stay specialized but share the trigger, density and overflow.
Draft metadata is kept locally, sent at creation and reset when the prompt is
sent. PluginAgentSnapshot.metadata, deferred from the metadata phase, lands
here. The unused provider badge is removed.
vitest resolves @floating-ui/react-native to its ESM build: the CJS entry
requires the real react-native, whose Flow sources do not parse.
* fix(plugins): composer commands run on current state, report failures, and reach the overflow sheet
Draft commands read the state captured at render, so a plugin's
`await setProvider(); await setModel()` acted on a stale provider. The draft
form and options are now plain stores that commands read live, and
`setProvider`/`setModel` reject an unavailable provider or a model the loaded
catalog does not list. Draft `options` include the plugin's pending edits.
Shared composer controls own the pending state and show a rejected command as
an error toast, like the built-in controls. Callback payloads are objects
(`{ value }`, `onPress(event)`) so they can grow without breaking plugins.
The overflow sheet's content portals to the app root and lost the composer's
agent, input, toast and query contexts, so plugin items drew nothing in it.
`useComposerSheetBridge` carries them across.
* test(plugins): composer commands tested on the models, overflow sheet in Playwright
The draft composer's command rules (unavailable provider, unlisted model,
edits belonging to the provider the form has now) moved from the React hooks
into the form store and the draft options model. They take the host's catalog
as a plain input, so the command tests run on the real models with no module
mocks and no renderer.
The overflow-sheet test faked the portal it was meant to verify. It is
replaced by a Playwright spec that installs the composer-controls example and
checks its rows in the narrow composer's Features sheet; it fails when the
sheet's context bridge is removed.
* test(plugins): lock draft option ownership without a subscription, name the overflow-sheet steps
The draft option test subscribed the options model to the form, which in the
app happens in a render effect, so it passed without the model observing the
form's provider itself. A case with no subscription now fails when that
observation is removed.
The overflow-sheet spec reads as steps through a helper.
* fix(plugins): composer option resets and model checks match the documented commands
A draft kept `null` as an option value, so `setOptions({ key: null })` sent the null to the
provider and creation failed. A null edit now removes the key from every draft layer, as the patch
does on a running agent. A running agent's `setModel` accepted ids its catalog does not list; it
now rejects them the way a draft does.
The composer controls imported the toast host through the toast context, which pulled the host's
animation module into every module that loads plugins. They use the API-only context instead.
The migration guide's registration table gets the `addComposerItem` row its test requires.
* fix(plugins): draft metadata survives a remount, plugin commands follow the composer lock
Draft metadata was component state while the prompt lived in the draft store, so a composer that
remounted across the compact breakpoint kept its text and lost the plugin's choices. It now lives
in the draft store beside the prompt, is cleared when the draft is sent, and is not cleared when
the prompt is emptied. It is not persisted across app restarts.
While the New workspace composer is creating the agent, the built-in controls are disabled but
plugin controls and commands were not. They read the composer's own lock: Paseo controls render
disabled, and setPrompt, insertText and the agent commands refuse.
The options and metadata a plugin reads are frozen copies, through the same helper the timeline
projection uses. The composer control test mounted components in JSDOM, which the testing rules
ban; it now runs in the browser project.
* fix(plugins): a kept command honors the composer lock, and useAgent metadata is frozen
A plugin action that awaits an RPC holds the composer it was created with. The lock was captured
at that render, so a submit that started meanwhile did not stop its insertText. Commands now ask
the composer whether it is locked when they run.
The agent snapshot a plugin gets from useAgent froze only the outer metadata record. It now uses
the same deep frozen copy as the composer.
* fix(plugins): attachment commands honor the composer lock
The creation request captures the attachments when it starts, so an attachment a plugin added or
removed afterwards was lost or left in the request. They refuse like the prompt commands.
* fix(plugins): a built-in select in the compact sheet keeps its action name
The shared control named every icon-only select by its value ("Speed: Fast"),
including in the compact sheet, where the row already shows the value. The sheet
row is named by its action again ("Select speed"), as before the shared renderer.
* test(app): composer item test installation carries the subscribe main added to InstalledPlugin
Main's plugin streaming (#8527) made subscribe a required field of InstalledPlugin; the
composer item model test builds its own installation and predates it.
* feat(plugins): New workspace items set the branch and workspace metadata
addNewWorkspaceItem mounts a plugin field on the New workspace screen, and
useNewWorkspace() exposes host, project, isolation, base ref, branch and
metadata with commands that follow the composer item rules: they act on the
screen's state when they run, resolve once applied, reject while the screen
is locked, and take JSON payloads. Host and project are read-only because a
command that switched them would unmount the item that called it.
The screen's pickable state (base selection, manual isolation, branch,
metadata) moves into one plain store so pickers and plugin commands write to
the same place; the screen reports derived facts to it on each render. The
branch is sent as source.branchName only for a branch-off worktree, and
metadata on workspace.create.
Items render through ComposerControl with a new "field" surface (badge on
desktop, full-width field when compact). Combobox ships here: Paseo owns the
loading and error states, keeps previous results, and drops superseded
searches.
* fix(plugins): New workspace commands work from mount effects, and branch and base ref match what creation uses
A command called from a New workspace item's mount effect rejected because the
setup model only received its inputs and the pickers' handlers from a passive
effect on the screen, which runs after a child's. The model now takes its
inputs at construction, picks the base and isolation itself, and is refreshed
in a layout effect; the screen supplies the side effects of a pick (the PR
attachment, the remembered isolation) as listeners.
The branch a plugin sets was dropped when no base was resolved, such as on a
detached HEAD, because it was sent only for an explicit branch-off. It now
applies whenever the worktree cuts a new branch, decided in one place that
mirrors the daemon.
baseRef.name was the display-shortened branch name, so setting back what was
read based the worktree on the local branch instead of the origin one. It is
now the exact ref creation uses, and host and baseRef are frozen like project
and metadata.
* fix(plugins): New workspace commands have the same effects as a pick, even from a mount effect
The screen attached the pick side effects (remembering the isolation, syncing the picker's PR
attachment) as listeners in its own useEffect, which runs after a child item's mount effect. A
mount-time setIsolation("worktree") switched the screen but was not saved, and setBaseRef skipped
the attachment sync. The effects are now injected when the setup model is built, so a command and
a pick run the same code from the first render. Closing the picker stays in the picker's handler.
* fix(plugins): a Combobox shows a failed refresh instead of the old results
The query keeps its cached data when a reopened list fails to refresh, so the old items stayed
selectable and the error never showed. A failed search now clears the list so its error shows.
The migration guide maps addNewWorkspaceItem, which its registration-coverage test requires, and
the superseded-search test waits on a later search instead of sleeping.
* fix(plugins): a branch and metadata chosen for one project are not sent for another
The setup model outlives the project and host it was chosen for, so an issue picked for project A
was sent on workspace.create after the user switched to project B. The model now drops the branch
and metadata when the project changes (project ids are host-scoped, so a host switch counts). A
project that is still resolving is not a switch. The base selection is left alone: the built-in
picker keeps a branch pick across a project switch, and that behavior is unchanged.
* test(plugins): the project-switch spec expects the other project's metadata to be empty
* fix(plugins): a host switch drops the branch and metadata even when the project id repeats
A remote repository keeps the same project id on every host, so comparing the project id alone
missed a switch from host A to host B for the same repository. The target is now the host and the
project id together.
* fix(server): a branch the caller named is not renamed from the first agent context
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.
* fix(plugins): New workspace items remount when the project or host changes
An item stayed mounted across a project switch, and its Combobox kept the previous project's
results (same query key, cached data) selectable until the next search returned, so an issue from
the old project could set the branch and metadata on the new one. A Combobox cannot detect that
its inline search now means another project, so the item host keys the items by host and project
id. Items and their search results start fresh for the new target, and a mount effect sets up
again after the model clears the branch and metadata.
* docs(plugins): a layout-effect command on mount also races the reset on a project switch
* fix(plugins): a New workspace item's commands stop working once its project or host is left
Every item received the same command functions, which act on the current screen state. A lookup
that finished after the user switched projects could still call setBranch or setMetadata and
overwrite the new project's choices; the reset on a switch only clears what was already written.
The setup model now hands out commands per target with commandsFor({ serverId, projectId }).
They reject with a "moved to another project or host" error when the screen is on another target,
like the lock rejects, so the promise only resolves when the change took effect.
* feat(plugins): reach a running agent's composer by reference
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.
* fix(plugins): composers by reference follow the focused agent and reach retained composers
- `{ 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.
* test(plugins): a Command Center agent item reaches the composer through the real client state
The command-path test now reads workspace and agent through
createPluginClientStateSource over seeded session stores, so an empty
lookup, which makes the Command Center return before calling the item,
would fail it. Also records at the focused-agent publisher that it
publishes in an effect.
* fix(plugins): a running agent's composer validates and refreshes against the agent's own catalog
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.
* fix(plugins): useComposers holds the composer it names, and a composer lives as long as its agent
PluginComposersSource.subscribe watched the stores directly and never the
composer model, so a component using useComposers did not keep the model's
catalog observer alive and reconnect repair skipped it. subscribe now takes the
selector and holds the model it names, following focus.
Dropping a composer when it went idle left a command a plugin had saved calling
a dead model after the tab reopened. A composer is now dropped when its agent
leaves the session and nothing watches it or has one mounted.
* fix(plugins): a composer refreshes a stale catalog on resumed demand, shares its store watches, and is released with its last holder
Watching again, or reading once, after the catalog was invalidated or failed
while nothing watched now starts one deduplicated refresh; the observer
refetches on mount. Reconnect repair only refetches watched queries, so an
unwatched catalog stayed stale.
Each subscribe installed its own session and draft store callbacks, so N
watchers received N squared notifications, including for other agents' drafts.
One pair of subscriptions now serves all listeners, and the draft callback
ignores drafts of other agents.
A composer whose agent was removed while it was held was only released on the
next session change. The last holder letting go now checks too, and an archived
agent's idle composer is releasable.
* test(server): live model and thinking switches use values the provider lists
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.
* test(server): the test providers list two models with thinking options, and the live switches run
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.
* fix(plugins): a retained composer that is not showing takes plugin writes through the draft
On native a retained composer keeps its input mounted under display none, so the ref alone said
the input was live and a plugin insertText replaced the stale selection instead of appending to
the draft. The input is live only while its retained panel is showing.
* test(plugins): the retained input liveness test runs in real Chromium
* fix(plugins): a model or thinking change, an unreported option key, and a draft model reach the provider unchecked
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.
* fix(plugins): a plugin reporting settings is sent every key of an options patch
* Run option, model, thinking and mode changes in the agent's lifecycle lane
The agent manager owns all sequencing for one agent: applyOptions, setModel,
setThinkingOption and setMode now run in the same lane as open, close, archive,
labels and metadata, so a provider session never sees two of them at once.
Provider-side serialization (the applying chains in the ACP, plugin and OpenCode
sessions, and Claude's apply-over-current-options handling) is deleted.
* Reopen a handleless agent inside its lifecycle lane
A stored agent with no resumable session is now reopened by
AgentManager.createAgentFromRecord, which rereads the record inside the agent's
lane before creating the first session. A metadata or label write queued ahead
is in the record it reads; one queued behind applies to the live agent. The
loader no longer passes a record it read outside the lane. createAgent itself
also runs in the lane, like resume.
* Return the draft's options from AgentClient.listControls
listControls now answers { controls, options } like AgentManager.listDraftControls:
the options a session opened from the draft would run with, with config
defaults merged in and anything the provider resolves natively for the ones
nobody set. ACP fills unset feature selects from the probe session's
currentValue, so a fresh Cursor draft shows the Fast selection the agent opens
with instead of no selection. The legacy features response and the MCP draft
listing already project from the manager's result, so they follow.
* Send a profile's options on every write, empty included
The profile form always sends options. The daemon reads a write without them as
one from an app that predates options and folds featureValues over the stored
ones; a write that carries them, empty included, replaces them. A profile left
with no options is stored without the key, so removing the last one persists.
* Read a remembered Codex fast_mode as its service tier
The create-agent preferences convert the legacy key on read, keyed on the
built-in codex provider id, so the Codex draft shows the Fast selection the user
last used. Tagged COMPAT(featureValues) with the other legacy-option shims.
* Document one option change at a time per agent
The agent manager runs option, model, thinking and mode changes in the agent's
lifecycle lane; providers do no sequencing. The hook deadlock warning now covers
agent.create.before and option writes.
* Flatten the profile fold's base selection and drop an unused import
* Carry the New workspace context through portal hosts and the draft's metadata through retry
* Convert legacy options when resolving a stored agent's effective options
* Keep stored-record reads tolerant of a legacy option the conversion rejects
* Keep plugin attachment items separate from the draft's own
* Hand plugin command inputs to the host as its own copies
|
||
|
|
e4b6acb36d |
fix(app): show the archive undo toast in the sidebar, with only Undo pressable (#9104)
* fix(app): show the archive undo toast in the sidebar, with only Undo pressable The archive button sat 2px from the kebab, so a press aimed at the menu could archive the workspace. The toast card was a Pressable used only to pause on hover, which made the whole card look clickable. Toasts raised by the workspace list now land above the sidebar footer, next to the row that raised them. A collapsed sidebar stays mounted and its rows still handle the archive shortcut, so while it is hidden those toasts go to the app host. A toast leaves when the sidebar hides because its dismissal is what ends an archive's undo window. * fix(app): show late archive failures after the sidebar unmounts An archive keeps the toast API it started with. Unmounting the sidebar (focus mode, a layout change, a plugin view) left the ref saying it was visible, so a failure arriving afterwards went to the removed host and the user saw nothing. * fix(app): tighten the Undo shortcut and prove late archive errors in a browser The button's gap and the shortcut badge's own inset added up to 12px between the label and the keys. Late archive failures are now covered by a Playwright journey that swaps the sidebar while a real archive request is held, replacing the JSDOM component test. * test(app): prove late archive errors against a real daemon refusal The test answered the held archive request with a response it built itself, so no daemon ever failed. It now releases the held request naming a workspace the daemon has no record of, which is the one archive refusal a test can trigger without patching the daemon. |
||
|
|
148a394981 |
test(app): validate font families in one settings journey (#9146)
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
a9d48c6729 |
test(app): cover pairing confirmation in one journey (#9141)
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
27872a8e86 |
test(app): wait for the model before the live-pin journey (#9102)
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
b509d499f7 |
fix(app): show one workspace header above split panes (#9088)
#8740 moved the workspace header into every split pane so each pane could pick full or compact chrome by its own width. Every pane then repeated the title, project row, workspace menu and Explorer toggle, and narrow panes swapped their tab rail for the tab counter. The tab counter belongs to the single-pane layout. With two or more visible panes, the split container now renders the header once above them (full or compact by column width, without a counter) and every pane keeps its tab rail. A single visible pane, including focus mode, keeps the merged compact header with the counter when narrow. |
||
|
|
79717e2bd3 |
test(app): share the settings sidebar scroll journey (#9089)
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
22f50d4dfa |
fix(app): preserve unsent drafts when closing agent tabs (#9079)
Archiving removes an agent from the active directory without deleting the conversation. Preserve its local composer draft there too, and clear it only on explicit entity removal. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
2d7a115614 |
fix(app): keep the latest turn footer in the chat column on web (#9083)
At widths beyond the chat's max width, the latest turn's footer sat at the chat's left padding instead of under the message column, while running and after it ended. Earlier footers were aligned. #8358 wrapped the web viewport's live auxiliary (the latest turn footer and pending permissions) in a measuring div with no style. That div is a block box, so the footer row's alignSelf: center no longer applied. Give it the same flex column style as the stream rows. Fixes #9081 |
||
|
|
835e339516 |
fix(app): show one hover card at a time while scanning sidebar workspaces (#9071)
* fix(app): keep one hover card open while scanning sidebar workspaces Sweeping the pointer down the workspace list showed cards about three frames late, stacked two or three at once, and left some open after the pointer moved on. The card was positioned through an async chain (measureInWindow, then onLayout, then a position effect, then the portal's passive mount), so it painted at -9999 first. While unplaced, its safe-zone bridge covered everything above the trigger, so upward pointer moves kept cancelling its close timer. The previous card also stayed for the close grace period plus its fade while the next one opened. Now each overlay layer holds one open card, and opening another trigger closes it immediately. The card reads its trigger and overlay rects in a layout effect inside the portal, so the first painted frame is already placed. The grace timer still covers jitter and the gap onto the card. * test(app): name the hover scan workspaces so the resting row's card is identified |
||
|
|
fcde2072ab |
feat(diff): stream Changes revisions without blocking the socket (#8907)
* feat(protocol): define streamed diff revisions and resolve tag bases * feat(server): stream shared diff revisions with paced file bodies * feat(client): observe streamed diffs with atomic revision updates * feat(app): show the Changes tree while diff bodies stream * fix(server): retain the legacy structured diff payload budget * fix(diff): preserve revision ordering and readable files * fix(diff): select renamed patches and read dangling symlinks * fix(diff): preserve nested checkouts and ambiguous patch paths Only regular files and symlinks are readable untracked entries. Prefer patch metadata to ambiguous unquoted headers so paths containing a prefix delimiter keep their hunks. * fix(diff): preserve BOM text and complete file replacements Decode each string without consuming its leading BOM. File-type replacements emit multiple patch sections for one listed path, so retain all matching hunks. Assert the optional rename metadata in the existing commit-file journey. * fix(diff): highlight symlinks from their stored target text Working tree source context must match the symlink blob emitted by Git rather than the contents reached by following the link. Otherwise highlight runs do not cover the patch line and encoding fails the revision. * fix(diff): tolerate disappearing highlight sources Source context is optional after Git has produced the patch. Missing worktree link metadata must preserve the existing hunk-based highlighting fallback instead of failing the revision. * fix(diff): batch cold reads and refresh logical comparison inputs Acquire bounded pages of tracked patches lazily after Begin while retaining per-file highlighting and awaited frame delivery. Keep legacy preferred-base selection live across revisions and include submodule Git state in worktree cache identity. * fix(diff): distinguish gitlink directories from replacements A raw old gitlink mode also occurs when a submodule becomes a regular file. Query nested Git state only for a current directory, preserving normal file identity for replacements. * fix(diff): invalidate timestamp-preserving file replacements Size and modification time do not identify live file contents after a preserving copy. Include filesystem change time and file identity for tracked and untracked reuse, and verify live revisions agree with fresh reads. * fix(diff): align highlight tokens with normalized patch lines Git can normalize CRLF worktrees while highlighting reads their original line endings. Match tokens to the emitted patch text and trim only a normalization CR before encoding runs; mismatched source tokens are not attached. * fix(diff): refresh equivalent worktree paths across platforms Explicit refreshes must match filesystem-equivalent roots, including Windows path aliases. Use the existing realpath-aware matcher, budget real Git completion waits consistently, and assert optional status metadata in the filesystem commit-file journey. * fix(diff): validate Git inputs and resolve refresh roots Reuse now accounts for effective attributes, configuration, and canonical worktree blobs as well as filesystem state. Explicit refresh resolves repository identity like subscription creation, and callers await that resolution before acknowledging the command. * fix(diff): keep non-Git workspace refresh successful Diff invalidation has no target repository for an ordinary folder. Treat that expected absence as a no-op while preserving other root-resolution errors. * fix(diff): observe scoped Git interpretation settings Classify each metadata root with its worktree and common scopes, retaining both when the directories coincide. Shared attributes affect all checkouts, while config.worktree belongs to the owning checkout. Real subscriptions verify automatic binary and hunk-context refreshes in main and linked worktrees. * fix(diff): observe interpretation inputs for pinned comparisons Immutable commit objects still depend on mutable Git configuration and attributes. Declare those metadata inputs independently of ref, index, and worktree mutability, while keeping unrelated endpoint events out of pinned comparisons. * fix(diff): observe interpretation independently of comparison endpoints Commit comparisons still read worktree attributes. Watch those inputs independently, checking effective Git interpretation before invalidating fixed contents. Retain paths hidden by whitespace filtering so attribute changes can expose them. Forward binary frames through the branch-loading test interceptor. |
||
|
|
249c35891b |
fix(app): load History after a browser reload (#9029)
Consume host connection statuses in History instead of a version-only dependency that the React Compiler removes. Keep saved sessions reachable when the host connects after the screen mounts. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
ab8e1e61ff |
fix(app): keep the composer ready after clicking Send (#9015)
Pointer submission blurs the editor before sending and clears retained focus intent. Restore focus through the existing default send action for mouse-driven layouts, while keeping touch keyboard dismissal intact. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
9131505810 |
Fill short collapsed chat histories (#8358)
* fix(app): fill short collapsed chat histories Collapsed whole turns can leave the initial viewport short even when older history exists. Continue paging through measured pages before revealing a fresh chat, while preserving visible history and the reading anchor. * fix(app): settle padded and stalled chat history Measure the native history edge using the same content area as viewport filling, and bound child history requests so retry can start after a stalled fetch. * fix(app): adapt viewport filling to the current timeline * fix(app): resume history filling after switching tabs |
||
|
|
c6f0c24bf6 |
Make a folded turn's summary line its own chat row (#9009)
* fix(app): make a folded turn's summary line its own chat row The summary line of a collapsed turn was drawn inside its prompt's row, so the row gap landed below the summary and the prompt touched it. Turn collapse now emits a `turn_summary` row between the host and the reply, and one spacing rule covers every prompt: 16 above whatever answers it (reply, summary row or tool row). The summary row spaces like the work rows it stands for, 4 above the reply and flush under a tool host. This removes the per-row summary lookup, the host revision tracking and the fragments in the view. A fold reuses a turn's previous summary row object while its work and open state are unchanged, so mounted rows do not re-render. * fix(app): keep the turn summary row at its host's timeline position The chat outline resolves the active prompt from the timeline position of the row under the reading line. The summary row had none, so the outline lost its mark while the row sat there. The row now carries its host's position and is replaced when that position lands. * fix(app): keep the reader on a folding turn's host, not its summary row When an older page completes the oldest loaded turn and it folds, the reading position moves from the hidden rows to the turn's host row, as on main, so the prompt and its summary row both land in view. The pagination helper now finds the summary line in the row under the prompt. |
||
|
|
b7b38412a0 |
Keep turn footer timing within the available chat width (#9007)
* fix(app): keep turn footer timing within the chat width Let visible timing text determine its height within the available chat width instead of sizing an absolute overlay by character count. Keep actions anchored when duration and timestamp labels swap. * fix(app): keep footer timestamp hover target stable * fix(app): reserve both wrapped timing label heights |
||
|
|
a449271d57 |
Let plugins provide custom sidebar views across hosts (#8906)
* feat(plugin): expose stable sidebar entity data across hosts Collections return interned references and preserve their arrays when membership and order stay unchanged. Entity selectors share one hook with identity defaults for full snapshots and shallow defaults for selections. The app caches host projections and shares underlying store and host subscriptions. Browser render counts exercise the real session store and plugin provider without memoized rows. Terminal hooks consume the collection source port; daemon-wide terminal discovery is the separate terminal phase. * feat(plugin): let users select a sidebar view An exclusive view replaces the sidebar content while the host retains navigation, callouts, and footer. The device remembers the selection, unavailable views fall back to the default, and throwing views provide recovery. Device-scoped plugin settings persist locally with schema validation and revision conflicts. Web E2E and Android tablet QA cover selection, preserved chrome, disabled-view fallback, and error recovery. * feat(plugin): add sidebar view examples and ref actions Keep sidebar layout and gestures inside the plugin while routing entity handles to their owning hosts. Examples cover empty projects, pinned workspaces, task sections, nested agents and device-local label folders. * feat(plugin): observe terminals across connected hosts Share host-wide terminal observations across plugin surfaces and gate them on terminalCollection. Keep directory observations compatible, and carry creation time in worker v6 state so v5 terminals can drain with an unknown timestamp. |
||
|
|
0e40022519 |
Preserve linked resources when pasting prompts (#8972)
* fix(app): preserve linked resources when pasting prompts Rich clipboard links carried their destination only in HTML, while the composer let textarea default paste insert the plain label. Convert linked fragments to Markdown at ingestion and replace the selected draft range through the existing editor command. * fix(app): keep pasted links undoable while drafting offline Use the browser edit command for converted paste so selection replacement participates in Undo and normal input publication. Keep complete Markdown clipboard text when it already carries the link destinations, and limit connection gating to image uploads. * fix(app): preserve copied Markdown with normalized links Copy renders normalized URLs in HTML while retaining original Markdown in plain text. Compare parsed destinations using the same Markdown parser so pasting complete responses keeps tables and formatting intact. * fix(app): retain external linked tables when pasting prompts Recovering missing link destinations must retain table rows and columns. Use the existing table converter and keep cell delimiters and line breaks inside their cells. * test(app): name rich clipboard paste arguments * fix(app): preserve pasted links around rowless tables The table converter requires a first row. Convert rowless table content separately so empty markup cannot abort recovery of surrounding destinations. * test(app): wait for workspace restore before undo teardown The directory appears before git finishes recreating the worktree. Wait for the host to publish its active workspace before cleanup removes the source repository. --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
9f68af0274 |
Keep uploaded terminal file paths separate (#8946)
* M1: terminal input-sink bridge, upload button in tab-bar slot and key bar, picked-file routing, space-separated pasted paths * M2: terminal dictation: mic in tab-bar slot and key bar, recording pill and overlay, per-terminal recording lifetime incl. pending mic acquisition, Cmd+D on macOS by keyboard focus, i18n * M2: review fixes: retry attempt guard, cancellable native pending start, measured terminal slot width, compact-web Cmd+D for terminal dictation * fix(terminal): separate uploaded file paths when pasting Keep the remote file-drop regression and its test coverage from the contributor change, without introducing new terminal controls. Each uploaded path needs a trailing separator because adjacent quoted paths are one shell word. Co-authored-by: Sebastian Arena <sebastian@walkofcode.io> --------- Co-authored-by: Sebastian Arena <sebastian@walkofcode.io> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
ea30d2616a |
Restore Mermaid source toggles and bound inline previews (#8932)
* fix(app): keep Mermaid controls usable in inline previews * test(app): verify Mermaid controls through user journeys Keep source-toggle mechanics in diagram helpers and compare preview geometry with its screen and controls instead of requiring exact pixel heights. Co-authored-by: luke <luopeihuan@gmail.com> --------- Co-authored-by: luke <luopeihuan@gmail.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
a9bce9a18d |
Keep the chat still when an agent's turn ends (#8944)
* Land a turn's reply, completed footer, and tool summary in one frame The stream handler closed turn liveness as soon as turn_completed arrived, while the final reply waited in the frame-coalesced reducer queue. For one commit the turn was finished with no reply: the footer vanished and the timeline jumped twice, and tool turns folded into their summary later. The viewed timeline owner now takes stream events whole: a turn boundary commits the queued content before liveness changes. Running and completed footers share one fixed content height so the phase switch moves nothing. * Document the turn-boundary commit and stop racing the re-pin in the turn-open test The turn-open test scrolled the received message into view with a programmatic scroll, which leaves the chat following its end. Pressing the turn summary swaps its icon, and that DOM change re-pins the chat to the end between pointerdown and pointerup. The summary had moved 14 px, which left the pointer 2 px inside the button; with the completed footer now 2 px taller, it moved 16 px and the click landed on the reply below. The message's fade and Show more are already on screen at the end, so the test no longer scrolls. |
||
|
|
73448d483e |
test(app): scope command-center stability checks to the panel (#8927)
Document-wide layout shifts could fail a stable file-search panel. Count only shifts attributed to the panel or its descendants, and exercise both unrelated and in-panel movement in the existing browser journey. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
161076a853 |
Show loading and errors in the slash command menu, and stop defaulting to /exit (#8905)
* feat(app): show loading and errors in the composer command menu Rows are single lines with a kind icon, the matched part of the name, the argument hint, the description and a source tag. The detail card is gone. The menu used to stay hidden while commands loaded and then show "Error: ..." over a list whose options Enter could still pick, so a failed load plus Enter archived the agent through a hidden /exit. Loading and error are now status rows with no options; the error row names the reason and offers Retry. Loading appears after 300ms so a warm cache paints the results first, as before. An empty query used to select /exit. Provider commands now come first, so the bottom row the input selects is a provider command. useAutocomplete derives the selection during render. When results replace the loading row, the selection is valid in the same render, so the popover stays mounted at its anchor instead of remounting. Claude Code's `__`-prefixed commands (/__remote-workflow) only run in its own server-launched sessions, so the Claude provider no longer lists them. * fix(app): never default the command menu to a Paseo action An arrow-key choice was tied to the query it was made for, so returning to that query brought it back: arrow to /exit on `/`, type `com`, delete it, and Enter archived the agent. A choice now clears whenever the query changes. An empty query still selected /exit when the agent had no provider or plugin commands, because Paseo's actions were the only entries. With nothing else to list, an empty query now lists nothing and waits for a search; typing `/ex` still finds /exit. * fix(app): sort empty-query commands without toSorted Hermes has no Array.prototype.toSorted, so typing `/` threw "undefined is not a function" on iOS and Android and the app showed its error screen. Browsers and Node have it, which is why web and unit tests passed. * test(app): cover the stale command choice in Playwright and wait out the loading row on devices The renderHook test mounted React under JSDOM, which docs/testing.md forbids. The Playwright test drives the same sequence in a real browser and fails without the reset in useAutocomplete: Enter chose the stale /exit. The composer-keyboard harnesses treated a visible popover as a ready list. The popover now shows a loading row first, and a cold Claude command list takes several seconds, so both harnesses wait for the rows. * fix(app): size the command menu loading spinner to the row icons Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
616f5e155c |
fix(app): show branch loading and fetch errors (#8938)
* fix(app): distinguish branch loading from empty results Preserve branch query pending and error states through the switcher so first open does not report missing branches before the response. Show request failures with a retry action. * fix(app): keep branch retry progress and keyboard focus Verify failed cached refresh and keyboard retry through the real browser and daemon. Keep focus in the footer while Retry is disabled so repeated Enter cannot select a cached branch. * fix(app): preserve picker interaction when retrying branch loads Use the existing close-and-reopen retry behavior with clear feedback rather than introducing an action that competes with native list keyboard ownership. |
||
|
|
471db4bdb5 |
Keep workspace tabs predictable and clear attention on deliberate focus (#8935)
* fix(app): preserve workspace tabs and acknowledge deliberate agent focus Workspace navigation no longer chooses an attention agent. Preserve repeated focus actions so an already selected chat can be acknowledged, and stop consuming attention on departure. * fix(app): retain deliberate focus until fresh agent data arrives Connection readiness permits a pending focus acknowledgement without creating a new focus action. Keep that intent across client changes and wait for the current timeline synchronization generation before consuming it. * fix(app): preserve reconnect feedback during attention acknowledgement |
||
|
|
ae157dbfb6 |
Even out chat footer spacing and show the Changes base only on worktrees (#8934)
* fix(app): space the turn footer like the rest of the timeline The footer sat 13px under the last line of text while the text sat about 21px under a tool or thinking row, so the footer looked glued to the message. The message drops its bottom padding when a footer follows, so the footer's top margin alone sets that gap; 20 makes the centre-to-centre distance match the tool-to-text gap. * fix(app): show the Changes base only on Paseo worktrees The header rendered "branch -> base" whenever checkout status carried a baseRef, and the daemon sends one for every git checkout, so plain checkouts showed "-> main" with no way to change it. Only Paseo worktrees store a base, so only they show it. The arrow is extra muted so it reads as a separator, and the base no longer shrinks, so a long branch name truncates instead of the base. * fix(app): let a long Changes base truncate instead of overflowing |
||
|
|
dc51a3b6a9 |
fix(app): preserve plugin-rendered replies when collapsing turns (#8811)
* fix(app): preserve plugin-rendered replies when collapsing turns * test(app): exercise plugin reply folding in the browser |
||
|
|
77798b2e46 |
Show and stop agent background work (#6220)
* feat(server): add an in-memory store for agent background work Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(claude): report background tasks as background work Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(server): track agent background work in the agent manager Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(protocol): add agent background work messages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(server): serve and push agent background work Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(client): expose agent background work on agent handles Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(sdk): document agent background work Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(claude): cover background shells in background work end to end Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(client): resync background work subscriptions on every snapshot subscribe() now lists the agent's background work each time the event observation is established, so the listener starts with the current list and catches up after a reconnect. A list answered after a newer update is dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server): answer background work lists without loading dormant agents Background work lives only in memory, so a stored agent that is not loaded has none. The list handler now answers [] from storage instead of resuming the agent, and still rejects unknown, internal, and archived agents. A new test pins that old apps never receive agent.background_work.update. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server): read Claude background tasks defensively The user's claude binary can differ from the SDK typings, so a task's description is read as unknown and an empty or missing one becomes null. Drop the monitor kind: Claude Monitor watches report as local_bash, so kind is "shell" or "other" today. The store now copies only the wire fields of each input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(server): prove internal background work was emitted before checking globals Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(sdk): describe background work resync and feature gating Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(claude): cover background work reported by a retired query The daemon stops reading a Claude query it no longer owns, so work that a retired process still had queued cannot reach the background work list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(client): let background work subscribers handle a failed list read When the list read that follows each subscription snapshot failed, the error was only logged and the caller got neither the list nor a failure. subscribe() now takes an optional onError that receives it; without one the error is still logged. The read is not retried, since the next reconnect reads the list again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(agents): show background work as active Keep background activity visible after a foreground turn ends while preserving independent send and interrupt behavior. * fix(agents): protect background work subscriptions Keep internal work out of public updates and restore observation after a subscription failure. * feat(agents): show Codex and OpenCode background work Keep provider-owned shell activity visible after a turn ends while leaving foreground turn controls independent. * feat(agents): stop individual background work Wait for provider confirmation so stopping one background item leaves sibling work and foreground turn controls independent. * refactor(agents): name background stop parameters * test(website): isolate browser tests from live metadata * test(agents): clarify background stop journeys * fix(sdk): report background work subscription failures Report setup failures so subscribers can replace an observation that the host released. * fix(app): restore background work after reconnect Wait for the host handshake before selecting the retained client so an offline pane starts observing when the connection returns. * fix(agents): retain background shells across rewind Preserve known live shell ownership when conversation history is rewound, and revoke it when the provider runtime exits. * fix(website): launch test seeder across platforms Invoke Wrangler through Node so local website test startup does not depend on an executable npm command shim. * feat(agents): clarify background task details and tracks Keep launch descriptions and commands distinct so background work remains identifiable, and allow composer tracks to scroll when space is limited. * fix(agents): preserve active background work during archive Keep background-running children out of bulk cleanup and warn before an individual archive. Preserve first taps on composer tracks while the keyboard is open. * fix(codex): retain background terminals across rewind Keep live terminal inventories attached to their originating threads so a fork does not hide or misroute their stop actions. * fix(codex): scope background terminal identities to threads Prevent commands on separate threads with the same provider process ID from sharing a stop target. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> |
||
|
|
6c1b48d296 |
Archive a workspace from its sidebar row, with undo (#8904)
* feat(app): archive a workspace from its sidebar row and undo it Hovering a workspace row on desktop shows an archive button next to the row menu. Archiving shows a toast with Undo and a Cmd/Ctrl+Z shortcut so a mis-click costs one keystroke instead of a trip through History. The daemon serializes archive and undo per workspace across every connection. An undo that lands while an archive is still tearing down aborts it at the next checkpoint and restores only the agents that archive took down; deleting the worktree directory is the point of no return, after which undo recreates the worktree. * fix(server,app): close archive and undo races found in review - An archive request now claims the workspace's archive queue before looking up the workspace, so an undo sent right behind it cancels it instead of running first and losing to the archive. - A repeat archive of an already archived workspace no longer wipes the agents the earlier archive saved for undo. - Undo no longer brings back an agent that was archived before the workspace archive and only loaded to read its history. - Each local archive or undo request owns its own intent token, so a late response to an earlier request cannot release a newer request's hold on the row. An archive canceled by another client's undo puts the row back. - The undo failure toast shows translated copy; the cause goes to the log. * fix(app): keep a workspace's layout when another client restores it during the undo window The undo window purged tabs and layout whenever it closed after a finished archive. If another client restored the workspace before this client's toast closed, the restored workspace lost its saved arrangement. The archive now commits only if the workspace is still gone when the window closes. * fix(app): show the archived toast's Undo as a button * fix(app): keep a workspace's agent tabs when it is opened while its undo runs * fix(app): keep a row whose undo is running when a directory snapshot omits it |
||
|
|
4b285a113d |
Space the touch composer toolbar by what it paints (#8909)
* feat(app): tighten the touch composer toolbar onto the text column
On touch, the toolbar's icon-only glyphs were 20pt muted outlines at a 1.5px
stroke in loosely spaced slots, and the row sat on a flat -6 margin, so the
attach + and the trailing control floated inside the text column. Touch now
places 44pt slots edge to edge, draws icon-only glyphs at 24pt with a 2px
foreground stroke, keeps label-paired glyphs at 20pt, and runs the context
ring at 26pt with a 3px stroke. The row overhangs into the composer padding
by each end control's ink inset (composerToolbarOverhang), so the + ink and
the trailing glyph or send/stop disc sit on the text column's edges.
Pointer sizing is unchanged.
The folded model control now drops its thinking label before the model
label truncates, and the model label truncates on a word boundary
("Ten second…") instead of leaving a space before the ellipsis. Both are
decided from measured widths. The web reports layout widths rounded to whole
pixels, so a tie counts as not fitting.
* fix(app): measure replacement model labels instead of hiding them
The folded model label and TailEllipsisText kept their measuring views mounted
when the label text changed, while discarding the cached widths. A retained
measuring node whose size does not change emits no new layout event on web or
native, so after switching to a model whose label (or a shortened prefix of it)
has the same width, the widths stayed unknown and the label stayed transparent.
Keying each measurer by what it measures remounts it, so every width is
measured again.
Also move the label width sweep into a named helper so the test body states
the expected label order.
* feat(app): space the touch composer toolbar by what it paints
- Lay each control out at its painted width, 20pt apart, with a disc-sized circle that is both its frame and its fully rounded pressed highlight. Pickers are 36pt pills.
- Match the header icons: 20pt glyphs at 1.5px, an 18pt context ring at 2px.
- Always end the row in a disc (voice mode, send or stop) so it does not shift, nested into the composer's corner, with a concentric 26pt corner radius on touch.
- Stop the picker row's clip from flattening the picker highlight's left edge.
- Budget the density ladder on laid-out widths.
* fix(app): disable the stand-in send disc and keep form model fields in their box
- An empty new chat's stand-in Send is disabled until there is content, as in fullscreen.
- A custom model selector trigger, such as the schedule form's field, drops the toolbar slot's margins.
|
||
|
|
1e2ab3d596 |
Keep the first prompt on screen when a new agent starts on Android (#8898)
* refactor(app): stop resetting the chat timeline when its agent id changes A mounted timeline only ever shows one agent: every host keys its panel by tab id, and a tab keeps its id across a target change only when a draft becomes the agent it created. The per-agent resets inside the timeline therefore never had a different agent to reset for. They only fired at that create handoff, where they hid and re-placed a timeline that was already on screen. Delete them: LegendList's dataKey and the placed agent id, the web viewport key, the bottom anchor's resetForAgent, the history window's agent id, and the expansion and path-opener resets in the stream view. * fix(app): keep one chat timeline across the draft-to-agent handoff A draft tab and the agent its first prompt creates rendered two different panels: DraftPanel mounted its own dock and list, and the retarget swapped it for AgentPanel's. On Android, with the LegendList timeline, the swap blanked the first turn and re-placed it. Render both from the agent panel. A draft supplies its timeline from the pending create in the create-flow store (composer/draft/timeline.ts) and its composer in the composer slot. The retarget then re-renders the same stream view with the agent's timeline, so the list instance and its measured rows stay mounted. Agent gating is unchanged. Float the compact header over draft tabs as it already floats over agent tabs. The header sat in flow above a draft and over an agent, so the list viewport grew 92 dp at the retarget and the turn moved. The handoff E2E now fails if the first row disappears, hides, or shifts down while the draft becomes the agent. * fix(app): load subagents and tasks only for an agent, not a draft tab |
||
|
|
8b9c42c266 |
Keep every sidebar footer action reachable in a narrow sidebar (#8885)
* fix(app): move sidebar footer actions that don't fit behind a caret Touch sizing makes each footer icon a 44pt target, so five of them plus the row's padding need 236pt. A docked sidebar can be 200pt and a fold column about as narrow, so Settings was cut off at the sidebar's edge. The row now measures itself and keeps as many full-size buttons as fit; the rest move behind a caret that opens a menu-engine popover. Help and Usage leave first, Settings and Add project last, and Hosts and Help open as pages of the overflow menu rather than second popovers. * fix(app): keep the sidebar footer empty until it has measured Laying the footer out before its first onLayout put all five buttons in the row, so a narrow sidebar painted Settings clipped for a few frames before the caret replaced it. The unmeasured row now places nothing and holds its height, so the first painted row is already the measured one. * fix(app): keep the footer overflow menu on a narrow, short screen The overflow menu asked for 240pt, and edge clamping can move a surface but not shrink it, so on a 220pt screen its right edge and submenu arrows hung off the display. Anchored surfaces now cap their width to the display inside the same edge padding that clamping uses. The Hosts page lists every host, so the overflow menu is scrollable: on a short screen the last hosts and Add host were below the fold with no way to reach them. |
||
|
|
a68dc55490 | fix(app): refresh visible usage summaries periodically (#7806) | ||
|
|
3ed57e9dd7 |
Change a worktree workspace base from Changes (#8642)
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> |
||
|
|
e9f897a66a |
fix(app): rest the iPhone composer at the screen's bottom clearance (#8823)
* fix(app): rest the iPhone composer at the screen's bottom clearance The composer sat 50pt above the bottom edge on every iPhone: the dock padded the rectangular home-indicator safe area (34pt) and the composer stacked another 16pt under it. On iPhone Duo that safe area is also taller than needed; iOS 27.1's bottom-bar layout guide, which follows the screen's corners, rests 26pt from the edge. The native window-layout probe now publishes that clearance, and WindowLayoutProvider overrides the safe-area context's bottom with it, so every useSafeAreaInsets() consumer reads one bottom value and null falls back to the system inset. The docked composer applies max(bottom, 16) once and keeps 16pt above the keyboard through the translate view's transform; the gap is not animated as padding because layout updates through the keyboard transition are expensive on Android Fabric. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep the Import session gap under the wide new workspace composer The composer's own bottom spacing used to separate it from the Import session pill on the wide form. With that spacing moved to the dock, the centered slot padded below the pill instead, so the pill touched the composer. The pill now owns its gap above. Co-authored-by: Elsa Granger <zeyugao@outlook.com> --------- Co-authored-by: Elsa Granger <zeyugao@outlook.com> |
||
|
|
cdd7800fed |
test(app): wait for workspace hover cards to settle (#8846)
Keyboard focus can briefly mount the next card while the previous card closes. Poll uniqueness and visibility together so the assertion tolerates that transition but still rejects persistent duplicates. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
902d43da7c |
chore(release): reconcile v0.15.2 into main (#8845)
* 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 |
||
|
|
5d1a2c10f8 |
test(app): wheel the thinking group into view before opening it (#8791)
The "reasoning follows Expand thinking (false)" spec clicked a tool-call group above the viewport. Playwright's scroll-into-view is programmatic, and the web timeline only leaves sticky-bottom on user input (wheel, key, touch, scrollbar), so it snapped back to the bottom while the click was in flight and the group never opened. Wheel the group into view first, reusing the helper the timeline pagination specs already use for prompts. |
||
|
|
2d5b8eb1fd |
Handle web terminal finger scrolling once (#8776)
* fix(app): scroll web terminal history with a finger swipe A phone browser claims the vertical drag and drops it, because the canvas covers the scrollable viewport and the page itself does not scroll. Keep the gesture in the touch handler and move one line per cell. * fix(app): handle terminal finger scrolling once Keep the vertical gesture in the existing terminal handler so xterm does not also scroll it at document level. Retain only the confirmed propagation correction from the contributor patch and cover the actual phone-sized app with trusted touch input. Co-authored-by: sino <sino2322@gmail.com> * fix(app): let xterm own terminal touch input Remove the duplicate gesture implementation so history scrolls once and terminal applications retain arrow-key and mouse-report input. Cover live application modes with trusted touch gestures and a real raw-stdin process. --------- Co-authored-by: sino <sino2322@gmail.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
ac775a2904 |
Reveal collapsed navigation as a touch overlay on wide layouts (#8739)
* feat(app): swipe open collapsed tablet navigation Reuse the retained sidebar and mobile panel motion for a partial-width navigation overlay when the wide dock is hidden. Preserve the dock preference, project gestures through the sidebar width, and dismiss the overlay when revealing workspace content. Reset the animated frame width explicitly across dock transitions and keep the resize origin on the UI thread. Validated on Duo portrait and landscape, iPad portrait and landscape, iPhone full-width navigation, and browser sidebar interactions. Typecheck, lint, targeted formatting, and 31 relevant tests passed. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): refine touch navigation overlays Enable navigation swipes only on touch-capable runtimes and reject mouse and pen input per gesture event. Allow swiping from anywhere in the workspace instead of requiring an edge start. Cover the full application window with the backdrop, including safe-area padding, and remove the wide overlay's inline close button. Preserve swipe, backdrop, and destination-selection dismissal. Validated interior swipes on Duo and iPad, mouse exclusion in a browser, and full-window dimming and dismissal in Duo portrait and landscape. Typecheck, lint, and targeted formatting passed. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * refactor(app): let the panel store drop the overlay target on dock Docking the sidebar, or any layout that no longer presents it as an overlay, now drops the transient agent-list target inside the panel store. App chrome publishes one left sidebar presentation (overlay, dock, hidden) instead of an effect in SidebarChrome watching flags. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * refactor(app): answer fine pointer and touch screen from one module Merge use-fine-pointer and use-has-touch-screen into hooks/pointer-capabilities, which reads both answers from the media queries it subscribes to. The touch answer now comes from (any-pointer: coarse) instead of navigator.maxTouchPoints, which that query's change events never reported. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep the phone web drawer on its edge swipe Restore the 32px left-edge start for the compact web navigation drawer, so interior rightward swipes over terminals, code blocks, and diffs stay with their horizontal scrollers. The wide navigation overlay still opens from anywhere. The edge rule moves into resolveMobilePanelGestureIntent so both drawers share it and it has a test. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * refactor(app): one sidebar handler per destination Settings, host settings, add host, add project, and every navigation row now use one handler that closes the navigation overlay before it navigates. Closing is idempotent, so the docked sidebar shares it and the overlay ? mobile : desktop handler pairs go away. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * refactor(app): resolve the left panel width in one place MobilePanelsProvider now resolves the left sidebar width (full window on compact, the saved width clamped to the safe viewport on wide) and exposes it through the panel runtime. The open and close gestures, the overlay frame, and the docked sidebar all read that value, so gesture travel and panel width cannot disagree. MobileGestureWrapper no longer recomputes it, and the panelWidth/width parameters go away. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * docs: fold the wide navigation overlay into the mobile panel rules Move the facts the overlay PR appended into the sections that own them: the destinations intro, store ownership of the agent-list target, the projection widths, gesture pointer and edge rules, and the sidebar presentation bullets. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep the sidebar pans live across dock and overlay switches On an Android tablet, opening the dock with the header toggle and collapsing it again left the touch swipe unable to open the overlay until the app reloaded. Swipe-to-close from the sidebar also stopped working after the same toggle cycle. Both pans took the dock/overlay state through RNGH's `.enabled()`. On Android, RNGH 2.32 cancels the native handler when `isEnabled` changes and resets its config on every update. After a false -> true flip the handler stayed inert. `.enabled()` now carries only the device capability (touch screen present), which does not change at runtime. The dock/overlay state moves to a UI-thread derived value that fails the gesture in onTouchesDown, next to the existing non-touch pointer check. The wide sidebar's workspace list now always receives the close gesture ref, as the compact sidebar already does. Toggling it between undefined and the ref rewired the list's simultaneous handlers on every dock switch, and that also left the close pan dead after a toggle cycle. This is a native RNGH handler behavior, so there is no unit repro. It was verified on the Android fold emulator: toggle open, toggle closed, swipe opens the overlay, swipe on an empty sidebar strip closes it, no reload. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep the workspace full width after collapsing the dock On an Android tablet, opening the dock with the header toggle and collapsing it again could leave the workspace at the docked width, with a blank strip on the right, until the overlay was next opened. Elsa's original head showed the same. The sidebar's panel host flips between an in-flow dock and an absolute overlay. Its width came from a Reanimated style that read the React `overlay` flag, so the width reached the native view on the UI thread after React had already committed the new presentation. Layout logs on the fold emulator show the lag on every toggle-open: the workspace laid out at width 0 while the docked host still had the overlay's full window width, then a second pass laid out the 320dp dock. When the late update was lost on collapse, the workspace kept its dock width. The host no longer has a width of its own: - The dock sizes to the inner sidebar view, which already carries the animated resize width. - The overlay host is absoluteFill, which covers the whole row including the safe-area padding. With no animated layout prop on the host, a presentation switch is one React commit, and the host and the workspace lay out in the same pass. Device evidence on the fold emulator: - Toggle open and collapse cycles: the host and the workspace lay out together (320/521dp, then 841/841dp). - A swipe opens the 320dp overlay with a full-window backdrop, and a swipe on the sidebar closes it. - Dragging the dock edge still resizes the dock and the workspace together. There is no unit repro: the defect is the ordering of Reanimated's UI-thread layout updates against React commits. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): cancel pending sidebar swipes when docking An opening swipe still has the agent target until release. Leaving overlay presentation must advance its revision even then, so motion ownership and queued commits reject the obsolete swipe. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep mouse selection free of the touch-only pan's capture On a touch-capable browser the navigation pan registers pointer listeners over the whole workspace. Gesture Handler's web pointer manager captured the pressed element before the handler saw the pointer, and captured it again on every move, so a mouse drag that the pan rejects stayed captured and the browser's selection stopped at the starting paragraph. The patched manager now captures after pointer-down, and only a pointer the handler still tracks. The resize handle and touch swipes keep capturing. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): drop the compact Explorer target when the layout widens Opening the Explorer on a compact layout selects the `file-explorer` mobile panel target. Widening the window (unfolding a foldable, rotating a tablet) kept that target, because the store only heard the left sidebar presentation, which is `overlay` on both compact layouts and wide layouts with a collapsed dock. The navigation swipe begins only from `agent`, so on the wide layout every swipe was rejected until something else reset the target. App chrome now publishes the whole panel layout (compact or wide, plus the left sidebar presentation) and the store drops any target that layout does not present. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep wide overlay sidebar content inside the left safe area The overlay starts at the window's left edge, over the workspace's safe-area padding, so its content now takes the left inset itself. The dock already sits inside app chrome's padding and stays unchanged. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): return an interrupted swipe to its anchor when docking cancels it Docking mid-swipe bumps the panel revision while keeping the agent target. The command treated the unchanged target as motion already in progress and skipped the animation, leaving the dragged position stranded between anchors. A command that interrupts a drag now always animates to its target. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): synchronize startup panel selection and gate pending swipes A child layout effect can publish panel layout before the motion provider subscribes. Establish the current selection when subscribing, then observe later commands. Recheck presentation eligibility before gesture activation so docking also cancels touches that have not started dragging. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(app): cover panel subscription through its public contract Verify initial snapshot delivery, later commands, and unsubscription directly in the existing panel-store suite instead of mounting test-only React components. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(app): reopen compact navigation after leaving settings Destination navigation clears the transient overlay selection. Reopen the compact drawer before asserting its configured footer rather than relying on a stale open target surviving a wide layout. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(app): open navigation by keyboard in composer focus checks Navigation pans intentionally reject mouse drags. Use the keyboard sidebar command to exercise focus and fullscreen Escape behavior without relying on the former mouse swipe helper. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): complete sidebar integration after main rebase Retain the resize width resolver and format the shared division-width and hybrid touch-sizing integration. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(desktop): give split browser panes room for tab rails After #8740, narrow panes replace tab rails with compact dropdowns. The desktop browser journey counts and selects rail tabs, so widen the real Electron content window before splitting instead of treating missing narrow-pane rails as missing panes. Co-authored-by: Elsa Granger <zeyugao@outlook.com> --------- Co-authored-by: Elsa <zeyugao@outlook.com> |
||
|
|
cab5eedfe3 |
Keep workspace layouts usable in narrow columns across platforms (#8740)
* fix(app): retain Explorer resize width until acknowledged Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): choose workspace header by measured column width Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): separate iOS composer clipping from its shadow Keep explicit coverage of the existing keyboard rotation guard. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): choose workspace chrome within each desktop pane The outer width wrapper added a compact header while the desktop split container kept its full header and pane rails. Each pane now owns the complete choice, replacing its header and rail together. Each split pane uses its own measured width: below 500 points it shows its own tab dropdown, even if the neighboring pane is wide. Pane width determines usable header space. Keep new-tab keyboard ownership outside the switched chrome and retain positive measurements while hidden. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): reserve readable width for the compact tab popover Native scroll content does not contribute intrinsic width to the anchored frame. The count badge alone allowed the popover to compress its tab list and footer labels. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): preserve compact pane actions and window clearance Carry the owning pane into compact launch actions, keep split and restore controls in its menu, and distribute window corners through visible split children. Keep tab rails below header-owned window controls. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): retain window dragging in compact panes Keep the shared titlebar drag region when pane width replaces full desktop chrome. Cover two compact panes with both sidebars closed through the desktop host adapter. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(app): align pane journeys with measured workspace chrome Give rail-specific split journeys enough column width, assert launched Diff content independently of header shape, and retain pane identity across compact transitions. Expect the compact selector in the half-screen Explorer workflow. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * refactor(app): share pane tab-switcher rendering after header rebase Keep compact and vertical-bar header actions on the same pane-aware switcher contract while preserving the merged control geometry and script ownership. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): retain Explorer acknowledgements across consecutive drags Keep earlier released widths as expected allocations while a newer gesture owns the frame, and restore the latest release if that gesture is canceled. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep focus mode exit in the compact workspace menu Carry the workspace focus state into its compact action menu so a narrow header retains an exit without keyboard shortcuts. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): distinguish resize rollback from pending acknowledgements Restore the last released width when repeated widths precede a canceled gesture, and retain ownership when an unchanged old allocation matches a newer release before its predecessors arrive. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(app): allow software WebGL time to finish atlas growth coverage The 256-batch pixel regression crosses more than 500 render boundaries and exceeds its 30-second total budget under CPU contention. Keep the corpus, every pixel assertion, and the two-second per-frame deadline; allow 60 seconds for the complete workload. Reproduced the timeout with Chromium confined to one CPU and verified two successful constrained runs after the adjustment. Co-authored-by: Elsa Granger <zeyugao@outlook.com> --------- Co-authored-by: Elsa Granger <zeyugao@outlook.com> |
||
|
|
4991efddf8 |
feat(app): follow iPhone Duo vertical bars and fold divisions (#8741)
* feat(app): follow system vertical bars and fold divisions Keep custom navigation controls outside system content areas and use active division regions for temporary panel allocation. Normal sidebar preferences resume when the division disappears. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): show window actions in the active portal scope The bottom-sheet provider creates its own portal context. Register the window action host within that context so symbols reach the root host. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): hide workspace bar actions behind compact panels Compact panels keep the workspace route focused while covering its content. Hide its portal actions until the workspace panel is active again. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): continue the sidebar surface across divisions Keep the fold free of controls while painting it as part of the sidebar, with the shared divider at the workspace edge. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): omit tab count beside the full tab rail The full tab rail already exposes navigation. Use the same compact/full header selection for the vertical action list. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): preserve folded navigation and bar action lifetime Use resolved sidebar visibility for desktop activity, and keep bar controls mounted while compact panels cover them so pending menu work survives. Carry circular button chrome inside the portal and exercise the rendered boundary in Chromium. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): own script actions across header placement changes Keep one script action instance on the workspace screen so pending restarts survive removal of either menu. Pass activity through the bar portal so covered menus close their overlays without taking workflow state with them. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(app): exercise script ownership through the workspace Replace synthetic ownership fixtures with a real workspace, script menu and isolated terminal worker. Hold terminal exit until the compact header is removed so moving pending state back into the menu fails the regression. Exercise portal activity with production menu and header controls. Co-authored-by: Elsa Granger <zeyugao@outlook.com> --------- Co-authored-by: Elsa Granger <zeyugao@outlook.com> |
||
|
|
fcbfb4f0d3 |
Size controls for touch on iPad and other touch screens (#8748)
* feat(app): size controls for touch on any touch device, not just compact layouts
One gate picks touch or pointer sizing: touch when the layout is compact or
the device has a touch screen. The touch fact is read once, so attaching a
mouse or keyboard to a tablet never shrinks its controls. Stylesheets take
`bySizing({ touch, pointer })`, which resolves to a breakpoint value, so the
existing `{ xs, md }` consumers follow the gate without call-site changes.
Touch values follow the iOS HIG 44pt hit region: controls, icon buttons,
rows, bars and menu rows are 44 on touch; `sm` controls are 32. Glyphs move
to a touch ladder (14/18/20/24). Pointer sizes are unchanged.
Merges duplicated header control heights, density's private icon ladder and
the compact-only icon button sizes into control-geometry.
* feat(app): size the composer for touch through control geometry
The composer toolbar, attach / dictation / realtime / send buttons, context
meter and model triggers take TOOLBAR_CONTROL_SIZES (28 pointer, 44 touch)
instead of private 28 constants. The density budget, button glyphs and the
fullscreen glyph rail offset resolve the same gate, replacing the isWeb /
isNative glyph branches. Track rows join the menu row height.
Desktop geometry is unchanged. On touch the toolbar steps down density
earlier: iPad portrait goes from condensed to tight.
* feat(app): fold composer controls before the model label truncates
The composer toolbar walks one width budget: full, condensed, tight,
compact, then icon-only. Tight keeps the model at its family label;
below that, thinking, mode and features fold into the model control's
sheet (the phone toolbar), and only then does the model drop its label.
A touch iPad in portrait with an attachment now reads
"GPT-6-Astra Medium" instead of "GP…".
The context meter glyph and combobox rows and icons take the density
ladder. Desktop is pixel-identical in the content area.
* refactor(app): keep the sizing gate free of the theme
constants/layout sized its bars through control-geometry, which loads the
theme and runs Platform.select at import. The gate (resolveControlSizing,
bySizing, TOUCH_TARGET) moves to control-sizing.ts with no theme import;
control-geometry re-exports it, so its callers are unchanged.
* test(app): expect touch sizing in the compact Changes and plugin sidebar specs
The compact Changes options button is a 44pt icon button with a 20pt glyph,
and its framed actions trigger is 32 tall. In the compact sidebar the nav
block is capped at half the height, so 44pt rows put the plugin header row
below the cap; the spec scrolls it into view as a user would.
* fix(app): close composer menus when the controls fold or unfold
Folding swaps the toolbar controls for the model sheet. A menu opened on the
separate controls stayed marked open across the fold, so widening the
composer again reopened Thinking without a click.
|
||
|
|
ce7b14a143 |
Keep context details open while chat streams (#8742)
* fix(app): keep hover cards open while unrelated content scrolls Hover cards closed on any scroll outside their content. A streaming chat scrolls on every new line, so the context window details closed while an agent was responding. Close only when the scrolled element contains the trigger, which is the case that moves the card's anchor. * test(app): confirm the turn is still streaming once the details open * test(app): cover context clicks and trigger scrolling Exercise the reported click path without replacing usage transport, and preserve dismissal when a sidebar scroll moves its trigger. Co-authored-by: 3ae3ae <ogyshjsm@naver.com> --------- Co-authored-by: 3ae3ae <ogyshjsm@naver.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
67628b48fe |
Keep workspaces usable when mobile navigation overflows (#8727)
* fix(app): cap the compact sidebar nav block so plugins cant crowd out workspaces The compact sidebar's top nav group (builtin rows plus plugin header items) was a fixed block with no height bound. On short screens - a foldable's cover display, for instance - enough plugin items pushed the scrollable workspace list to zero height, leaving it unusable. Wrap the nav rows in a bounded ScrollView capped at half the sidebar height. Rows scroll inside the cap when they overflow; the separator moves to the scroll frame so it stays fixed at the region's bottom. On native the scroll view is the gesture-handler variant with the close pan as a simultaneous handler, matching the workspace list's contract. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(app): keep the mobile nav separator theme-safe and disable iOS bounce Review follow-ups: the border lived on the gesture-handler ScrollView, which Unistyles never rewrites, so move the divider to a SidebarSeparator sibling. On iOS the block rubber-banded under a fixed divider even when every row fit, so disable alwaysBounceVertical. Inline simultaneousHandlers now that GHScrollView types it, and tag the region for Maestro. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(app): keep workspaces reachable with overflowing sidebar navigation Reproduce the short compact sidebar failure with the existing ten-row plugin fixture. Check that navigation scrolls independently and both navigation actions and workspaces remain usable. Co-authored-by: hugo <jaredwangcoode@gmail.com> --------- Co-authored-by: hugo <jaredwangcoode@gmail.com> Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
a880c9619d |
feat: background agents for hidden helpers (#8683)
* feat(server): add background agents An agent can be created background in any workspace. It stays a durable, addressable agent, but default discovery, workspace status and attention leave it out. Effective background is agent.background || workspace.background. - Protocol: `create_agent_request.background` now marks the agent itself and stays the workspace intent for a workspace the request creates. Snapshots carry `background`. The daemon advertises `features.backgroundAgents`. Clients advertise the `background_agents` capability. - Daemon: the flag persists in the agent record, so restart and resume keep it. AgentDirectory owns visibility. Inclusive reads and subscriptions from sources without `background_agents` see only agents in background workspaces, because older apps would otherwise open a tab for each one. - Inheritance: omitted on agent-scoped creation, children and new workspaces take the caller agent's background. - MCP: create_agent accepts `background`. - SDK: placing a background agent in an existing workspace, or under a caller, fails fast with "Update the host to use background agents." on older hosts. - CLI: `ls -a` and `ls --background` add a BACKGROUND column. * feat(app): hide background agents from tabs, track and status * docs(plugins): point hidden helpers at background agents * fix: keep background agents hidden across cache reload and connected hosts * fix: keep remote creation working on older hosts and inherit foreign parents' background |
||
|
|
6740d41fab |
Open plugin settings from a cog, with a tab per settings screen (#8675)
* feat(app): open plugin settings from a cog with a tab per screen The plugin row shows a settings button next to its switch when the plugin registers settings screens. The actions menu keeps Logs, Reload and Remove. The settings page shows one tab per registered screen; a plugin with one screen shows no tabs. Tabs swap screenId in place, so existing plugins/[pluginId]/[screenId] links and openSettings keep working. * fix(app): render settings tab icons inside the plugin's runtime Component icons now mount under the installation provider, so plugin SDK hooks such as useTranslation work. Each icon has its own error boundary; an icon that throws leaves its tab with the label alone. * feat(app): name only the plugin in its settings page header The header shows the plugin's manifest name, or its id when it has none, for single- and multi-screen plugins alike. The selected tab names the screen. The Plugins page and the header share one plugin list query. * feat(app): show plugin display names and a Settings tooltip on the cog Plugin rows on Settings → Plugins show the manifest name, falling back to the id; when a name is set, the id stays visible beneath it. The row's controls, logs sheet, confirmations and feedback use the same name. The cog is a ToolbarButton labeled Settings, with a matching tooltip. |