Commit Graph
3210 Commits
Author SHA1 Message Date
Mohamed Boudra 19eefc8cfb feat(app): the client and app work on options and controls
The agent snapshot's `options` are the state and `controls` only draw them.
Running and closed agents draw from the snapshot; drafts and profile forms ask
`provider.controls.list.request` with their `providerOptions`, so aliases such
as a legacy Fast profile's `service_tier: "fast"` are resolved once, by the
daemon, and the draft keeps Fast highlighted. A draft adds only the user's
edits and each provider's last-used control values; applying a profile seeds
its non-control options (e.g. `sandbox_mode`) into the draft, and a profile
form saves them back instead of pruning to the controls it can draw.

A daemon without `server_info.features.providerOptionControls` still sends
`features` and `featureValues`. `LegacyProviderOptions` in the client package
is the one place that translates snapshots, draft listings, create and apply
requests, and daemon-config profiles, so the app never branches on it.
Persisted preferences and tab layouts rename `featureValues` through tagged
COMPAT preprocessors.

The features/controls bridge moves from the server into the protocol package
so the client can reuse it.
2026-11-04 05:00:32 +01:00
Mohamed Boudra a880c9619d feat: background agents for hidden helpers (#8683)
* feat(server): add background agents

An agent can be created background in any workspace. It stays a durable,
addressable agent, but default discovery, workspace status and attention
leave it out. Effective background is agent.background ||
workspace.background.

- Protocol: `create_agent_request.background` now marks the agent itself
  and stays the workspace intent for a workspace the request creates.
  Snapshots carry `background`. The daemon advertises
  `features.backgroundAgents`. Clients advertise the `background_agents`
  capability.
- Daemon: the flag persists in the agent record, so restart and resume
  keep it. AgentDirectory owns visibility. Inclusive reads and
  subscriptions from sources without `background_agents` see only agents
  in background workspaces, because older apps would otherwise open a tab
  for each one.
- Inheritance: omitted on agent-scoped creation, children and new
  workspaces take the caller agent's background.
- MCP: create_agent accepts `background`.
- SDK: placing a background agent in an existing workspace, or under a
  caller, fails fast with "Update the host to use background agents." on
  older hosts.
- CLI: `ls -a` and `ls --background` add a BACKGROUND column.

* feat(app): hide background agents from tabs, track and status

* docs(plugins): point hidden helpers at background agents

* fix: keep background agents hidden across cache reload and connected hosts

* fix: keep remote creation working on older hosts and inherit foreign parents' background
2026-11-04 07:19:21 +07:00
Mohamed Boudra 6740d41fab Open plugin settings from a cog, with a tab per settings screen (#8675)
* feat(app): open plugin settings from a cog with a tab per screen

The plugin row shows a settings button next to its switch when the plugin
registers settings screens. The actions menu keeps Logs, Reload and Remove.
The settings page shows one tab per registered screen; a plugin with one
screen shows no tabs. Tabs swap screenId in place, so existing
plugins/[pluginId]/[screenId] links and openSettings keep working.

* fix(app): render settings tab icons inside the plugin's runtime

Component icons now mount under the installation provider, so plugin SDK
hooks such as useTranslation work. Each icon has its own error boundary;
an icon that throws leaves its tab with the label alone.

* feat(app): name only the plugin in its settings page header

The header shows the plugin's manifest name, or its id when it has none,
for single- and multi-screen plugins alike. The selected tab names the
screen. The Plugins page and the header share one plugin list query.

* feat(app): show plugin display names and a Settings tooltip on the cog

Plugin rows on Settings → Plugins show the manifest name, falling back to
the id; when a name is set, the id stays visible beneath it. The row's
controls, logs sheet, confirmations and feedback use the same name.
The cog is a ToolbarButton labeled Settings, with a matching tooltip.
2026-11-04 07:19:13 +07:00
Mohamed Boudra ca6b54fc2e feat(app): mark composer pills that open a menu and keep the pill track's space (#8673)
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.
2026-11-04 07:19:06 +07:00
paseo-bot[bot]andSeth Jeon 40b95f70f4 Fix Android chat mouse-wheel direction (#8693)
* 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>
2026-11-03 19:25:40 +00:00
1c4400194a Support hardware keyboard shortcuts across native and desktop apps (#8625)
* 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>
2026-11-03 18:08:27 +07:00
Tahir Khan b807c261ca Show progress while plugin screens load (#8123)
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
2026-11-03 06:05:00 +00:00
Mohamed Boudra 85be2121b8 Link #123 issue and pull request references in chat (#8636)
* 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.
2026-11-03 13:00:24 +07:00
Mohamed Boudra f4eeb8a538 chore(release): reconcile main with v0.15.0 (#8635)
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.
2026-11-03 12:44:50 +07:00
Mohamed Boudra fc710be1af fix(app): keep linked badges inline in rendered Markdown on web (#8530)
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
`[![Retrigger](badge.svg)](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.
2026-11-03 11:46:15 +07:00
paseo-bot[bot]andOkan YILDIRIM 66e1523f2f Keep Codex multi-file patch diffs in the timeline (#8613)
* 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>
2026-11-03 04:06:44 +00:00
Mohamed Boudra 336e7d1b89 Add first-run setup that walks new users through Paseo (#8363)
* 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.
2026-11-03 10:28:26 +07:00
Mohamed Boudra 6f6712be62 Make interrupted-agent recovery opt-in (#8619)
* fix(agents): require opt-in for restart recovery

Keep automatic interrupted-turn resumption off during further dogfooding. Preserve explicit opt-in and custom resume prompts.

* test(settings): verify restart recovery opt-in persists

* docs(agents): describe restart recovery opt-in
2026-11-03 10:20:26 +07:00
paseo-bot[bot]andhadan8977 e06adf7a3e Show new files in collapsed folders after Refresh (#8582)
* 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>
2026-11-02 21:59:28 +00:00
7495c20f78 fix(app): keep HTML section links inside the preview (#8559)
* 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>
2026-11-02 15:54:49 +00:00
Mohamed Boudra b92a34ab3d Keep reconnecting and updating status visible in native chats (#8568)
* 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.
2026-11-02 22:49:53 +07:00
Mohamed Boudra d4ac64f335 Let plugins stream live events and binary data to the app (#8527)
* 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.
2026-11-02 14:10:15 +07:00
Mohamed Boudra 70cb34927a fix(app): keep question choices open until explicit navigation (#8525)
Allow description clicks to select options while users copy text. Removing automatic advance keeps that interaction reversible; links remain independent and submission stays explicit.
2026-11-02 14:08:07 +07:00
da8d58bfee fix(plugins): preserve agent message provenance in timeline transforms (#8492)
Co-authored-by: luke <luopeihuan@gmail.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-11-02 05:04:23 +00:00
Mohamed Boudra 7c2812dde6 fix(app): add hosts without leaving the current screen (#8512)
* 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
2026-11-02 11:07:48 +07:00
e2f0779379 Render Markdown tables written directly below prose (#8485)
* 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>
2026-11-02 03:30:52 +00:00
3e83f433da Import large Claude sessions without exceeding the string limit (#8457)
* 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>
2026-11-01 15:56:42 +00:00
Dominic 8130147426 fix(app): stop the terminal from typing its version reply into the shell (#8419)
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
2026-11-01 10:04:25 +00:00
Mohamed Boudra ef77b823ae chore(release): reconcile 0.15.0-beta.1 metadata (#8443)
* docs(changelog): prepare 0.15.0-beta.1

(cherry picked from commit 7e5ad53491)

* chore(release): cut 0.15.0-beta.1

(cherry picked from commit 8bd38fb236)

* test(docs): follow nested worktrees page
2026-11-01 12:56:41 +07:00
Mohamed Boudra da75aa7a4a Fix terminal emoji size and alignment (#8375)
* 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.
2026-11-01 10:33:59 +07:00
paseo-bot[bot]andOkan YILDIRIM 2d7adfc450 fix(app): restore directory subscriptions after demand returns (#8403)
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>
2026-10-31 23:58:14 +00:00
paseo-bot[bot] 7d9741ef77 fix(app): expose the SSL checkbox checked state on web (#8399)
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>
2026-10-31 22:50:27 +00:00
f0dfb9134c fix(app): keep the open workspace visible in collapsed groups (#8395)
* 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>
2026-10-31 20:03:31 +00:00
QTomandClaude Opus 5.5 2dd931c8bd fix(app): accept host:port and URLs in the direct connection host field (#8223)
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>
2026-10-31 18:06:12 +00:00
QTomandClaude Opus 5.5 58eafd81e5 fix(app): fall back to native terminal copy/paste without the Clipboard API (#8230)
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>
2026-10-31 17:01:27 +00:00
Mohamed Boudra 1008e9fffc Insert suggested commands into the composer (#8371)
* feat(composer): insert suggested commands into drafts

* fix(composer): close suggestions after command insertion

* fix(composer): place caret after inserted commands
2026-10-31 19:25:16 +07:00
paseo-bot[bot]andOuyang Xingyuan 0e5bcbd9f5 fix(app): open relative chat links inside nested repositories (#8361)
* 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>
2026-10-31 11:05:06 +00:00
Mohamed Boudra 254166e7c1 feat(terminal): add native selection handles (#8348)
* feat(terminal): add native selection handles

* fix(terminal): preserve selection at row boundaries

* fix(terminal): preserve selection boundary when handles cross
2026-10-31 14:23:10 +07:00
Mohamed Boudra 9c926ed126 fix(terminal): preserve 10,000 rows with direct ANSI replay (#8282)
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.
2026-10-31 10:51:45 +07:00
Mohamed Boudra 0a549945b5 fix(terminal): keep native text aligned with terminal cells (#8336)
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.
2026-10-31 09:45:57 +07:00
Mohamed Boudra 0be49c0620 feat(transport): keep large messages intact across relay connections (#8302)
Negotiate shared binary chunk framing below application messages and routing, with bounded logical assembly and paced writes that respect encrypted relay limits. Preserve legacy admission, reconnect state, sibling route isolation, and native single-frame upload behavior.
2026-10-31 09:26:24 +07:00
Mohamed Boudra 184ab66cde Show sub-agents as launch markers in the parent timeline (#8276)
* 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.
2026-10-30 21:26:38 +07:00
Mohamed Boudra 385c29b655 Keep failed chat history available for retry (#8278)
* 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.
2026-10-30 17:57:52 +07:00
Mohamed Boudra 9477fe0c00 fix(app): refresh Android layout after background fold changes (#8285)
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.
2026-10-30 17:22:53 +07:00
PeanutandPeanut-HZM 1f79efcd62 fix(providers): name the MiniMax Code binary so npx can resolve it (#8201)
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>
2026-10-30 08:08:14 +00:00
Mohamed Boudra e3682ba18c Usage fetches only when shown, and can be turned off (#8181)
* 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
2026-10-30 14:32:19 +07:00
a8e0effb32 Follow system rotation on mobile devices (#8256)
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>
2026-10-30 13:42:38 +07:00
paseo-bot[bot] c4b4516890 Keep split dividers under the pointer across mouse and gesture input (#6011)
* 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>
2026-10-29 09:10:25 +00:00
Mohamed Boudra 75f58c4fe7 fix(release): upload only en-US screenshots to Google Play (#8195)
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.
2026-10-29 15:57:08 +07:00
Mohamed Boudra f7eef9dd0c chore(release): reconcile main with v0.14.0 (#8193)
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)
2026-10-29 15:40:29 +07:00
babo-jeonguk 61841d1ed5 fix(app): stop web menus snapping back to their entering-animation rect (#8180)
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.
2026-10-29 07:58:09 +00:00
Mohamed Boudra 7c88a48be4 Publish store screenshots from the repo on each stable release (#8186)
* 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.
2026-10-29 14:39:42 +07:00
Mohamed Boudra e12feb633f feat(usage): show when each limit resets, and make usage rows easier to read (#8162)
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.
2026-10-29 11:24:34 +07:00
Mohamed Boudra 2f0fb2a8ba Bring 0.14.0-beta.2 versions and changelog to main (#8139)
* 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.
2026-10-29 00:50:24 +07:00
Mohamed Boudraandsuntianc 83ed52669d Resume interrupted agents after host restart (#8127)
* 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>
2026-10-28 23:40:35 +07:00