* 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.
* 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.
* 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>
* 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
* feat(workspaces): record workspace creation parentage
Retain the caller workspace as informational ancestry for future sidebar nesting, independently of workspace placement and lifecycle.
* fix(app): preserve workspace parentage in replica cache
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.
Change a worktree workspace's base from the Changes pane. The choice persists and diffs, counts, commits and Git actions follow it.
Supersedes #6872.
Co-authored-by: Tianyu Yang <yangtianyu.cl@bytedance.com>
The 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(client): keep active transfers alive during foreground checks
Foreground used an independent three-second probe that closed live connections while a pong waited behind bulk data. Share the heartbeat clock and refresh its outstanding deadline on validated inbound frames, including incomplete chunk assemblies.
* test(client): deliver bulk response before the queued pong
* fix(client): preserve latency deadlines when sharing heartbeat pings
* fix(client): keep heartbeat cadence during active downloads
* fix(sidebar): clear the failed status once the error is read
Reading an errored agent already cleared its error attention, but the
state bucket also mapped lifecycle status "error" to failed. Lifecycle
only leaves "error" when the next turn starts, so the red dot and the
Failed group stuck until the agent was prompted again. The failed bucket
now follows unread error attention, the same way the green dot follows
unread finished attention; lifecycle status stays "error".
Because failed now depends on attention, a failure must replace unread
finished attention, otherwise a reload that fails on an unread agent
would show green.
* fix(sidebar): keep failed subagents red and let read failures be marked unread
The failed bucket now follows unread error attention, which broke three
places that relied on lifecycle "error" alone:
- Subagent track rows and provider-subagent tabs have no read state of
their own, so failed children lost their red mark. They now map the
child's lifecycle directly.
- A read errored workspace became "done" and offered Mark as unread, but
the daemon only accepted idle or closed agents. Marking it unread now
restores error attention.
- A reload that failed again while the lifecycle was already "error"
raised no new attention. A failed reload now raises error attention
directly instead of relying on a lifecycle transition.
* fix(app): hold the loading screen until first-run setup is decided
A new user saw the empty home, the sidebar and a logo jump before setup
popped in. Startup now keeps the splash while setup may still open over
the empty home, for at most 3 s and only while the setup host is
reachable. Remembered-workspace restore does not wait. Web dialogs now
fade in as they already fade out.
(cherry picked from commit a0a7620b62)
(cherry picked from commit 72d7c0bf26)
* fix(app): fade the dialog card in and land the dim at once
(cherry picked from commit 6ef05705a0)
(cherry picked from commit 71c28b431c)
* fix(app): keep the loading screen up while startup redirects
(cherry picked from commit 0b28d4cbff)
(cherry picked from commit 13cc485e6f)
* test(app): cover a setup host that drops while startup waits
(cherry picked from commit 2a906ef83e)
(cherry picked from commit 7b6e21207a)
* fix(app): type the web-only dialog enter animation style
The CSS animation props are not part of ViewStyle. CI typechecks without the
generated expo-env.d.ts, which declares them, so the cast is required there.
(cherry picked from commit b887d979d2)
* fix(app): keep the setup window mounted when the theme changes
The shell's appearance boundary remounts its dialog hosts whenever a theme
color changes, so picking a theme in setup, or a system light/dark switch,
replaced the open setup window and replayed its fade. Setup draws colors
through CSS variables and themed props, which update in place, and changes
no font size, so it now sits outside that remount.
The dialog enter fade is skipped with reduced motion, and its style is typed
for CI, which typechecks without the generated expo-env.d.ts.
(cherry picked from commit b24c6eacf8)
(cherry picked from commit cb0e9907f5)
* docs(changelog): prepare 0.15.2
(cherry picked from commit 5af1a8ff66)
* chore(release): cut 0.15.2
(cherry picked from commit e896c05b09)
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.
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.
- 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.
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): 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
Bring in what v0.15.0 shipped: the 0.15.0 entry in CHANGELOG.md and
CHANGELOG.zh-CN.md exactly as tagged (replacing the 0.15.0-beta.1 series
entry), version 0.15.0 across package.json/package-lock.json, the F-Droid
changelogs 150001-150004.txt, and the matching Nix npm deps hash.
* feat(app): add first-run setup modal
New users landed on an empty "Add a project" screen and never found
providers, Paseo tools or phone pairing, which all live in Settings. A
one-time setup modal now walks them through Appearance, Providers,
Projects, Orchestration and Phone. Every step renders the same component
Settings uses, so changing something in setup changes it in Settings too.
The modal opens only when the setup host is connected, its workspaces have
loaded and it has no projects. Existing users already have projects, so
they never see it after upgrading and no migration is needed. Finishing or
skipping writes `@paseo:onboarding` on this client. It uses its own key
because it records feature state, and e2e specs replace app settings
wholesale. Settings > General > "Show setup again" reopens the modal on
step 1. Native phones skip the modal for now.
Shared pieces moved out so setup and Settings render the same thing:
- ThemePicker is a tile grid and replaces the Settings theme dropdown.
- PaseoToolsCard is extracted from the host page.
- The skeleton pulse moves to components/ui. Provider rows show it while
the first snapshot loads instead of "Loading…".
- The pair-device direct-connection hint links to the Tailscale docs.
The e2e fixture marks setup completed by default so existing specs keep
their starting state. The desktop smoke and lifecycle scripts, which start
from a fresh daemon, mark it before they continue.
* feat(onboarding): suggest projects from agent history
The setup's Projects step lists the repos the user already ran agents in,
with busy recent ones preselected, so a new user adds their projects in one
click instead of browsing for folders. With rows checked, the footer's main
button adds them and only moves on once every one was added; a checked row
always means "this will be added".
The daemon resolves each session folder to its project root (main checkout
for worktrees, repo root for subfolders) because only it can read the git
state; the app asks once per provider so a slow or broken provider never
holds up the others and can be retried on its own. Hosts without the
projectSuggestions feature show only the Add project button.
Onboarding e2e hosts read an empty Claude history with other providers
off, so suggestions never depend on the machine running the tests.
* fix(onboarding): keep late project adds on their step and show Retry early
- A finished add only advances setup while the Projects step is still open.
- A failed provider shows its Retry as soon as any provider answers.
- The desktop lifecycle check no longer reloads the window: setup never opens
there because no host is connected.
* fix(onboarding): bind late project adds to their step visit and freeze rows while adding
- Each step visit has an id; a finished add only advances the visit that started it,
so leaving and returning to Projects, or reopening setup, ignores it.
- Suggestion rows are disabled while their batch is being added.
* test(app): drive theme selection through the tile picker
Settings → Appearance no longer has a theme dropdown. The Escape test opens the
syntax theme dropdown instead, and the Android system-bars check taps theme tiles.
* fix(server): return project suggestion roots in the form project.add stores
Git reports Windows repo roots with forward slashes, so an added suggestion kept
showing because its path never matched the new project's root.
* feat(app): two control sizes, orchestration switches and mobile connect choices in setup
Controls had four sizes picked per call site, so a button in a dialog, a
settings row and a setup step came out at different heights. Controls now
have two: md (default; 32px with a pointer, 44px in the compact layout so
phone controls are full touch targets) and sm (28px, for tight chrome and
nested settings rows). The breakpoint picks md's height, not the caller.
Setup:
- Orchestration is three switches (Paseo tools, agent skills, CLI), all on,
applied on Continue. Turning one off undoes it; the desktop CLI uninstall
removes only the file this app installed.
- The Mobile step has store buttons and a "Choose how to connect" section:
Relay opens the existing pairing modal, Tailscale opens the docs.
Settings section docs links are one pattern, SettingsSection's `link` prop,
and modals sit on surface0 like a screen.
The vitest unistyles stub now resolves breakpoint values by window width,
as Unistyles does at runtime; without it md controls had no height in
browser tests.
* fix(app): finish setup on its last step and confirm undoing orchestration
In the compact layout Orchestration is the last step, and Finish closed
setup without applying its switches. The last step's work now runs before
Finish, the same as Continue.
Turning off skills now asks first, as Settings does. Removing the CLI keeps
a paseo command this app didn't install and says so. A failed Paseo tools
update shows a toast. Adding suggested projects no longer advances when a
row was checked while the add ran.
Chat sender attribution and the message preview toggle go back to the sm
button: md padding is a breakpoint value, and on web its media-query rule
beat the zero right padding that aligns the sender with its bubble.
* fix(app): ignore a late Finish once the user left the last setup step
Finish called complete(), which ignored the step visit, so pressing Back
while Orchestration applied its choices still closed setup and recorded it
as done. next(visit) now finishes setup from the last step, so a late
Finish is dropped the same way a late Continue already was.
* fix(app): hold the skills switch while setup applies its choices
The Paseo tools and CLI switches were disabled while Continue or Finish
applied them, but the skills switch only locked while a skills operation
ran. With skills needing no change, it stayed editable during a pending
tools update and the new value was silently dropped.
* fix(app): read setup's skills choice as off when no skill is picked
Saving an empty selection in the skills picker left setup's skills switch
on. Continue then asked the host to install nothing, the host reported
skills as not installed, and the step stayed put with no error. Setup now
reads skills as off with nothing picked, as Settings does.
* fix(schedules): match either restricted cron day field
* refactor(schedules): name the combined day match conditions
Keep wildcard days conjunctive so weekday-only and date-only schedules
retain their meaning, while restricted day fields follow cron's union
rule. Preserve lexical wildcard identity rather than inferring it from
allowed values, since an explicit full range is still restricted.
Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com>
---------
Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com>
* fix(plugins): parse RPC values once at the receiver
Sending parsed values caused transforms to run again on incompatible schema outputs. Keep the wire values in schema-input form and parse only where consumed.
* fix(plugins): let omitted RPC inputs reach schema defaults
* fix(plugins): accept omitted RPC outputs for client defaults
* feat(plugins): stream events and bytes between plugins and the app
Plugins can declare a subscription with defineSubscription, handle it with
server.handle(contract, (input, { push, signal })), and listen from a surface
with useSubscription or client.subscribe. A top-level Uint8Array in an event,
an RPC output, or an RPC input crosses the wire as a binary frame (opcodes
0x20-0x2f) without JSON or base64; a nested one fails validation on the sender.
Each subscriber runs its own handler, owned by the session that subscribed,
so release, disconnect, plugin stop and reload need no plugin code. The
runtime buffers pushes until the subscribe response has gone out, so a client
never sees an event for a subscription it does not know yet.
Gated on server_info.features.pluginSubscriptions; binary outputs reach only
clients that advertise plugin_binary_payloads.
* fix(plugins): close subscription gaps found in review
- Losing daemon.manage now releases the session's plugin subscriptions;
binary events bypass the outbound permission check, so streams would
otherwise outlive the revocation.
- A subscribe that times out sends unsubscribe, since the plugin may
still start the handler after the daemon gave up.
- The byte limit leaves room for the largest frame header and routing,
and lives in assertPluginWireValue so events, outputs and inputs share it.
- A release right after the host accepted keeps `ready` resolved, so a
handler that returns at once reports closed rather than an error.
- Session trace logging tolerates loggers without isLevelEnabled.
* fix(plugins): withhold binary plugin results after plugin access is revoked
A binary RPC output or event skipped the outbound permission check its
JSON form passes, so an RPC in flight when daemon.manage was revoked still
delivered its bytes. Each frame now checks the permission of the JSON
message it stands in for.
* test(plugins): cover subscriptions in a real browser instead of JSDOM
The app subscription tests mounted hooks in JSDOM, which docs/testing.md
rules out. The adapter and contract behavior is now a plain unit test,
and a Playwright spec drives the diff-watch and media-stream examples:
JSON events following file changes, streamed bytes, and an upload.
* test(plugins): add the plain subscription unit test and the browser spec
* fix(plugins): apply subscription schema transforms once
Both ends parsed each subscription value, and the sender sent its parse
output, so the receiver parsed a schema's output as fresh input. An event
schema like z.string().transform((s) => s.length) closed the stream as
invalid. The sender now validates and sends the value as given; the
receiver's parse produces what the handler or callback sees.
* fix(plugins): parse a subscription's input once in the plugin process
server.handle wrapped subscription handlers in a second input parse, so a
transforming input schema rejected its own parsed output before the
handler ran.
* fix(terminal): render emoji at their intended width
The native grid scales glyphs to the columns assigned by the terminal buffer. Use grapheme widths across current hosts and clients so emoji fit their slots and replay preserves cursor positions.
* fix(terminal): make width regression test portable
* test(terminal): wait for replay fixture output to finish
A trailing newline could arrive after the readiness marker and change the retained history between replay captures.
Restore configured terminal history on reconnect, retaining 10,000 rows plus the screen. Serialize the worker-owned xterm buffer directly to ANSI and forward negotiated bounded replay chunks while preserving ANSI-era clients and per-viewer live ordering.
Preserve captured geometry through replay, bound pressure recovery to 500 rows, and keep the omitted visible-history default at 200. Remove the full cell-grid copy and obsolete JSON rendering.
All 21 CI checks pass. Real browser, native Debug emulator/simulator and Linux Electron coverage and platform limits are documented in the PR. The Android Debug invalid native delegate dispatch also reproduces on upstream main; its cause and any change in frequency remain unproven.
Credit Manav Dhindsa (DrakenKor), #6139 and #6148, and Lennart Schoch, #6743, for prior ANSI replay and retention work informing this change.
Closes#6137.
Closes#6147.
* fix: make sub-agent cards launch markers
Sub-agent tool calls no longer accumulate the child's activity into
`log`. Claude, Codex, OpenCode, OMP and Pi emit the card when the child
starts and when its label or status changes; the child's messages and
tool calls stay in the sub-agent's own timeline. The app no longer
renders a sub-agent log.
The wire schema makes `log` optional. Daemons still send `log: ""` so
apps that require the field keep parsing the card.
* fix(server): put a tintinweb foreground answer in the child timeline
A foreground @tintinweb/pi-subagents child has no session file, so its
answer only exists in the Agent tool result. Emit that text as the
child's assistant message when the child finishes. Tag every sub_agent
producer's `log: ""` with COMPAT(subagent-log) so the shim removal
finds them.
* fix(server): settle child compactions and keep sub-agent cards stable
- Codex: a child compaction shown as loading now completes on the
child's thread/compacted or turn end, once.
- Claude: a streamed child tool call's empty start input is a
placeholder, so the assistant message's input reaches its card.
- OMP: the task card follows only its first child, so later children
no longer re-send an unchanged card.
* fix(server): keep a gotgenes foreground answer on replay
A foreground @gotgenes/pi-subagents child finds its session file only
through the live runtime marker, which history does not keep. Without a
file, emit the spawn result text as the child's assistant message.
* fix(usage): fetch only when shown and add a host-wide off switch
* fix(usage): restore policy defaults and isolate usage test state
* refactor(usage): give the sidebar footer sole ownership of its summary
Bring in what v0.14.0 shipped: the 0.14.0 entry in CHANGELOG.md and
CHANGELOG.zh-CN.md exactly as tagged (replacing the 0.14.0-beta.2 series
entry), the 0.14.0 version in every package.json and package-lock.json, and
the generated F-Droid changelogs. The unreleased notes on main stay above the
entry and now say they are not in 0.14.0.
Release commit: 8c6de842a3 (tag v0.14.0)
* docs(changelog): fold 0.14.0-beta.2 UI fixes into the 0.14.0 entry
(cherry picked from commit 28bb7e29be)
* chore(release): cut 0.14.0-beta.2
(cherry picked from commit 1a05491be5)
* docs(changelog): keep unreleased main changes out of the 0.14.0-beta.2 entry
The beta.2 tag was cut from a selective hotfix branch, so #8100, #8117 and #8081 are on main but not in beta.2. Every changelog parser (app, website, release-notes sync) skips the preamble, so listing them there keeps them out of the beta.2 release body and What's new until the next release entry absorbs them.
* feat(server): resume interrupted agents after host restart
Continue saved running conversations after graceful restart or crash, with host opt-out and a configurable continuation prompt. Preserve explicit launch overrides when reopening the same provider session.
Co-authored-by: coolwind <45729206+coolwindX@users.noreply.github.com>
Co-authored-by: suntianc <suntc8985@gmail.com>
* fix(app): translate restart recovery host settings
* test(app): synchronize sidebar drags and clean up added projects
Wait for the drag sensor click blocker to expire before the next user action. Resolve UI project keys to daemon IDs for cleanup so empty projects do not contaminate later sidebar tests.
* fix(app): tuck the resume prompt behind an edit sheet
Match the System prompt row: an Edit button left of the restart recovery
switch opens the prompt in a sheet, and is disabled while resume is off.
* fix(server): resume interrupted agents without holding startup
Startup now only chooses resume candidates before anything else reads or
repairs persisted running state. Reopening and prompting run in the
background after connections open, so a provider that is slow to reopen
holds back only its own agent. Finish observation waits for a pending
child's resume, so parents are not told the child was closed meanwhile.
* style(server): format restart resume e2e test
* test(server): wait for the fetch result before advancing the narrow refresh debounce
The test released the held fetch, flushed six microtasks, then advanced
the 1s refresh debounce. When the fetch result landed after that advance
(slow Windows runners), the debounce started late and vi.waitFor's
auto-advance reached it only at its real-time timeout. Wait on the
service's in-flight fetch count instead, as the sibling test does.
* test(server): retry stalled OpenCode fixture downloads within the test budget
npm abandons a stalled registry download only after its 300s default
fetch timeout, past the install's 180s budget. The killed install left
the fixture directory locked on Windows, so cleanup failed with EBUSY and
hid the timeout. Against a registry proxy that stalls the first tarball,
a default install hung 225s; with --fetch-timeout it retried and finished
in 16s.
---------
Co-authored-by: suntianc <suntc8985@gmail.com>
* M1: terminals survive daemon restarts: stop reasons, detached terminal worker, kill cleanup
restart_server_request.killTerminals / shutdown_server_request.keepTerminals and server_info.features.terminalsSurviveRestart; CLI daemon restart --kill-terminals and daemon stop --keep-terminals (capability-gated). The terminal worker runs detached over an owner-only, secret-authenticated socket and is adopted on daemon start. One kill cleanup contract (the reaper) by birth time, used by the daemon, the supervisor and stopDaemonInstance. Settings offers Restart and close terminals.
* M1: fix review findings: reaper session/table retry, short-command creation order, remote kill-restart cleanup report, stop test port
* M1: fix review findings: reaper-owned cleanup record, session member identity by terminal id, unreadable cleanup records, Settings restart error rendered in the card
* M2: worker protocol versions and draining, worker runs from its own copy, desktop upgrades keep terminals, out-of-date daemon notice, docs/terminal-worker.md
- Daemon classifies live workers by protocol: adopts current, drains one version old, shuts
down older, leaves a newer one running and answers list/create with
terminal_worker_incompatible (daemon status prints it). Never more than two workers.
- esbuild worker bundle (build:lib); a built daemon copies bundle, shell integration and
node-pty into $PASEO_HOME/terminal-worker/<version>-<hash>/ and launches from it; copies no
record names are pruned.
- Desktop app_update/version_mismatch stop with keep terminals on macOS/Linux; Windows and a
daemon without the capability close them.
- Out-of-date daemon sidebar notice with Update button, worded by terminalsSurviveRestart.
- PASEO-13: unreadable worker records fail status and kill-restart retry.
- PASEO-14: a session member without a terminal id is unknown, not unrelated.
- Smoke test 46 with A/B installation fixtures and per-fixture CLI shims (PASEO-9 fold).
* M2: track in-flight worker requests without leaving an unhandled rejection
detach({ afterPendingRequests }) tracked each request with request.finally(), which rejects
with the request; a fire-and-forget send that timed out surfaced as an unhandled rejection
(worker-terminal-manager.test.ts). Track settlement only.
* M2: CI round 1 fixes: worker terminal manager in the MCP key test, status cleanup field, shared socket dir
- mcp-server.test.ts (new upstream test) built the worker manager with the old fork API;
it now starts a real detached worker in a throwaway home.
- daemon-session.test.ts expected status without terminalCleanupUnconfirmed (added in M1).
- paseo-daemon.test.ts: a home under the scratch temp dir is too long for a unix socket, so
the worker listens in the per-user paseo-<uid> directory, which outlives workers by design;
the test now requires it to be empty instead of absent.
* M2: run Windows PowerShell by its install path for process identity
server-tests (windows-latest): bootstrap-provider-availability.test.ts starts a daemon with a
PATH that holds only git, and the terminal worker failed to start: 'cannot read its own process
identity: spawn powershell.exe ENOENT'. Process inspection (managed-processes.ts inspect and the
terminal worker's process table) now runs %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe.
* M2: lifecycle e2e plain stop uses the shutdown RPC on Windows
CI round 2, server-integration (windows-latest): 'stop and stop --force never signal processes
a stale or earlier-boot record names' called stopDaemonInstance without a shutdown channel,
which Windows refuses ('has no graceful shutdown channel'). Plain stops in the file now pass
one, as the CLI does.
* M2: native notice outlet, Windows desktop-update wording, worker cap counts unconfirmed cleanups, create recovers after a drain
* M2: phone sidebar mounts the callout slot (iOS notice verified), worker record reads retry transient errors
* M2: native notice preconditions use explicit working directories, one block per terminal
* M1: compact sidebar shows only native callouts on every platform; PTY round trips in the MCP key test get a 5 s window
* ci: pin Playwright browser jobs to ubuntu-24.04
The pinned Playwright (1.58) has no ubuntu-26.04 build, and forks whose
ubuntu-latest already resolves to 26.04 fail at browser install. Job names
are unchanged, so required-check contexts still match.
* ci: use ubuntu-latest for Playwright jobs, same as upstream
Reverts the ubuntu-24.04 pin; upstream main's Playwright shards pass on ubuntu-latest.
* fix(server): keep terminal worker record IO off the daemon event loop
* fix(server): restore terminal creation after worker failure
A disconnected terminal worker remained the current manager indefinitely, leaving dead sessions listed and every later create bound to a closed socket. Confirm process death before retiring the member and use the existing single launch path for replacement. Reconnect live workers and replay their screens while preserving terminal identity.
* test(server): verify orphan cleanup during worker recovery
Recovery reaps orphaned programs before a later daemon stop. The previous fixed-delay assertion raced that cleanup and failed when it completed promptly. Verify orphan removal, same-daemon replacement creation and cleanup on normal or forced stop instead.
* test(server): isolate Windows dependency fixture setup
Build the local package registry in setup so its npm packing processes do not consume the installation journey deadline. Install the platform package used by Windows runtime fixtures directly, avoiding an unused wrapper postinstall probe without a timeout.
* test(server): wait for pending watchers before disposal
Observation admission starts before asynchronous path setup reaches watcher subscription. Disposing at the admission counter races setup and never exercises late watcher cleanup. Wait for the subscriptions to be pending before disposing.
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
Reuse verified host-scoped plugin bundles across client launches and reconstruct complete catalogs when unchanged code is omitted. Keep store ownership on the connected client, including adopted bootstrap clients, and persist in the background with bounded writes.
Remove catalog catch-up deferral and fix repeated timeline preparation that promoted cached completed history into a live head. This preserves completed plugin-rendered chat when catalog installation precedes timeline catch-up.
Carries forward #6732 on an upstream branch, with the original hash/cache contribution and integration attribution preserved.
Co-authored-by: luke <luopeihuan@gmail.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
* fix(app): keep autonomous turns distinct and readable
Hidden system prompts merged autonomous work into the previous response and reopened completed turns. Preserve canonical turn boundaries and expose their triggers, while making received messages attributable and expanded turn details readable in one step.
* fix(timeline): mount history at actual run boundaries
* fix(timeline): load and retain complete available turns
* fix(app): summarize actions and preserve conversation events
* test(timeline): verify whole-turn paging stays responsive
* fix(app): group reasoning according to thinking preference
* test(app): align history journeys with whole-turn loading
* feat(voice): add a Voice settings page with provider choice and a local model catalog
Each host gets a Voice page under host settings. Dictation and voice mode
each pick a provider (Local or OpenAI) and a model. Provider settings sit in
one group of collapsible rows: local speech-to-text and text-to-speech list
every catalog model with its size, languages and description before download,
and OpenAI takes a write-only API key and an optional base URL.
The local catalog grows from Parakeet/Kokoro to Whisper, SenseVoice, Dolphin,
GigaAM, Parakeet Japanese, multilingual Kokoro, Kitten and Piper voices. Each
entry declares its sherpa engine family, so the recognizer and TTS build the
matching model config and the downloader verifies the files that family needs.
Kitten v0.8 is left out: it crashes the pinned sherpa-onnx 1.12.28.
Voice mode is now opt-in. The default lived only in code (true), never in
config.json, so hosts that never toggled it find it off after upgrading.
OpenAI speech-to-text resolved one model for both features, with voice mode's
choice taking precedence, so dictation silently used voice mode's model. The
resolved config now carries a model per feature; STT_MODEL still overrides both.
Rows revealed under a collapsible settings row are a nested density level set
by the container: tighter padding, smaller text, and xs controls, with
controls taking their size from the row instead of each caller.
* test(app): pre-bundle the JSX dev runtime for browser tests
Compiling test JSX with the automatic runtime imports react/jsx-dev-runtime. Vite discovered it mid-run on a cold cache and reloaded the page, which failed the first browser test file to import it.
* refactor(voice): keep speech provider settings at each use site
Store credentials and endpoint options beside the speech feature that consumes them so saved page values cannot be shadowed by a separate provider block. Normalize existing config on load and mirror the inline shape on config reads and patches.
This is the config, resolver and wire phase; the provider sheet follows separately.
* fix(voice): preserve saved keys when resaving provider settings
Forms send the selected provider on each save but cannot read back its API key. Merge patches for the existing provider so these saves retain credentials; replace the site only when the provider changes.
* feat(voice): configure each speech use site from a provider sheet
The Voice page now lists Speech-to-text, Listening and Speaking as one-line
summaries. Tapping one opens a sheet with the provider choice and only that
provider's settings: the local catalog (size, languages and description
before download, download state per model) or the OpenAI key, base URL,
model and language or voice.
The sheet's form model sends only the fields the user changed, so re-saving
never clears a saved key. Fields an env var decides are read-only with a
"Set by" hint; the API key and base URL stay editable because a saved value
wins over the env fallback. Save failures show a toast and keep the sheet
open with the input.
E2E daemons pin speech providers through env vars to avoid model downloads,
so the browser spec covers the locked-provider path, the key fallback and
the failed save; provider switching is covered by the form model tests.
* refactor(voice): press settings and catalog rows through PressHighlight
Settings rows that lead somewhere and local model rows now use the standard
row press primitive, so native shows the finger-down flash the rest of the
app's rows have. Web hover and press styling are unchanged.
* fix(voice): preserve STT language for local providers
Language belongs to every STT use site. Preserve legacy local language settings through config loading and saving, resolve them without provider gating, and expose them in the provider form.
* fix(app): keep the sidebar alive when a workspace entry has not hydrated (#7896)
ProjectBlock asserted that every placement in a project has an entry in
workspaceEntriesByKey. A retained sidebar that is inactive (compact layout
with the sidebar closed) or still waiting for sessions keeps an empty map,
and Show background stops the project list from being pruned to hydrated
entries. From the eighth workspace on, selectSidebarGroupVisibility asked
for the status of a missing entry and the app crashed on start.
Count a missing entry as Done. It renders no row, so it should not claim a
slot past the limit.
Closes#7869
Co-authored-by: brucey0x <11062155+brucey0x@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6bc4b73c42)
* docs(release): add 0.13.1 hotfix notes
* chore(release): cut 0.13.1
---------
Co-authored-by: Budi <11062155+budivoogt@users.noreply.github.com>
Co-authored-by: brucey0x <11062155+brucey0x@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(server): release idle agent runtimes with provider safety checks
Require provider permission before releasing resumable runtimes so foreground idleness does not discard tracked background work. Preserve the saved conversation and serialize prompt admission with runtime closure; allow hosts to disable collection.
* docs: translate idle runtime cleanup configuration and provider contract
* feat(server): collect runtimes after one idle hour with optional provider vetoes
Keep callers resident while local or remote finish notification intent remains outstanding. Daemon-owned work determines baseline eligibility; provider checks add optional vetoes. Own the idle clock and sweep lifecycle in one cleanup module while the manager retains guarded closure.
* feat(agents): control idle runtime release live from host settings
Let hosts retain runtimes without restarting the daemon. Disabling invalidates pending collection decisions while preserving idle clocks for re-enabling. ACP-owned terminals and pending adapter work veto collection until they finish.
* fix(settings): clarify idle agent release with an info tooltip
Order ordinary sidebar workspaces by Created or Last prompt, preserve project and pin ordering, and keep later active or unacknowledged work visible beyond the first seven rows. Preserve reveal state through live updates and verify sorting, visibility, compatibility, and rendering behavior.
Protect relay sessions against replayed commands with authenticated Noise NK negotiation, ordered records, and identity-bound security pins. Preserve legacy admission during migration and add daemon enforcement policy.
All 21 CI checks passed. General engineering review is complete; specialist cryptographic review of the handwritten Noise implementation and static-key reuse remains required before release or deployment.
Closes#359