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.
* 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
* 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.
Plugin composer pills with a menu or popover now show an upward chevron at
every width, so a pill that opens a surface reads differently from a one-tap
action pill. Built-in Tasks and Subagents pills are unchanged.
The transcript's bottom inset used to be reserved only while some pill
existed. Plugins that add pills when a turn ends and remove them when the next
starts moved the transcript every turn. The inset is now reserved whenever the
agent has an active composer.
* fix(android): correct inverted chat wheel direction
* test(android): run wheel regressions with current React Native
Provide the native-only test context with React Native’s event dispatcher for an absent JavaScript instance, retaining real scroll handling.
Co-authored-by: Seth Jeon <paseo@sethjeon.com>
---------
Co-authored-by: Seth Jeon <paseo@sethjeon.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* feat(keyboard): support hardware keyboards on iPad
Register app shortcuts as iOS UIKeyCommands so a paired hardware keyboard
drives the same bindings as web and desktop. Overlays (menus, comboboxes,
modals, command center) get first refusal on bare keys, and a focused
terminal keeps the keys it needs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(keyboard): record iOS hardware key observation spike
With a UITextView focused on iPad Pro M4/iOS 18.6, window and controller observers received Cmd+B, Cmd+P and Escape. Escape also invoked UIKeyCommand when registered, so the stream must be the sole emitter. Use observation for shortcuts and commands only for text-control ownership. Simulator intercepted Cmd+K as HID184; input capture blocked synthetic typing entirely. Keep capture off and verify the toggle via a rebind during simulator QA.
* refactor(keyboard): give overlays one key registration API
Overlay ownership now follows one layer-ordered registry on every runtime. Web retains focus trapping and restoration through its adapter; native registers the same key handler without a DOM scope. Picker navigation and dismissal no longer require a parallel native stack.
Co-authored-by: jeroenfrenken <16974549+jeroenfrenken@users.noreply.github.com>
* feat(keyboard): route native hardware keys through the shared resolver
Observe each physical press once, including native modal windows, and derive focus from declared composer and terminal surfaces. Native bindings and rebinds now use the same resolver and overlay precedence as browser and Electron input.
Co-authored-by: jeroenfrenken <16974549+jeroenfrenken@users.noreply.github.com>
* feat(keyboard): show shortcuts when hardware keyboards are present
Keep routing independent of compact layout and detect hardware capability for hints and settings. Rebind capture uses the shared overlay interface on native too. Keep the filtered sidebar model available for keyboard navigation while its panel is hidden.
Co-authored-by: Les Stroud <public@lesstroud.com>
Co-authored-by: jeroenfrenken <16974549+jeroenfrenken@users.noreply.github.com>
* test(keyboard): allow shortcut hints in compact menu assertions
Compact menus now display hardware keyboard hints. Assert action labels, order and uniqueness without treating a trailing shortcut badge as part of the action label.
* fix(keyboard): retain an overlay's opening layer in state
Capture the registry's current top when an overlay opens and retain that layer through rerenders. React Compiler removed the previous useMemo cache after the registry getter moved across modules, allowing a parent modal to rise above its child and start an infinite focus loop.
Compile this hook in Vitest as Expo does. The new hook regression failed first with layer 220 instead of 20, then passed with explicit opening state. Focused unit and browser tests pass, and the previously failing onboarding E2E completes through the real Expo compiler. Typecheck, lint and format pass.
* refactor(keyboard): let overlays own native hardware input
Remove the TextInput key handlers that duplicate declared overlay arrows and Escape in Add Project and Command Center. Remove Add Project's hidden responder now that native window sources observe keys without it. Keep soft-keyboard submit editing behavior.
* fix(keyboard): close Add Project outside its state updater
Root Escape previously called the parent close action from a React state updater, producing a cross-component render warning when navigation was still queued. Compute the previous page in the event handler and close outside the updater.
The existing Escape/Back E2E now batches navigation and dismissal through the DOM key source. It failed first with the AddProjectFlowHost render warning, then passed after the correction. Typecheck, lint and format pass; native Escape checks pass on Android and iPad.
* fix(keyboard): preserve Android dead-key composition
* fix(keyboard): recognize focused native text inputs
* test(keyboard): replace JSDOM coverage with browser tests
* refactor(menu): spell out arrow navigation branches
* fix(composer): claim fullscreen Escape on native
* refactor(composer): one fullscreen Escape module for every platform
The shared registration lived in fullscreen-exit.shared.ts with two one-line
re-exports (fullscreen-exit.ts and fullscreen-exit.native.ts). Metro needs no
native override now that the Android back button is its own adapter, so the
implementation moves into fullscreen-exit.ts and the browser test imports the
module every platform resolves.
---------
Co-authored-by: Ubuntu <jeroenfrenken@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: jeroenfrenken <16974549+jeroenfrenken@users.noreply.github.com>
Plugin screens can report unavailable while a successful catalog request
is still pending. Track the host catalog state so these screens show
progress until their contributions register.
Keep missing surfaces, failed requests, and offline hosts distinct from
loading. Retain existing installed surfaces during catalog refreshes.
Cover pending-to-ready rendering with a real browser regression and
catalog lifecycle tests.
Related to getpaseo/paseo#7088
* feat(app): link #123 issue and pull request references in Markdown
Agents cite issues and pull requests by number; reading one meant looking
up the URL by hand. Markdown in a workspace now links forge shorthand
against the workspace's origin remote: `#N`, GitLab and Gitea `!N`, and
cross-repository `owner/repo#N` on the same host.
Each forge's URL grammar declares which sigil maps to which path, so the
syntax stays per forge: GitHub has no `!`, and its `#` targets /issues/,
which GitHub redirects to the pull request. Recognition goes through the
Markdown engine's text link regex so code spans, fences and authored links
stay plain without a second Markdown parse while streaming. The reference
alternative sits before path recognition so `group/project#12` is not read
as a file path.
* fix(app): keep http:// for self-hosted forge web links
A self-hosted forge cloned over http:// serves its web UI over http on
the same origin. Forcing https made reference, blob and tree links fail
against an HTTP-only port. Cloud hosts and SSH remotes stay on https.
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.
The library's web renderer marked an image inline only when it was a
direct child of a paragraph with other content. A linked image sits
under a Link node, so badges such as a review bot's
`[](url) Confidence Score` header rendered as
100%-wide, 320px-tall block images in the PR panel and file preview.
Images nested in links (or emphasis) now count as inline when their
paragraph has other content; a paragraph made only of images or
image-only links still renders them as blocks. Inline images now take
the line height with automatic width instead of a line-height square,
so wide badges and photos keep their aspect ratio instead of squashing.
* fix(codex): retain multi-file patch diffs in timeline details
Combine each file's diff in canonical file-change items and legacy patch
text, keeping the legacy parsers and envelope unchanged. Cover both legacy
lifecycle events and the failed status.
* fix(codex): handle path-only and moved files in patch diffs
Multi-file assembly generated headers without patch text and discarded
move destinations when reducing object kind tags. Skip textless entries
and retain move_path for generated destination headers, keeping the
single-file mapping and existing truncation limit.
Add mapper regressions for both cases, demonstrated failing on 179c3f7f6
before the fix.
* fix(codex): keep later files in bounded patch previews
Share the existing diff detail budget across files so a large first diff cannot hide later changes in canonical or legacy patch events.
Co-authored-by: Okan YILDIRIM <63511519+ragokan@users.noreply.github.com>
* fix(codex): preserve patch preview labels and move destinations
Keep complete file and hunk labels within the preview budget and count files omitted for space. Select the primary file from rendered content and retain move destinations in path-keyed and legacy events.
* fix(codex): bound generated file additions without argument overflow
* fix(codex): retain move-only files in multi-file previews
Represent moves without content changes as rename metadata and remove the provider move notice before classifying diff contents.
* fix(codex): keep move normalization inside multi-file previews
Recognize the provider move notice only while assembling multi-file diffs so the existing single-file path continues to retain its destination.
* fix(codex): retain empty additions in multi-file previews
---------
Co-authored-by: Okan YILDIRIM <63511519+ragokan@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* 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(app): show current contents of collapsed folders after Files refresh
Refresh only re-listed expanded folders, and expanding a folder skips the
request when a listing is cached, so a folder collapsed during Refresh
kept showing its old contents. Drop the cached listings Refresh does not
re-list. The refresh helper moves to file-explorer/tree.ts so it can be
tested.
* test(app): cover refreshing collapsed folders through the browser
Exercise the reported file creation, refresh and reopen sequence through a real daemon, then check refresh while expanded.
Co-authored-by: hadan8977 <zc-huang23@mails.tsinghua.edu.cn>
* fix(app): ignore directory replies invalidated by Refresh
A directory request started before Refresh could finish afterwards and restore a stale collapsed listing. Track the refresh generation in explorer state and accept completion only from the current generation. Keep directory loading and invalidation in the file-explorer model.
* fix(app): preserve expansion when Refresh cancels restoration
Keep an invalidated directory operation distinct from a failed listing. Propagate cancellation through saved-folder restoration so it preserves the current expansion state instead of committing an incomplete restored path set.
---------
Co-authored-by: hadan8977 <zc-huang23@mails.tsinghua.edu.cn>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): keep HTML preview fragment links inside the preview
A file previewed through `srcDoc` resolves `<a href="#section">` against the
app's URL, so the link loaded Paseo inside the iframe and the preview became
the Welcome screen. Handle same-document fragment clicks from a small script
in the preview document, and let the page's own handlers go first.
Closes#7465
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(app): preserve native HTML fragment navigation in previews
Give each HTML preview its own document URL so section links resolve within the file. Browser default navigation preserves cancellation by page handlers without an injected click interceptor.
Co-authored-by: 3ae3ae <ogyshjsm@naver.com>
---------
Co-authored-by: 3ae3ae <ogyshjsm@naver.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): keep chat status toasts visible below floating headers
Compact native chats extend beneath the floating header. Panel toasts need measured header clearance and a layer above its fade; placing them below the fade leaves an excessive gap. Keep dock overlays outside keyboard translation so opening the keyboard cannot move them offscreen.
* test(app): verify rendered chat status spacing on native devices
Measure the real header and toast bounds with the software keyboard closed and open. The former arithmetic helper test passed even when the viewport stopped consuming header clearance; the native regression fails against that broken production wiring.
* test(app): exercise toast placement through host disconnection
Observe the public reconnecting state with host connectivity interrupted externally. Keep native bounds and software keyboard assertions while removing runtime evaluation and private timeline-owner mutations.
* 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(app): add hosts without leaving the current screen
A retained Settings route replayed its addHost query after appearance remounts. Own the connection flow outside screen lifetimes and let each consumer choose its completion navigation.
* fix(app): release the project dialog before adding a host
* fix(app): size the QR scanner viewport on wide layouts
* test(app): align creation and desktop setup with in-place host flow
* fix(app): render tables immediately after paragraphs
Insert a paragraph break before GFM tables adjacent to prose so MD4C
recognizes them, preserving quote/list containers and line endings.
* fix(app): preserve table headers during source adaptation
Replace the preceding line ending when inserting a table separator.
This keeps the separator before the header's source range, so entity
and local image edits cannot consume the paragraph break.
---------
Co-authored-by: Okan YILDIRIM <63511519+ragokan@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: thsrite <thsrite@gmail.com>
Co-authored-by: Woosuk Nam <me@suk.kr>
* fix(claude): read the session rename from the end of a large transcript
Importing a Claude session read the whole transcript into one UTF-8 string to
look for its `custom-title` record, so a transcript past V8's maximum string
length failed the import with `RangeError: Invalid string length` before the
incremental history reader ever ran. The scan now reads the file backwards a
chunk at a time, which keeps the original intent of not walking the transcript
and never holds it as one string. Records without the needle are not decoded, so
a transcript with no rename costs a byte search per chunk.
Closes#8451
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(app): wait for send readiness in composer recall
The previous prompt can appear and clear its draft before the send acknowledgement arrives. Wait for Send to be enabled before pressing Enter so recall setup does not silently leave the next prompt unsent.
Co-authored-by: qwist1233-cpu <qwist1233@gmail.com>
* fix(claude): scan large transcript records once
Retain unfinished record chunks and assemble each record once instead of copying and rescanning a growing buffer on every backward read. Cover import of a large record after the last explicit rename.
---------
Co-authored-by: qwist1233-cpu <qwist1233@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
The browser terminal is a renderer and leaves protocol replies to the
daemon, but it still answered XTVERSION (CSI > q) and DECRQSS
(DCS $ q ... ST). Those replies went out through onData as if the user
had typed them, so a query from a program in the shell left text like
">|xterm.js(6.1.0-beta.292)" on the next input line. Both queries are
now swallowed with the other protocol queries.
Fixes#8111
* 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.
Invalidate refresh satisfaction when subscriptions are released, and retry
if demand returns before the overtaken refresh completes. Otherwise the
same connection source can stay satisfied with no live list listeners.
Cover both demand sources and completion orderings with live delta checks.
Co-authored-by: Okan YILDIRIM <63511519+ragokan@users.noreply.github.com>
The web renderer does not map accessibilityState.checked onto aria-checked. Supply the checked property explicitly so accessibility clients agree with the checkmark and connection URI.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): keep the open workspace visible in collapsed sidebar groups
A collapsed group showed its first seven rows plus working and
needs-attention rows, so an idle open workspace past the limit was
hidden behind Show more and no sidebar row was highlighted.
selectSidebarGroupVisibility now takes the route selection. When the
collapsed limit would hide the open workspace, it is returned as
leadingItem and rendered first in project groups, status groups and
both Pinned sections. Rows the group already shows stay in place,
expanded groups keep group order, and drag lists render the leading
row outside their sortable data, so a reorder never saves its borrowed
position.
Refs #8078
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(app): keep shortcut targets visible in collapsed groups
The navigation journey still required the selected overflow workspace to stay hidden. Assert its visible selection while preserving collapsed-group navigation and the seven ordinary rows.
Co-authored-by: 0xRoy <1997roylee@gmail.com>
---------
Co-authored-by: 0xRoy <1997roylee@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
The direct connection form treated any host containing a colon as a bare
IPv6 address, so entering "100.66.69.36:6767" or "tcp://host:6767" built
"tcp://[100.66.69.36:6767]:6767" and surfaced the browser's raw "Invalid URL"
error. Normalize the host input first: strip a leading scheme and any
path/query/fragment, split "host:port" (the inline port wins), and only treat
unbracketed hosts with two or more colons as IPv6.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Browsers omit navigator.clipboard outside secure contexts, such as the daemon web UI served over plain http on a Tailscale or LAN address. The non-Mac Ctrl+C/Ctrl+V terminal shortcuts then threw synchronously, and Ctrl+V had already prevented the native paste, so terminal copy/paste broke. When the Clipboard API is unavailable, the shortcuts now leave the default action alone so xterm's native copy/paste event handlers do the work, and the browser clipboard rejects instead of throwing.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(composer): insert suggested commands into drafts
* fix(composer): close suggestions after command insertion
* fix(composer): place caret after inserted commands
* fix(app): open a nested-repo file when the workspace-root path is missing
A workspace that contains another git repo often gets paths written
relative to that nested repo. `schema/x.md` is really
`repo-a/schema/x.md`. A markdown link was joined onto the workspace
root and opened a missing file. Inline code already asked the daemon
for a suffix match.
Resolved workspace-relative paths that contain a slash now use that
same lookup before opening. An exact hit stays on the original path.
A suffix hit replaces it and keeps the line range. A failed lookup
still opens the original path. Bare names, absolute paths, and `~/`
paths are unchanged.
Example:
- Input: workspace `/repo`, link `schema/x.md`, file at `repo-a/schema/x.md`
- Before: open `/repo/schema/x.md` (missing)
- After: open `/repo/repo-a/schema/x.md`
* fix(app): preserve explicit destinations when finding nested files
Relative chat links can name a file inside a nested repository. Retain the suffix lookup while using the original href and document base to preserve absolute paths, preview links, and folder destinations.
Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com>
* fix(app): keep network share links on their absolute destination
Use the shared absolute-path classifier so network share hrefs retain their destination alongside drive and POSIX paths.
* fix(app): keep nested-file lookup inside ordered path opening
Resolve the original directory before considering suffix matches, and allocate click ordering before asynchronous lookup. The existing opener now owns the complete operation so a slow earlier click cannot replace a later selection.
* test(app): load UI modules before test lifecycle hooks
Cold module transformation exceeded the lifecycle hook deadline before these suites could run. Complete module initialization before registering the tests, retaining their assertions and timeout settings.
---------
Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
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.
Shape rows in native Canvas and CoreText, then place clusters on xterm columns so fallback font advances cannot separate text from its cursor. Wait for measured dimensions before attachment and repaint overhanging ink with affected rows.
Use Android platform ICU to avoid bundling approximately 14 MB of data; retain and document the Android 10 conjunct limitation.
* 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(app): keep failed chat history until retry
Automatic retries repeatedly reopen provider sessions after persistent failures. Keep the error visible until the user retries, while retaining reconnect recovery.
* fix(app): catch up chats after subscription failure
A failed replacement subscription leaves chats unsubscribed, so discard its old catch-up state and fetch missed history after retry.
* fix(app): recover gaps during history retry
Keep gap recovery active while a user-initiated retry is running so the chat is not marked current before its follow-up fetch.
* fix(app): show history errors in archived chats
Cached archived chats need the same manual retry affordance when history synchronization fails.
Backport the Unistyles 3.5.3 Activity configuration fix while keeping 3.2.4 and its existing fixes. Application resources could leave the responsive shell on the previous fold size after resume. Cover the real device-state sequence with a native UI regression.
The MiniMax Code catalog entry ran `npx -y @minimax-ai/code@0.6.13 acp`.
npx derives the executable from the unscoped package name, which is `code`,
but the package publishes `mcode` and `mcode-tools`. npm therefore exits
with "could not determine executable to run" before the agent starts.
Paseo reports this as a provider that timed out during `initialize`, so the
entry looks like a broken agent rather than a malformed command.
Name the package and the binary separately. The ACP handshake then completes
and the provider reports its models and modes.
Co-authored-by: Peanut-HZM <peanut@hzm.me>
* 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
The app no longer locks to portrait. Phones and tablets on Android and iOS follow the device rotation and respect the system rotation lock, so Android tablets fill the display in landscape instead of letterboxing.
Modals pass all orientations explicitly because React Native presents iPhone modals in portrait by default.
Closes#1669
Co-authored-by: yz <43207690+yzim@users.noreply.github.com>
Co-authored-by: magiclaw <930198090@qq.com>
* fix(app): keep split dividers under the mouse before Explorer is first opened
The main row only left the Explorer pane out once the Explorer had been
shown, because the split container read the registered pane id directly.
Before that, the hidden Explorer stayed in the row with its share of the
sizes, and every divider drag moved about 1.28 times the mouse distance.
Resolve the pane the same way the workspace screen does, so the default
Explorer pane is left out from the start.
Closes#5998
* fix(app): keep split dividers under gesture input
Measure the gesture from its initial press so activation does not discard the movement before its threshold. Cover coarse-pointer mouse and touch input alongside app-created workspaces, Explorer changes and reload.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
supply uploads every locale folder under fastlane/metadata/android in one
edit. Play rejects the F-Droid-only ja locale, which failed the whole edit in
the 0.14.0 release ("ja - Invalid request"), so no screenshots were replaced.
Stage only the en-US images, add a standalone workflow to rerun the upload for
the current release, and document the App Store associatedErrors gotcha.
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)
Reanimated's web path saves a custom keyframe's element rect when the
animation ends, and writes it back as inline top/left/width/height when
it cleans the keyframe up 750ms after the start. A menu whose content
resized in between, such as a plugin popover whose data had just
arrived, was left clipped to its loading-state size or hanging over its
anchor.
Play the same keyframes through the Web Animations API on web, which
leaves no inline style, and drop the height release that only undid
part of that write. Native keeps the Reanimated Keyframe.
* feat(release): publish store screenshots from the repo
Stable releases now upload the committed store renders: the App Store
review lane attaches the iPhone and iPad sets to the version it submits,
and a new Play lane replaces the phone and tablet screenshots after the
Android submit. F-Droid reads the same Android files, which now link to
the store renders instead of the website images.
The Play lane needs its own copy of the submit service-account key
(GOOGLE_PLAY_JSON_KEY in the EAS production environment) because the key
Expo stores for eas submit is not available to custom jobs, and EAS
Metadata does not handle screenshots for either store.
* fix(release): rewrite the Android screenshot links on every render
Google Play and F-Droid read numbered links to the Google renders. The
renderer now rewrites them from the panels it exported, so adding or
renaming a panel reaches all three stores.
The reset countdown floored to one unit, so a weekly window read "resets 2d"
from 2d 23h down to 2d 0h and looked stuck for a day; the session window had
the same problem with hours. Countdowns now use two units ("2d 23h", "4h 59m"),
and every row also shows the clock time it resets ("7:20 AM", "Sat 8:00 AM",
"Oct 31, 3:00 PM"), so users can plan work around a reset without hovering.
Rows now put the window name and percent on one line, the bar under it, and the
reset on its own full-width line, so the exact time fits at phone width in
longer languages. The pinned-row background is gone; the pin glyph, drawn in
the foreground colour when pinned, is the only pinned signal. Bars are 6px.
* 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>